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,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