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,726 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+ require 'timecop'
5
+
6
+ class TestCriteriaEvaluator < Minitest::Test
7
+ PROJECT_ENV_ID = 1
8
+ TEST_ENV_ID = 2
9
+ KEY = 'the-key'
10
+ DEFAULT_VALUE = 'default_value'
11
+ DESIRED_VALUE = 'desired_value'
12
+ WRONG_ENV_VALUE = 'wrong_env_value'
13
+
14
+ DEFAULT_VALUE_CONFIG = PrefabProto::ConfigValue.new(string: DEFAULT_VALUE)
15
+ DESIRED_VALUE_CONFIG = PrefabProto::ConfigValue.new(string: DESIRED_VALUE)
16
+
17
+ TRUE_CONFIG = PrefabProto::ConfigValue.new(bool: true)
18
+ FALSE_CONFIG = PrefabProto::ConfigValue.new(bool: false)
19
+
20
+ DEFAULT_ROW = PrefabProto::ConfigRow.new(
21
+ values: [
22
+ PrefabProto::ConditionalValue.new(value: DEFAULT_VALUE_CONFIG)
23
+ ]
24
+ )
25
+
26
+ def setup
27
+ super
28
+ @base_client = FakeBaseClient.new
29
+ end
30
+
31
+ def test_always_true
32
+ config = PrefabProto::Config.new(
33
+ id: 123,
34
+ key: KEY,
35
+ config_type: PrefabProto::ConfigType::CONFIG,
36
+ rows: [
37
+ DEFAULT_ROW,
38
+ PrefabProto::ConfigRow.new(
39
+ project_env_id: PROJECT_ENV_ID,
40
+ values: [
41
+ PrefabProto::ConditionalValue.new(
42
+ criteria: [PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)],
43
+ value: DESIRED_VALUE_CONFIG
44
+ )
45
+ ]
46
+ )
47
+ ]
48
+ )
49
+
50
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
51
+
52
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
53
+ end
54
+
55
+ def test_nested_props_in
56
+ config = PrefabProto::Config.new(
57
+ key: KEY,
58
+ rows: [
59
+ DEFAULT_ROW,
60
+ PrefabProto::ConfigRow.new(
61
+ project_env_id: PROJECT_ENV_ID,
62
+ values: [
63
+ PrefabProto::ConditionalValue.new(
64
+ criteria: [
65
+ PrefabProto::Criterion.new(
66
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
67
+ value_to_match: string_list(%w[ok fine]),
68
+ property_name: 'user.key'
69
+ )
70
+ ],
71
+ value: DESIRED_VALUE_CONFIG
72
+ )
73
+ ]
74
+ )
75
+ ]
76
+ )
77
+
78
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
79
+ namespace: nil)
80
+
81
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
82
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({ user: { key: 'wrong' } })).unwrapped_value
83
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({ user: { key: 'ok' } })).unwrapped_value
84
+ end
85
+
86
+ def test_nested_props_not_in
87
+ config = PrefabProto::Config.new(
88
+ key: KEY,
89
+ rows: [
90
+ DEFAULT_ROW,
91
+ PrefabProto::ConfigRow.new(
92
+ project_env_id: PROJECT_ENV_ID,
93
+ values: [
94
+ PrefabProto::ConditionalValue.new(
95
+ criteria: [
96
+ PrefabProto::Criterion.new(
97
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_NOT_ONE_OF,
98
+ value_to_match: string_list(%w[wrong bad]),
99
+ property_name: 'user.key'
100
+ )
101
+ ],
102
+ value: DESIRED_VALUE_CONFIG
103
+ )
104
+ ]
105
+ )
106
+ ]
107
+ )
108
+
109
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
110
+ namespace: nil)
111
+
112
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
113
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({ user: { key: 'ok' } })).unwrapped_value
114
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({ user: { key: 'wrong' } })).unwrapped_value
115
+ end
116
+
117
+ def test_prop_is_one_of
118
+ config = PrefabProto::Config.new(
119
+ key: KEY,
120
+ rows: [
121
+ PrefabProto::ConfigRow.new(
122
+ project_env_id: PROJECT_ENV_ID,
123
+ values: [
124
+ PrefabProto::ConditionalValue.new(
125
+ criteria: [
126
+ PrefabProto::Criterion.new(
127
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
128
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
129
+ property_name: 'user.email_suffix'
130
+ )
131
+ ],
132
+ value: DESIRED_VALUE_CONFIG
133
+ )
134
+ ]
135
+ ),
136
+ DEFAULT_ROW
137
+ ]
138
+ )
139
+
140
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
141
+ namespace: nil)
142
+
143
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
144
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email_suffix: 'prefab.cloud' })).unwrapped_value
145
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email_suffix: 'hotmail.com' })).unwrapped_value
146
+ end
147
+
148
+ def test_prop_is_not_one_of
149
+ config = PrefabProto::Config.new(
150
+ key: KEY,
151
+ rows: [
152
+ DEFAULT_ROW,
153
+ PrefabProto::ConfigRow.new(
154
+ project_env_id: PROJECT_ENV_ID,
155
+ values: [
156
+ PrefabProto::ConditionalValue.new(
157
+ criteria: [
158
+ PrefabProto::Criterion.new(
159
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_NOT_ONE_OF,
160
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
161
+ property_name: 'user.email_suffix'
162
+ )
163
+ ],
164
+ value: DESIRED_VALUE_CONFIG
165
+ )
166
+ ]
167
+ )
168
+ ]
169
+ )
170
+
171
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
172
+ namespace: nil)
173
+
174
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
175
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email_suffix: 'prefab.cloud' })).unwrapped_value
176
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email_suffix: 'hotmail.com' })).unwrapped_value
177
+ end
178
+
179
+ def test_prop_ends_with_one_of
180
+ config = PrefabProto::Config.new(
181
+ key: KEY,
182
+ rows: [
183
+ DEFAULT_ROW,
184
+ PrefabProto::ConfigRow.new(
185
+ project_env_id: PROJECT_ENV_ID,
186
+ values: [
187
+ PrefabProto::ConditionalValue.new(
188
+ criteria: [
189
+ PrefabProto::Criterion.new(
190
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
191
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
192
+ property_name: 'user.email'
193
+ )
194
+ ],
195
+ value: DESIRED_VALUE_CONFIG
196
+ )
197
+ ]
198
+ )
199
+ ]
200
+ )
201
+
202
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
203
+ namespace: nil)
204
+
205
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
206
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
207
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@hotmail.com' })).unwrapped_value
208
+ end
209
+
210
+ def test_prop_does_not_end_with_one_of
211
+ config = PrefabProto::Config.new(
212
+ key: KEY,
213
+ rows: [
214
+ DEFAULT_ROW,
215
+ PrefabProto::ConfigRow.new(
216
+ project_env_id: PROJECT_ENV_ID,
217
+ values: [
218
+ PrefabProto::ConditionalValue.new(
219
+ criteria: [
220
+ PrefabProto::Criterion.new(
221
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_DOES_NOT_END_WITH_ONE_OF,
222
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
223
+ property_name: 'user.email'
224
+ )
225
+ ],
226
+ value: DESIRED_VALUE_CONFIG
227
+ )
228
+ ]
229
+ )
230
+ ]
231
+ )
232
+
233
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
234
+ namespace: nil)
235
+
236
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
237
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
238
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@hotmail.com' })).unwrapped_value
239
+ end
240
+
241
+ def test_in_seg
242
+ segment_key = 'segment_key'
243
+
244
+ segment_config = PrefabProto::Config.new(
245
+ config_type: PrefabProto::ConfigType::SEGMENT,
246
+ key: segment_key,
247
+ rows: [
248
+ PrefabProto::ConfigRow.new(
249
+ values: [
250
+ PrefabProto::ConditionalValue.new(
251
+ value: TRUE_CONFIG,
252
+ criteria: [
253
+ PrefabProto::Criterion.new(
254
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
255
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
256
+ property_name: 'user.email'
257
+ )
258
+ ]
259
+ ),
260
+ PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
261
+ ]
262
+ )
263
+ ]
264
+ )
265
+
266
+ config = PrefabProto::Config.new(
267
+ key: KEY,
268
+ rows: [
269
+ DEFAULT_ROW,
270
+
271
+ PrefabProto::ConfigRow.new(
272
+ project_env_id: PROJECT_ENV_ID,
273
+ values: [
274
+ PrefabProto::ConditionalValue.new(
275
+ criteria: [
276
+ PrefabProto::Criterion.new(
277
+ operator: PrefabProto::Criterion::CriterionOperator::IN_SEG,
278
+ value_to_match: PrefabProto::ConfigValue.new(string: segment_key)
279
+ )
280
+ ],
281
+ value: DESIRED_VALUE_CONFIG
282
+ )
283
+ ]
284
+ )
285
+ ]
286
+ )
287
+
288
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID,
289
+ base_client: @base_client, namespace: nil,
290
+ resolver: resolver_fake({ segment_key => segment_config }))
291
+
292
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
293
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
294
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@hotmail.com' })).unwrapped_value
295
+ end
296
+
297
+ def test_not_in_seg
298
+ segment_key = 'segment_key'
299
+
300
+ segment_config = PrefabProto::Config.new(
301
+ config_type: PrefabProto::ConfigType::SEGMENT,
302
+ key: segment_key,
303
+ rows: [
304
+ PrefabProto::ConfigRow.new(
305
+ values: [
306
+ PrefabProto::ConditionalValue.new(
307
+ value: TRUE_CONFIG,
308
+ criteria: [
309
+ PrefabProto::Criterion.new(
310
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
311
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
312
+ property_name: 'user.email'
313
+ )
314
+ ]
315
+ ),
316
+ PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
317
+ ]
318
+ )
319
+ ]
320
+ )
321
+
322
+ config = PrefabProto::Config.new(
323
+ key: KEY,
324
+ rows: [
325
+ DEFAULT_ROW,
326
+ PrefabProto::ConfigRow.new(
327
+ project_env_id: PROJECT_ENV_ID,
328
+ values: [
329
+ PrefabProto::ConditionalValue.new(
330
+ criteria: [
331
+ PrefabProto::Criterion.new(
332
+ operator: PrefabProto::Criterion::CriterionOperator::NOT_IN_SEG,
333
+ value_to_match: PrefabProto::ConfigValue.new(string: segment_key)
334
+ )
335
+ ],
336
+ value: DESIRED_VALUE_CONFIG
337
+ )
338
+ ]
339
+ )
340
+ ]
341
+ )
342
+
343
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID,
344
+ base_client: @base_client, namespace: nil,
345
+ resolver: resolver_fake({ segment_key => segment_config }))
346
+
347
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
348
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
349
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@hotmail.com' })).unwrapped_value
350
+ end
351
+
352
+ def test_multiple_conditions_in_one_value
353
+ segment_key = 'segment_key'
354
+
355
+ segment_config = PrefabProto::Config.new(
356
+ config_type: PrefabProto::ConfigType::SEGMENT,
357
+ key: segment_key,
358
+ rows: [
359
+ PrefabProto::ConfigRow.new(
360
+ values: [
361
+ PrefabProto::ConditionalValue.new(
362
+ value: TRUE_CONFIG,
363
+ criteria: [
364
+ PrefabProto::Criterion.new(
365
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
366
+ value_to_match: string_list(['prefab.cloud', 'gmail.com']),
367
+ property_name: 'user.email'
368
+ ),
369
+
370
+ PrefabProto::Criterion.new(
371
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
372
+ value_to_match: TRUE_CONFIG,
373
+ property_name: 'user.admin'
374
+ )
375
+ ]
376
+ ),
377
+ PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
378
+ ]
379
+ )
380
+ ]
381
+ )
382
+
383
+ config = PrefabProto::Config.new(
384
+ key: KEY,
385
+ rows: [
386
+ DEFAULT_ROW,
387
+ PrefabProto::ConfigRow.new(
388
+ project_env_id: PROJECT_ENV_ID,
389
+ values: [
390
+ PrefabProto::ConditionalValue.new(
391
+ criteria: [
392
+ PrefabProto::Criterion.new(
393
+ operator: PrefabProto::Criterion::CriterionOperator::IN_SEG,
394
+ value_to_match: PrefabProto::ConfigValue.new(string: segment_key)
395
+ ),
396
+
397
+ PrefabProto::Criterion.new(
398
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_NOT_ONE_OF,
399
+ value_to_match: TRUE_CONFIG,
400
+ property_name: 'user.deleted'
401
+ )
402
+ ],
403
+ value: DESIRED_VALUE_CONFIG
404
+ )
405
+ ]
406
+ )
407
+ ]
408
+ )
409
+
410
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID,
411
+ base_client: @base_client, namespace: nil,
412
+ resolver: resolver_fake({ segment_key => segment_config }))
413
+
414
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
415
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
416
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud', admin: true })).unwrapped_value
417
+ assert_equal DEFAULT_VALUE,
418
+ evaluator.evaluate(context(user: { email: 'example@prefab.cloud', admin: true, deleted: true })).unwrapped_value
419
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@gmail.com' })).unwrapped_value
420
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@gmail.com', admin: true })).unwrapped_value
421
+ assert_equal DEFAULT_VALUE,
422
+ evaluator.evaluate(context(user: { email: 'example@gmail.com', admin: true, deleted: true })).unwrapped_value
423
+ end
424
+
425
+ def test_multiple_conditions_in_multiple_values
426
+ segment_key = 'segment_key'
427
+
428
+ segment_config = PrefabProto::Config.new(
429
+ config_type: PrefabProto::ConfigType::SEGMENT,
430
+ key: segment_key,
431
+ rows: [
432
+ PrefabProto::ConfigRow.new(
433
+ values: [
434
+ PrefabProto::ConditionalValue.new(
435
+ value: TRUE_CONFIG,
436
+ criteria: [
437
+ PrefabProto::Criterion.new(
438
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
439
+ value_to_match: string_list(['prefab.cloud', 'gmail.com']),
440
+ property_name: 'user.email'
441
+ )
442
+ ]
443
+ ),
444
+ PrefabProto::ConditionalValue.new(
445
+ value: TRUE_CONFIG,
446
+ criteria: [
447
+ PrefabProto::Criterion.new(
448
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
449
+ value_to_match: TRUE_CONFIG,
450
+ property_name: 'user.admin'
451
+ )
452
+ ]
453
+ ),
454
+ PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
455
+ ]
456
+ )
457
+ ]
458
+ )
459
+
460
+ config = PrefabProto::Config.new(
461
+ key: KEY,
462
+ rows: [
463
+ DEFAULT_ROW,
464
+ PrefabProto::ConfigRow.new(
465
+ project_env_id: PROJECT_ENV_ID,
466
+ values: [
467
+ PrefabProto::ConditionalValue.new(
468
+ criteria: [
469
+ PrefabProto::Criterion.new(
470
+ operator: PrefabProto::Criterion::CriterionOperator::IN_SEG,
471
+ value_to_match: PrefabProto::ConfigValue.new(string: segment_key)
472
+ ),
473
+
474
+ PrefabProto::Criterion.new(
475
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_NOT_ONE_OF,
476
+ value_to_match: TRUE_CONFIG,
477
+ property_name: 'user.deleted'
478
+ )
479
+ ],
480
+ value: DESIRED_VALUE_CONFIG
481
+ )
482
+ ]
483
+ )
484
+ ]
485
+ )
486
+
487
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID,
488
+ base_client: @base_client, namespace: nil,
489
+ resolver: resolver_fake({ segment_key => segment_config }))
490
+
491
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
492
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
493
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { admin: true })).unwrapped_value
494
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud', admin: true })).unwrapped_value
495
+ assert_equal DEFAULT_VALUE,
496
+ evaluator.evaluate(context(user: { email: 'example@prefab.cloud', admin: true, deleted: true })).unwrapped_value
497
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@gmail.com' })).unwrapped_value
498
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@gmail.com', admin: true })).unwrapped_value
499
+ assert_equal DEFAULT_VALUE,
500
+ evaluator.evaluate(context(user: { email: 'example@gmail.com', admin: true, deleted: true })).unwrapped_value
501
+ end
502
+
503
+ def test_stringifying_property_values_and_names
504
+ config = PrefabProto::Config.new(
505
+ key: KEY,
506
+ rows: [
507
+ DEFAULT_ROW,
508
+ PrefabProto::ConfigRow.new(
509
+ project_env_id: PROJECT_ENV_ID,
510
+ values: [
511
+ PrefabProto::ConditionalValue.new(
512
+ criteria: [
513
+ PrefabProto::Criterion.new(
514
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
515
+ value_to_match: string_list(%w[1 true hello]),
516
+ property_name: 'team.name'
517
+ )
518
+ ],
519
+ value: DESIRED_VALUE_CONFIG
520
+ )
521
+ ]
522
+ )
523
+ ]
524
+ )
525
+
526
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil,
527
+ namespace: nil, base_client: @base_client)
528
+
529
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
530
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(team: { name: 'prefab.cloud' })).unwrapped_value
531
+
532
+ [1, true, :hello].each do |value|
533
+ [:name, 'name'].each do |property_name|
534
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(team: { property_name => value })).unwrapped_value
535
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(team: { property_name => value.to_s })).unwrapped_value
536
+ end
537
+ end
538
+ end
539
+
540
+ def test_in_int_range
541
+ config = PrefabProto::Config.new(
542
+ key: KEY,
543
+ rows: [
544
+ PrefabProto::ConfigRow.new(
545
+ project_env_id: PROJECT_ENV_ID,
546
+ values: [
547
+ PrefabProto::ConditionalValue.new(
548
+ criteria: [
549
+ PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::IN_INT_RANGE,
550
+ value_to_match: PrefabProto::ConfigValue.new(int_range: PrefabProto::IntRange.new(start: 30, end: 40)), property_name: 'user.age')
551
+ ],
552
+ value: DESIRED_VALUE_CONFIG
553
+ ),
554
+
555
+ PrefabProto::ConditionalValue.new(
556
+ criteria: [
557
+ PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)
558
+ ],
559
+ value: PrefabProto::ConfigValue.new(string: DEFAULT_VALUE)
560
+ )
561
+ ]
562
+ )
563
+ ]
564
+ )
565
+
566
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil,
567
+ namespace: nil, base_client: @base_client)
568
+
569
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
570
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({ 'user' => { 'age' => 32 } })).unwrapped_value
571
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({ 'user' => { 'age' => 29 } })).unwrapped_value
572
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({ 'user' => { 'age' => 41 } })).unwrapped_value
573
+ end
574
+
575
+ def test_in_int_range_for_time
576
+ now = Time.now
577
+
578
+ config = PrefabProto::Config.new(
579
+ key: KEY,
580
+ rows: [
581
+ PrefabProto::ConfigRow.new(
582
+ project_env_id: PROJECT_ENV_ID,
583
+ values: [
584
+ PrefabProto::ConditionalValue.new(
585
+ criteria: [
586
+ PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::IN_INT_RANGE,
587
+ value_to_match: PrefabProto::ConfigValue.new(
588
+ int_range: PrefabProto::IntRange.new(
589
+ start: (now.to_i - 60) * 1000, end: (now.to_i + 60) * 1000
590
+ )
591
+ ), property_name: 'prefab.current-time')
592
+ ],
593
+ value: DESIRED_VALUE_CONFIG
594
+ ),
595
+
596
+ PrefabProto::ConditionalValue.new(
597
+ criteria: [
598
+ PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)
599
+ ],
600
+ value: PrefabProto::ConfigValue.new(string: DEFAULT_VALUE)
601
+ )
602
+ ]
603
+ )
604
+ ]
605
+ )
606
+
607
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil,
608
+ namespace: nil, base_client: @base_client)
609
+
610
+ Timecop.freeze(now) do
611
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
612
+ end
613
+
614
+ Timecop.freeze(now - 60) do
615
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
616
+ end
617
+
618
+ Timecop.freeze(now - 61) do
619
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
620
+ end
621
+
622
+ Timecop.freeze(now + 59) do
623
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
624
+ end
625
+
626
+ Timecop.freeze(now + 60) do
627
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
628
+ end
629
+ end
630
+
631
+ def test_evaluating_a_log_level
632
+ config = PrefabProto::Config.new(
633
+ id: 999,
634
+ key: 'log-level',
635
+ config_type: PrefabProto::ConfigType::LOG_LEVEL,
636
+ rows: [
637
+ PrefabProto::ConfigRow.new(
638
+ values: [
639
+ PrefabProto::ConditionalValue.new(
640
+ criteria: [PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)],
641
+ value: PrefabProto::ConfigValue.new(log_level: PrefabProto::LogLevel::DEBUG)
642
+ )
643
+ ]
644
+ )
645
+ ]
646
+ )
647
+
648
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
649
+
650
+ assert_equal :DEBUG, evaluator.evaluate(context({})).unwrapped_value
651
+ end
652
+
653
+ def test_evaluating_a_weighted_value
654
+ config = PrefabProto::Config.new(
655
+ id: 123,
656
+ key: KEY,
657
+ config_type: PrefabProto::ConfigType::CONFIG,
658
+ rows: [
659
+ PrefabProto::ConfigRow.new(
660
+ project_env_id: PROJECT_ENV_ID,
661
+ values: [
662
+ PrefabProto::ConditionalValue.new(
663
+ criteria: [PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)],
664
+ value: PrefabProto::ConfigValue.new(weighted_values: weighted_values([['abc', 98], ['def', 1], ['ghi', 1]]))
665
+ )
666
+ ]
667
+ )
668
+ ]
669
+ )
670
+
671
+ evaluator = Prefab::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
672
+
673
+ 2.times do
674
+ assert_equal 'abc', evaluator.evaluate(context('user' => { 'key' => '1' })).unwrapped_value
675
+ end
676
+
677
+ 3.times do
678
+ context = context({ 'user' => { 'key' => '12' } })
679
+ assert_equal 'def', evaluator.evaluate(context).unwrapped_value
680
+ end
681
+
682
+ context = context({ 'user' => { 'key' => '4' } })
683
+ assert_equal 'ghi', evaluator.evaluate(context).unwrapped_value
684
+ end
685
+
686
+ private
687
+
688
+ def string_list(values)
689
+ PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: values))
690
+ end
691
+
692
+ class FakeResolver
693
+ def initialize(config, base_client)
694
+ @config = config
695
+ @base_client = base_client
696
+ end
697
+
698
+ def raw(key)
699
+ @config[key]
700
+ end
701
+
702
+ def get(key, properties = {})
703
+ # This only gets called for segments, so we don't need to pass in a resolver
704
+ Prefab::CriteriaEvaluator.new(@config[key], project_env_id: nil, resolver: nil,
705
+ namespace: nil, base_client: @base_client).evaluate(properties)
706
+ end
707
+ end
708
+
709
+ def resolver_fake(config)
710
+ FakeResolver.new(config, @base_client)
711
+ end
712
+
713
+ class FakeBaseClient
714
+ def initialize
715
+ Prefab::LoggerClient.new($stdout)
716
+ end
717
+
718
+ def evaluation_summary_aggregator
719
+ @evaluation_summary_aggregator ||= Prefab::EvaluationSummaryAggregator.new(client: self, max_keys: 9999, sync_interval: 9999)
720
+ end
721
+
722
+ def instance_hash
723
+ 'fake-base-client-instance_hash'
724
+ end
725
+ end
726
+ end