sdk-reforge 1.9.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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc.sample +3 -0
  3. data/.github/CODEOWNERS +2 -0
  4. data/.github/pull_request_template.md +8 -0
  5. data/.github/workflows/ruby.yml +48 -0
  6. data/.gitmodules +3 -0
  7. data/.rubocop.yml +13 -0
  8. data/.tool-versions +1 -0
  9. data/CHANGELOG.md +257 -0
  10. data/CODEOWNERS +1 -0
  11. data/Gemfile +29 -0
  12. data/Gemfile.lock +182 -0
  13. data/LICENSE.txt +20 -0
  14. data/README.md +105 -0
  15. data/Rakefile +63 -0
  16. data/VERSION +1 -0
  17. data/compile_protos.sh +20 -0
  18. data/dev/allocation_stats +60 -0
  19. data/dev/benchmark +40 -0
  20. data/dev/console +12 -0
  21. data/dev/script_setup.rb +18 -0
  22. data/lib/prefab_pb.rb +77 -0
  23. data/lib/reforge/caching_http_connection.rb +95 -0
  24. data/lib/reforge/client.rb +133 -0
  25. data/lib/reforge/config_client.rb +275 -0
  26. data/lib/reforge/config_client_presenter.rb +18 -0
  27. data/lib/reforge/config_loader.rb +67 -0
  28. data/lib/reforge/config_resolver.rb +84 -0
  29. data/lib/reforge/config_value_unwrapper.rb +123 -0
  30. data/lib/reforge/config_value_wrapper.rb +18 -0
  31. data/lib/reforge/context.rb +241 -0
  32. data/lib/reforge/context_shape.rb +20 -0
  33. data/lib/reforge/context_shape_aggregator.rb +70 -0
  34. data/lib/reforge/criteria_evaluator.rb +345 -0
  35. data/lib/reforge/duration.rb +58 -0
  36. data/lib/reforge/encryption.rb +65 -0
  37. data/lib/reforge/error.rb +6 -0
  38. data/lib/reforge/errors/env_var_parse_error.rb +11 -0
  39. data/lib/reforge/errors/initialization_timeout_error.rb +12 -0
  40. data/lib/reforge/errors/invalid_sdk_key_error.rb +19 -0
  41. data/lib/reforge/errors/missing_default_error.rb +13 -0
  42. data/lib/reforge/errors/missing_env_var_error.rb +11 -0
  43. data/lib/reforge/errors/uninitialized_error.rb +13 -0
  44. data/lib/reforge/evaluation.rb +53 -0
  45. data/lib/reforge/evaluation_summary_aggregator.rb +86 -0
  46. data/lib/reforge/example_contexts_aggregator.rb +77 -0
  47. data/lib/reforge/exponential_backoff.rb +21 -0
  48. data/lib/reforge/feature_flag_client.rb +43 -0
  49. data/lib/reforge/fixed_size_hash.rb +14 -0
  50. data/lib/reforge/http_connection.rb +45 -0
  51. data/lib/reforge/internal_logger.rb +43 -0
  52. data/lib/reforge/javascript_stub.rb +99 -0
  53. data/lib/reforge/local_config_parser.rb +151 -0
  54. data/lib/reforge/murmer3.rb +50 -0
  55. data/lib/reforge/options.rb +191 -0
  56. data/lib/reforge/periodic_sync.rb +74 -0
  57. data/lib/reforge/prefab.rb +120 -0
  58. data/lib/reforge/rate_limit_cache.rb +41 -0
  59. data/lib/reforge/resolved_config_presenter.rb +86 -0
  60. data/lib/reforge/semver.rb +132 -0
  61. data/lib/reforge/sse_config_client.rb +112 -0
  62. data/lib/reforge/time_helpers.rb +7 -0
  63. data/lib/reforge/weighted_value_resolver.rb +42 -0
  64. data/lib/reforge/yaml_config_parser.rb +34 -0
  65. data/lib/reforge-sdk.rb +57 -0
  66. data/test/fixtures/datafile.json +87 -0
  67. data/test/integration_test.rb +171 -0
  68. data/test/integration_test_helpers.rb +114 -0
  69. data/test/support/common_helpers.rb +201 -0
  70. data/test/support/mock_base_client.rb +41 -0
  71. data/test/support/mock_config_client.rb +19 -0
  72. data/test/support/mock_config_loader.rb +1 -0
  73. data/test/test_caching_http_connection.rb +218 -0
  74. data/test/test_client.rb +351 -0
  75. data/test/test_config_client.rb +84 -0
  76. data/test/test_config_loader.rb +82 -0
  77. data/test/test_config_resolver.rb +502 -0
  78. data/test/test_config_value_unwrapper.rb +270 -0
  79. data/test/test_config_value_wrapper.rb +42 -0
  80. data/test/test_context.rb +271 -0
  81. data/test/test_context_shape.rb +50 -0
  82. data/test/test_context_shape_aggregator.rb +150 -0
  83. data/test/test_criteria_evaluator.rb +1180 -0
  84. data/test/test_duration.rb +37 -0
  85. data/test/test_encryption.rb +16 -0
  86. data/test/test_evaluation_summary_aggregator.rb +162 -0
  87. data/test/test_example_contexts_aggregator.rb +233 -0
  88. data/test/test_exponential_backoff.rb +18 -0
  89. data/test/test_feature_flag_client.rb +16 -0
  90. data/test/test_fixed_size_hash.rb +119 -0
  91. data/test/test_helper.rb +17 -0
  92. data/test/test_integration.rb +75 -0
  93. data/test/test_internal_logger.rb +25 -0
  94. data/test/test_javascript_stub.rb +176 -0
  95. data/test/test_local_config_parser.rb +147 -0
  96. data/test/test_logger_initialization.rb +12 -0
  97. data/test/test_options.rb +93 -0
  98. data/test/test_prefab.rb +16 -0
  99. data/test/test_rate_limit_cache.rb +44 -0
  100. data/test/test_semver.rb +108 -0
  101. data/test/test_sse_config_client.rb +211 -0
  102. data/test/test_weighted_value_resolver.rb +71 -0
  103. metadata +345 -0
@@ -0,0 +1,1180 @@
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 = Reforge::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 = Reforge::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 = Reforge::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 = Reforge::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 = Reforge::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 = Reforge::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 = Reforge::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_prop_starts_with_one_of
242
+ config = PrefabProto::Config.new(
243
+ key: KEY,
244
+ rows: [
245
+ DEFAULT_ROW,
246
+ PrefabProto::ConfigRow.new(
247
+ project_env_id: PROJECT_ENV_ID,
248
+ values: [
249
+ PrefabProto::ConditionalValue.new(
250
+ criteria: [
251
+ PrefabProto::Criterion.new(
252
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_STARTS_WITH_ONE_OF,
253
+ value_to_match: string_list(['one', 'two']),
254
+ property_name: 'user.testProperty'
255
+ )
256
+ ],
257
+ value: DESIRED_VALUE_CONFIG
258
+ )
259
+ ]
260
+ )
261
+ ]
262
+ )
263
+
264
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
265
+ namespace: nil)
266
+
267
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
268
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { testProperty: 'three dogs' })).unwrapped_value
269
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { testProperty: 'one tree' })).unwrapped_value
270
+ end
271
+
272
+ def test_prop_does_not_start_with_one_of
273
+ config = PrefabProto::Config.new(
274
+ key: KEY,
275
+ rows: [
276
+ DEFAULT_ROW,
277
+ PrefabProto::ConfigRow.new(
278
+ project_env_id: PROJECT_ENV_ID,
279
+ values: [
280
+ PrefabProto::ConditionalValue.new(
281
+ criteria: [
282
+ PrefabProto::Criterion.new(
283
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_DOES_NOT_START_WITH_ONE_OF,
284
+ value_to_match: string_list(['one', 'two']),
285
+ property_name: 'user.testProperty'
286
+ )
287
+ ],
288
+ value: DESIRED_VALUE_CONFIG
289
+ )
290
+ ]
291
+ )
292
+ ]
293
+ )
294
+
295
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
296
+ namespace: nil)
297
+
298
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
299
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { testProperty: 'three dogs' })).unwrapped_value
300
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { testProperty: 'one tree' })).unwrapped_value
301
+ end
302
+
303
+
304
+ def test_prop_contains_one_of
305
+ config = PrefabProto::Config.new(
306
+ key: KEY,
307
+ rows: [
308
+ DEFAULT_ROW,
309
+ PrefabProto::ConfigRow.new(
310
+ project_env_id: PROJECT_ENV_ID,
311
+ values: [
312
+ PrefabProto::ConditionalValue.new(
313
+ criteria: [
314
+ PrefabProto::Criterion.new(
315
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_CONTAINS_ONE_OF,
316
+ value_to_match: string_list(['one', 'two']),
317
+ property_name: 'user.testProperty'
318
+ )
319
+ ],
320
+ value: DESIRED_VALUE_CONFIG
321
+ )
322
+ ]
323
+ )
324
+ ]
325
+ )
326
+
327
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
328
+ namespace: nil)
329
+
330
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
331
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { testProperty: 'three dogs' })).unwrapped_value
332
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { testProperty: 'see one tree' })).unwrapped_value
333
+ end
334
+
335
+ def test_prop_does_not_contain_one_of
336
+ config = PrefabProto::Config.new(
337
+ key: KEY,
338
+ rows: [
339
+ DEFAULT_ROW,
340
+ PrefabProto::ConfigRow.new(
341
+ project_env_id: PROJECT_ENV_ID,
342
+ values: [
343
+ PrefabProto::ConditionalValue.new(
344
+ criteria: [
345
+ PrefabProto::Criterion.new(
346
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_DOES_NOT_CONTAIN_ONE_OF,
347
+ value_to_match: string_list(['one', 'two']),
348
+ property_name: 'user.testProperty'
349
+ )
350
+ ],
351
+ value: DESIRED_VALUE_CONFIG
352
+ )
353
+ ]
354
+ )
355
+ ]
356
+ )
357
+
358
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
359
+ namespace: nil)
360
+
361
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
362
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { testProperty: 'three dogs' })).unwrapped_value
363
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { testProperty: 'see one tree' })).unwrapped_value
364
+ end
365
+
366
+ def test_in_seg
367
+ segment_key = 'segment_key'
368
+
369
+ segment_config = PrefabProto::Config.new(
370
+ config_type: PrefabProto::ConfigType::SEGMENT,
371
+ key: segment_key,
372
+ rows: [
373
+ PrefabProto::ConfigRow.new(
374
+ values: [
375
+ PrefabProto::ConditionalValue.new(
376
+ value: TRUE_CONFIG,
377
+ criteria: [
378
+ PrefabProto::Criterion.new(
379
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
380
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
381
+ property_name: 'user.email'
382
+ )
383
+ ]
384
+ ),
385
+ PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
386
+ ]
387
+ )
388
+ ]
389
+ )
390
+
391
+ config = PrefabProto::Config.new(
392
+ key: KEY,
393
+ rows: [
394
+ DEFAULT_ROW,
395
+
396
+ PrefabProto::ConfigRow.new(
397
+ project_env_id: PROJECT_ENV_ID,
398
+ values: [
399
+ PrefabProto::ConditionalValue.new(
400
+ criteria: [
401
+ PrefabProto::Criterion.new(
402
+ operator: PrefabProto::Criterion::CriterionOperator::IN_SEG,
403
+ value_to_match: PrefabProto::ConfigValue.new(string: segment_key)
404
+ )
405
+ ],
406
+ value: DESIRED_VALUE_CONFIG
407
+ )
408
+ ]
409
+ )
410
+ ]
411
+ )
412
+
413
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID,
414
+ base_client: @base_client, namespace: nil,
415
+ resolver: resolver_fake({ segment_key => segment_config }))
416
+
417
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
418
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
419
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@hotmail.com' })).unwrapped_value
420
+ end
421
+
422
+ def test_not_in_seg
423
+ segment_key = 'segment_key'
424
+
425
+ segment_config = PrefabProto::Config.new(
426
+ config_type: PrefabProto::ConfigType::SEGMENT,
427
+ key: segment_key,
428
+ rows: [
429
+ PrefabProto::ConfigRow.new(
430
+ values: [
431
+ PrefabProto::ConditionalValue.new(
432
+ value: TRUE_CONFIG,
433
+ criteria: [
434
+ PrefabProto::Criterion.new(
435
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
436
+ value_to_match: string_list(['hotmail.com', 'gmail.com']),
437
+ property_name: 'user.email'
438
+ )
439
+ ]
440
+ ),
441
+ PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
442
+ ]
443
+ )
444
+ ]
445
+ )
446
+
447
+ config = PrefabProto::Config.new(
448
+ key: KEY,
449
+ rows: [
450
+ DEFAULT_ROW,
451
+ PrefabProto::ConfigRow.new(
452
+ project_env_id: PROJECT_ENV_ID,
453
+ values: [
454
+ PrefabProto::ConditionalValue.new(
455
+ criteria: [
456
+ PrefabProto::Criterion.new(
457
+ operator: PrefabProto::Criterion::CriterionOperator::NOT_IN_SEG,
458
+ value_to_match: PrefabProto::ConfigValue.new(string: segment_key)
459
+ )
460
+ ],
461
+ value: DESIRED_VALUE_CONFIG
462
+ )
463
+ ]
464
+ )
465
+ ]
466
+ )
467
+
468
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID,
469
+ base_client: @base_client, namespace: nil,
470
+ resolver: resolver_fake({ segment_key => segment_config }))
471
+
472
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
473
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
474
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@hotmail.com' })).unwrapped_value
475
+ end
476
+
477
+ def test_multiple_conditions_in_one_value
478
+ segment_key = 'segment_key'
479
+
480
+ segment_config = PrefabProto::Config.new(
481
+ config_type: PrefabProto::ConfigType::SEGMENT,
482
+ key: segment_key,
483
+ rows: [
484
+ PrefabProto::ConfigRow.new(
485
+ values: [
486
+ PrefabProto::ConditionalValue.new(
487
+ value: TRUE_CONFIG,
488
+ criteria: [
489
+ PrefabProto::Criterion.new(
490
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
491
+ value_to_match: string_list(['prefab.cloud', 'gmail.com']),
492
+ property_name: 'user.email'
493
+ ),
494
+
495
+ PrefabProto::Criterion.new(
496
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
497
+ value_to_match: TRUE_CONFIG,
498
+ property_name: 'user.admin'
499
+ )
500
+ ]
501
+ ),
502
+ PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
503
+ ]
504
+ )
505
+ ]
506
+ )
507
+
508
+ config = PrefabProto::Config.new(
509
+ key: KEY,
510
+ rows: [
511
+ DEFAULT_ROW,
512
+ PrefabProto::ConfigRow.new(
513
+ project_env_id: PROJECT_ENV_ID,
514
+ values: [
515
+ PrefabProto::ConditionalValue.new(
516
+ criteria: [
517
+ PrefabProto::Criterion.new(
518
+ operator: PrefabProto::Criterion::CriterionOperator::IN_SEG,
519
+ value_to_match: PrefabProto::ConfigValue.new(string: segment_key)
520
+ ),
521
+
522
+ PrefabProto::Criterion.new(
523
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_NOT_ONE_OF,
524
+ value_to_match: TRUE_CONFIG,
525
+ property_name: 'user.deleted'
526
+ )
527
+ ],
528
+ value: DESIRED_VALUE_CONFIG
529
+ )
530
+ ]
531
+ )
532
+ ]
533
+ )
534
+
535
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID,
536
+ base_client: @base_client, namespace: nil,
537
+ resolver: resolver_fake({ segment_key => segment_config }))
538
+
539
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
540
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
541
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud', admin: true })).unwrapped_value
542
+ assert_equal DEFAULT_VALUE,
543
+ evaluator.evaluate(context(user: { email: 'example@prefab.cloud', admin: true, deleted: true })).unwrapped_value
544
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@gmail.com' })).unwrapped_value
545
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@gmail.com', admin: true })).unwrapped_value
546
+ assert_equal DEFAULT_VALUE,
547
+ evaluator.evaluate(context(user: { email: 'example@gmail.com', admin: true, deleted: true })).unwrapped_value
548
+ end
549
+
550
+ def test_multiple_conditions_in_multiple_values
551
+ segment_key = 'segment_key'
552
+
553
+ segment_config = PrefabProto::Config.new(
554
+ config_type: PrefabProto::ConfigType::SEGMENT,
555
+ key: segment_key,
556
+ rows: [
557
+ PrefabProto::ConfigRow.new(
558
+ values: [
559
+ PrefabProto::ConditionalValue.new(
560
+ value: TRUE_CONFIG,
561
+ criteria: [
562
+ PrefabProto::Criterion.new(
563
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
564
+ value_to_match: string_list(['prefab.cloud', 'gmail.com']),
565
+ property_name: 'user.email'
566
+ )
567
+ ]
568
+ ),
569
+ PrefabProto::ConditionalValue.new(
570
+ value: TRUE_CONFIG,
571
+ criteria: [
572
+ PrefabProto::Criterion.new(
573
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
574
+ value_to_match: TRUE_CONFIG,
575
+ property_name: 'user.admin'
576
+ )
577
+ ]
578
+ ),
579
+ PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
580
+ ]
581
+ )
582
+ ]
583
+ )
584
+
585
+ config = PrefabProto::Config.new(
586
+ key: KEY,
587
+ rows: [
588
+ DEFAULT_ROW,
589
+ PrefabProto::ConfigRow.new(
590
+ project_env_id: PROJECT_ENV_ID,
591
+ values: [
592
+ PrefabProto::ConditionalValue.new(
593
+ criteria: [
594
+ PrefabProto::Criterion.new(
595
+ operator: PrefabProto::Criterion::CriterionOperator::IN_SEG,
596
+ value_to_match: PrefabProto::ConfigValue.new(string: segment_key)
597
+ ),
598
+
599
+ PrefabProto::Criterion.new(
600
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_NOT_ONE_OF,
601
+ value_to_match: TRUE_CONFIG,
602
+ property_name: 'user.deleted'
603
+ )
604
+ ],
605
+ value: DESIRED_VALUE_CONFIG
606
+ )
607
+ ]
608
+ )
609
+ ]
610
+ )
611
+
612
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID,
613
+ base_client: @base_client, namespace: nil,
614
+ resolver: resolver_fake({ segment_key => segment_config }))
615
+
616
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
617
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud' })).unwrapped_value
618
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { admin: true })).unwrapped_value
619
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@prefab.cloud', admin: true })).unwrapped_value
620
+ assert_equal DEFAULT_VALUE,
621
+ evaluator.evaluate(context(user: { email: 'example@prefab.cloud', admin: true, deleted: true })).unwrapped_value
622
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@gmail.com' })).unwrapped_value
623
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { email: 'example@gmail.com', admin: true })).unwrapped_value
624
+ assert_equal DEFAULT_VALUE,
625
+ evaluator.evaluate(context(user: { email: 'example@gmail.com', admin: true, deleted: true })).unwrapped_value
626
+ end
627
+
628
+ def test_stringifying_property_values_and_names
629
+ config = PrefabProto::Config.new(
630
+ key: KEY,
631
+ rows: [
632
+ DEFAULT_ROW,
633
+ PrefabProto::ConfigRow.new(
634
+ project_env_id: PROJECT_ENV_ID,
635
+ values: [
636
+ PrefabProto::ConditionalValue.new(
637
+ criteria: [
638
+ PrefabProto::Criterion.new(
639
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_IS_ONE_OF,
640
+ value_to_match: string_list(%w[1 true hello]),
641
+ property_name: 'team.name'
642
+ )
643
+ ],
644
+ value: DESIRED_VALUE_CONFIG
645
+ )
646
+ ]
647
+ )
648
+ ]
649
+ )
650
+
651
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil,
652
+ namespace: nil, base_client: @base_client)
653
+
654
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
655
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(team: { name: 'prefab.cloud' })).unwrapped_value
656
+
657
+ [1, true, :hello].each do |value|
658
+ [:name, 'name'].each do |property_name|
659
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(team: { property_name => value })).unwrapped_value
660
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(team: { property_name => value.to_s })).unwrapped_value
661
+ end
662
+ end
663
+ end
664
+
665
+ def test_in_int_range
666
+ config = PrefabProto::Config.new(
667
+ key: KEY,
668
+ rows: [
669
+ PrefabProto::ConfigRow.new(
670
+ project_env_id: PROJECT_ENV_ID,
671
+ values: [
672
+ PrefabProto::ConditionalValue.new(
673
+ criteria: [
674
+ PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::IN_INT_RANGE,
675
+ value_to_match: PrefabProto::ConfigValue.new(int_range: PrefabProto::IntRange.new(start: 30, end: 40)), property_name: 'user.age')
676
+ ],
677
+ value: DESIRED_VALUE_CONFIG
678
+ ),
679
+
680
+ PrefabProto::ConditionalValue.new(
681
+ criteria: [
682
+ PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)
683
+ ],
684
+ value: PrefabProto::ConfigValue.new(string: DEFAULT_VALUE)
685
+ )
686
+ ]
687
+ )
688
+ ]
689
+ )
690
+
691
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil,
692
+ namespace: nil, base_client: @base_client)
693
+
694
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
695
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({ 'user' => { 'age' => 32 } })).unwrapped_value
696
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({ 'user' => { 'age' => 29 } })).unwrapped_value
697
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({ 'user' => { 'age' => 41 } })).unwrapped_value
698
+ end
699
+
700
+ def test_in_int_range_for_time
701
+ now = Time.now
702
+
703
+ config = PrefabProto::Config.new(
704
+ key: KEY,
705
+ rows: [
706
+ PrefabProto::ConfigRow.new(
707
+ project_env_id: PROJECT_ENV_ID,
708
+ values: [
709
+ PrefabProto::ConditionalValue.new(
710
+ criteria: [
711
+ PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::IN_INT_RANGE,
712
+ value_to_match: PrefabProto::ConfigValue.new(
713
+ int_range: PrefabProto::IntRange.new(
714
+ start: (now.to_i - 60) * 1000, end: (now.to_i + 60) * 1000
715
+ )
716
+ ), property_name: 'prefab.current-time')
717
+ ],
718
+ value: DESIRED_VALUE_CONFIG
719
+ ),
720
+
721
+ PrefabProto::ConditionalValue.new(
722
+ criteria: [
723
+ PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)
724
+ ],
725
+ value: PrefabProto::ConfigValue.new(string: DEFAULT_VALUE)
726
+ )
727
+ ]
728
+ )
729
+ ]
730
+ )
731
+
732
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil,
733
+ namespace: nil, base_client: @base_client)
734
+
735
+ Timecop.freeze(now) do
736
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
737
+ end
738
+
739
+ Timecop.freeze(now - 60) do
740
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
741
+ end
742
+
743
+ Timecop.freeze(now - 61) do
744
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
745
+ end
746
+
747
+ Timecop.freeze(now + 59) do
748
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
749
+ end
750
+
751
+ Timecop.freeze(now + 60) do
752
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
753
+ end
754
+ end
755
+
756
+ def test_evaluating_a_log_level
757
+ config = PrefabProto::Config.new(
758
+ id: 999,
759
+ key: 'log-level',
760
+ config_type: PrefabProto::ConfigType::LOG_LEVEL,
761
+ rows: [
762
+ PrefabProto::ConfigRow.new(
763
+ values: [
764
+ PrefabProto::ConditionalValue.new(
765
+ criteria: [PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)],
766
+ value: PrefabProto::ConfigValue.new(log_level: PrefabProto::LogLevel::DEBUG)
767
+ )
768
+ ]
769
+ )
770
+ ]
771
+ )
772
+
773
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
774
+
775
+ assert_equal :DEBUG, evaluator.evaluate(context({})).unwrapped_value
776
+ end
777
+
778
+ def test_evaluating_a_weighted_value
779
+ config = PrefabProto::Config.new(
780
+ id: 123,
781
+ key: KEY,
782
+ config_type: PrefabProto::ConfigType::CONFIG,
783
+ rows: [
784
+ PrefabProto::ConfigRow.new(
785
+ project_env_id: PROJECT_ENV_ID,
786
+ values: [
787
+ PrefabProto::ConditionalValue.new(
788
+ criteria: [PrefabProto::Criterion.new(operator: PrefabProto::Criterion::CriterionOperator::ALWAYS_TRUE)],
789
+ value: PrefabProto::ConfigValue.new(weighted_values: weighted_values([['abc', 98], ['def', 1], ['ghi', 1]]))
790
+ )
791
+ ]
792
+ )
793
+ ]
794
+ )
795
+
796
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
797
+
798
+ 2.times do
799
+ assert_equal 'abc', evaluator.evaluate(context('user' => { 'key' => '1' })).unwrapped_value
800
+ end
801
+
802
+ 3.times do
803
+ context = context({ 'user' => { 'key' => '12' } })
804
+ assert_equal 'def', evaluator.evaluate(context).unwrapped_value
805
+ end
806
+
807
+ context = context({ 'user' => { 'key' => '4' } })
808
+ assert_equal 'ghi', evaluator.evaluate(context).unwrapped_value
809
+ end
810
+
811
+ def test_prop_regex_matches
812
+ config = PrefabProto::Config.new(
813
+ key: KEY,
814
+ rows: [
815
+ DEFAULT_ROW,
816
+ PrefabProto::ConfigRow.new(
817
+ project_env_id: PROJECT_ENV_ID,
818
+ values: [
819
+ PrefabProto::ConditionalValue.new(
820
+ criteria: [
821
+ PrefabProto::Criterion.new(
822
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_MATCHES,
823
+ value_to_match: PrefabProto::ConfigValue.new(string: "a+b+"),
824
+ property_name: 'user.testProperty'
825
+ )
826
+ ],
827
+ value: DESIRED_VALUE_CONFIG
828
+ )
829
+ ]
830
+ )
831
+ ]
832
+ )
833
+
834
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
835
+ namespace: nil)
836
+
837
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
838
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { testProperty: 'abc' })).unwrapped_value
839
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { testProperty: 'aabb' })).unwrapped_value
840
+ end
841
+
842
+ def test_prop_regex_does_not_match
843
+ config = PrefabProto::Config.new(
844
+ key: KEY,
845
+ rows: [
846
+ DEFAULT_ROW,
847
+ PrefabProto::ConfigRow.new(
848
+ project_env_id: PROJECT_ENV_ID,
849
+ values: [
850
+ PrefabProto::ConditionalValue.new(
851
+ criteria: [
852
+ PrefabProto::Criterion.new(
853
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_DOES_NOT_MATCH,
854
+ value_to_match: PrefabProto::ConfigValue.new(string: "a+b+"),
855
+ property_name: 'user.testProperty'
856
+ )
857
+ ],
858
+ value: DESIRED_VALUE_CONFIG
859
+ )
860
+ ]
861
+ )
862
+ ]
863
+ )
864
+
865
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
866
+ namespace: nil)
867
+
868
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
869
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: { testProperty: 'abc' })).unwrapped_value
870
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { testProperty: 'aabb' })).unwrapped_value
871
+ end
872
+
873
+ def test_prop_regex_does_not_match_returns_false_bad_regex
874
+ config = PrefabProto::Config.new(
875
+ key: KEY,
876
+ rows: [
877
+ DEFAULT_ROW,
878
+ PrefabProto::ConfigRow.new(
879
+ project_env_id: PROJECT_ENV_ID,
880
+ values: [
881
+ PrefabProto::ConditionalValue.new(
882
+ criteria: [
883
+ PrefabProto::Criterion.new(
884
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_DOES_NOT_MATCH,
885
+ value_to_match: PrefabProto::ConfigValue.new(string: "[a+b+"),
886
+ property_name: 'user.testProperty'
887
+ )
888
+ ],
889
+ value: DESIRED_VALUE_CONFIG
890
+ )
891
+ ]
892
+ )
893
+ ]
894
+ )
895
+
896
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
897
+ namespace: nil)
898
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
899
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { testProperty: 'abc' })).unwrapped_value
900
+
901
+ assert_stderr [ "warning: character class has duplicated range: /^[a+b+$/" ]
902
+ end
903
+
904
+
905
+ def test_prop_regex_match_returns_false_bad_regex
906
+ config = PrefabProto::Config.new(
907
+ key: KEY,
908
+ rows: [
909
+ DEFAULT_ROW,
910
+ PrefabProto::ConfigRow.new(
911
+ project_env_id: PROJECT_ENV_ID,
912
+ values: [
913
+ PrefabProto::ConditionalValue.new(
914
+ criteria: [
915
+ PrefabProto::Criterion.new(
916
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_MATCHES,
917
+ value_to_match: PrefabProto::ConfigValue.new(string: "[a+b+"),
918
+ property_name: 'user.testProperty'
919
+ )
920
+ ],
921
+ value: DESIRED_VALUE_CONFIG
922
+ )
923
+ ]
924
+ )
925
+ ]
926
+ )
927
+
928
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
929
+ namespace: nil)
930
+
931
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
932
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { testProperty: 'abc' })).unwrapped_value
933
+
934
+ assert_stderr [ "warning: character class has duplicated range: /^[a+b+$/" ]
935
+ end
936
+
937
+ def test_less_than_works
938
+ config = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_LESS_THAN, property_name: 'user.age', value_to_match: 10)
939
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
940
+ namespace: nil)
941
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
942
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: 10})).unwrapped_value
943
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 9})).unwrapped_value
944
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 9.5})).unwrapped_value
945
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: 10.1})).unwrapped_value
946
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: "9"})).unwrapped_value
947
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: "9.2"})).unwrapped_value
948
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: "not a number"})).unwrapped_value
949
+ end
950
+
951
+ def test_less_or_equal_to_works
952
+ config = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_LESS_THAN_OR_EQUAL, property_name: 'user.age', value_to_match: 10)
953
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
954
+ namespace: nil)
955
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
956
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 10})).unwrapped_value
957
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 9})).unwrapped_value
958
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 9.5})).unwrapped_value
959
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: 10.1})).unwrapped_value
960
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: "9"})).unwrapped_value
961
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: "not a number"})).unwrapped_value
962
+ end
963
+
964
+
965
+ def test_greater_than_works
966
+ config = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_GREATER_THAN, property_name: 'user.age', value_to_match: 10)
967
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
968
+ namespace: nil)
969
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
970
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: 10})).unwrapped_value
971
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: 9})).unwrapped_value
972
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: 9.5})).unwrapped_value
973
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 10.1})).unwrapped_value
974
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 12})).unwrapped_value
975
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: "19"})).unwrapped_value
976
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: "not a number"})).unwrapped_value
977
+ end
978
+
979
+ def test_greater_than_or_equal_to_works
980
+ config = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_GREATER_THAN_OR_EQUAL, property_name: 'user.age', value_to_match: 10)
981
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
982
+ namespace: nil)
983
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
984
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 10})).unwrapped_value
985
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: 9})).unwrapped_value
986
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: 9.5})).unwrapped_value
987
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 10.1})).unwrapped_value
988
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user: {age: 12})).unwrapped_value
989
+
990
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: "19"})).unwrapped_value
991
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: {age: "not a number"})).unwrapped_value
992
+ end
993
+
994
+ def test_date_before_works
995
+ date = "2024-12-01T00:00:00Z"
996
+ millis = Time.iso8601(date).utc.to_i * 1000
997
+ config_with_string = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_BEFORE, property_name: 'user.joinDate', value_to_match: date)
998
+ evaluator_with_string_config = Reforge::CriteriaEvaluator.new(config_with_string, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
999
+ namespace: nil)
1000
+ config_with_millis = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_BEFORE, property_name: 'user.joinDate', value_to_match: millis)
1001
+ evaluator_with_millis_config = Reforge::CriteriaEvaluator.new(config_with_millis, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
1002
+ namespace: nil)
1003
+ assert_equal DEFAULT_VALUE, evaluator_with_millis_config.evaluate(context({})).unwrapped_value
1004
+ assert_equal DESIRED_VALUE, evaluator_with_millis_config.evaluate(context(user: {joinDate: millis-10000})).unwrapped_value
1005
+ assert_equal DESIRED_VALUE, evaluator_with_millis_config.evaluate(context(user: {joinDate: "2024-11-01T00:00:00Z"})).unwrapped_value
1006
+ assert_equal DEFAULT_VALUE, evaluator_with_millis_config.evaluate(context(user: {joinDate: millis+10000})).unwrapped_value
1007
+ assert_equal DEFAULT_VALUE, evaluator_with_millis_config.evaluate(context(user: {joinDate: "2024-12-02T00:00:00Z"})).unwrapped_value
1008
+
1009
+ assert_equal DEFAULT_VALUE, evaluator_with_string_config.evaluate(context({})).unwrapped_value
1010
+ assert_equal DESIRED_VALUE, evaluator_with_string_config.evaluate(context(user: {joinDate: millis-10000})).unwrapped_value
1011
+ assert_equal DESIRED_VALUE, evaluator_with_string_config.evaluate(context(user: {joinDate: "2024-11-01T00:00:00Z"})).unwrapped_value
1012
+ assert_equal DEFAULT_VALUE, evaluator_with_string_config.evaluate(context(user: {joinDate: millis+10000})).unwrapped_value
1013
+ assert_equal DEFAULT_VALUE, evaluator_with_string_config.evaluate(context(user: {joinDate: "2024-12-02T00:00:00Z"})).unwrapped_value
1014
+ end
1015
+
1016
+ def test_date_after_works
1017
+ date = "2024-12-01T00:00:00Z"
1018
+ millis = Time.iso8601(date).utc.to_i * 1000
1019
+ config_with_string = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_AFTER, property_name: 'user.joinDate', value_to_match: date)
1020
+ evaluator_with_string_config = Reforge::CriteriaEvaluator.new(config_with_string, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
1021
+ namespace: nil)
1022
+ config_with_millis = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_AFTER, property_name: 'user.joinDate', value_to_match: millis)
1023
+ evaluator_with_millis_config = Reforge::CriteriaEvaluator.new(config_with_millis, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client,
1024
+ namespace: nil)
1025
+ assert_equal DEFAULT_VALUE, evaluator_with_millis_config.evaluate(context({})).unwrapped_value
1026
+ assert_equal DEFAULT_VALUE, evaluator_with_millis_config.evaluate(context(user: {joinDate: millis-10000})).unwrapped_value
1027
+ assert_equal DEFAULT_VALUE, evaluator_with_millis_config.evaluate(context(user: {joinDate: "2024-11-01T00:00:00Z"})).unwrapped_value
1028
+ assert_equal DESIRED_VALUE, evaluator_with_millis_config.evaluate(context(user: {joinDate: millis+10000})).unwrapped_value
1029
+ assert_equal DESIRED_VALUE, evaluator_with_millis_config.evaluate(context(user: {joinDate: "2024-12-02T00:00:00Z"})).unwrapped_value
1030
+
1031
+ assert_equal DEFAULT_VALUE, evaluator_with_string_config.evaluate(context({})).unwrapped_value
1032
+ assert_equal DEFAULT_VALUE, evaluator_with_string_config.evaluate(context(user: {joinDate: millis-10000})).unwrapped_value
1033
+ assert_equal DEFAULT_VALUE, evaluator_with_string_config.evaluate(context(user: {joinDate: "2024-11-01T00:00:00Z"})).unwrapped_value
1034
+ assert_equal DESIRED_VALUE, evaluator_with_string_config.evaluate(context(user: {joinDate: millis+10000})).unwrapped_value
1035
+ assert_equal DESIRED_VALUE, evaluator_with_string_config.evaluate(context(user: {joinDate: "2024-12-02T00:00:00Z"})).unwrapped_value
1036
+ end
1037
+
1038
+ def test_semver_less_than
1039
+ config = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_SEMVER_LESS_THAN, property_name: 'user.version', value_to_match: "2.0.0")
1040
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
1041
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
1042
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user:{version: "nonsense"})).unwrapped_value
1043
+
1044
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user:{version: "1.0.0"})).unwrapped_value
1045
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user:{version: "2.0.0"})).unwrapped_value
1046
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user:{version: "3.0.0"})).unwrapped_value
1047
+ end
1048
+
1049
+ def test_semver_equal_to
1050
+ config = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_SEMVER_EQUAL, property_name: 'user.version', value_to_match: "2.0.0")
1051
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
1052
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
1053
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user:{version: "nonsense"})).unwrapped_value
1054
+
1055
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user:{version: "1.0.0"})).unwrapped_value
1056
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user:{version: "2.0.0"})).unwrapped_value
1057
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user:{version: "3.0.0"})).unwrapped_value
1058
+ end
1059
+
1060
+ def test_semver_greater_than
1061
+ config = create_prefab_config(operator: PrefabProto::Criterion::CriterionOperator::PROP_SEMVER_GREATER_THAN, property_name: 'user.version', value_to_match: "2.0.0")
1062
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
1063
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context({})).unwrapped_value
1064
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user:{version: "nonsense"})).unwrapped_value
1065
+
1066
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user:{version: "1.0.0"})).unwrapped_value
1067
+ assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user:{version: "2.0.0"})).unwrapped_value
1068
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context(user:{version: "3.0.0"})).unwrapped_value
1069
+ end
1070
+
1071
+ def test_date_before_with_current_time
1072
+ future_time = Time.now.utc + 3600 # 1 hour in the future
1073
+ config = create_prefab_config(
1074
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_BEFORE,
1075
+ property_name: 'prefab.current-time',
1076
+ value_to_match: future_time.iso8601
1077
+ )
1078
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
1079
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
1080
+ end
1081
+
1082
+ def test_date_after_with_current_time
1083
+ past_time = Time.now.utc - 3600 # 1 hour in the past
1084
+ config = create_prefab_config(
1085
+ operator: PrefabProto::Criterion::CriterionOperator::PROP_AFTER,
1086
+ property_name: 'prefab.current-time',
1087
+ value_to_match: past_time.iso8601
1088
+ )
1089
+ evaluator = Reforge::CriteriaEvaluator.new(config, project_env_id: PROJECT_ENV_ID, resolver: nil, base_client: @base_client, namespace: nil)
1090
+ assert_equal DESIRED_VALUE, evaluator.evaluate(context({})).unwrapped_value
1091
+ end
1092
+
1093
+ private
1094
+
1095
+ def string_list(values)
1096
+ PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: values))
1097
+ end
1098
+
1099
+ class FakeResolver
1100
+ def initialize(config, base_client)
1101
+ @config = config
1102
+ @base_client = base_client
1103
+ end
1104
+
1105
+ def raw(key)
1106
+ @config[key]
1107
+ end
1108
+
1109
+ def get(key, properties = {})
1110
+ # This only gets called for segments, so we don't need to pass in a resolver
1111
+ Reforge::CriteriaEvaluator.new(@config[key], project_env_id: nil, resolver: nil,
1112
+ namespace: nil, base_client: @base_client).evaluate(properties)
1113
+ end
1114
+ end
1115
+
1116
+ def resolver_fake(config)
1117
+ FakeResolver.new(config, @base_client)
1118
+ end
1119
+
1120
+ class FakeBaseClient
1121
+ def evaluation_summary_aggregator
1122
+ @evaluation_summary_aggregator ||= Reforge::EvaluationSummaryAggregator.new(client: self, max_keys: 9999, sync_interval: 9999)
1123
+ end
1124
+
1125
+ def instance_hash
1126
+ 'fake-base-client-instance_hash'
1127
+ end
1128
+ end
1129
+
1130
+
1131
+ def create_prefab_config(
1132
+ key: KEY,
1133
+ project_env_id: PROJECT_ENV_ID,
1134
+ operator:,
1135
+ value_to_match:,
1136
+ property_name:,
1137
+ desired_value_config: DESIRED_VALUE_CONFIG
1138
+ )
1139
+ PrefabProto::Config.new(
1140
+ key: key,
1141
+ rows: [
1142
+ DEFAULT_ROW,
1143
+ PrefabProto::ConfigRow.new(
1144
+ project_env_id: project_env_id,
1145
+ values: [
1146
+ PrefabProto::ConditionalValue.new(
1147
+ criteria: [
1148
+ PrefabProto::Criterion.new(
1149
+ operator: operator,
1150
+ value_to_match: build_config_value(value_to_match),
1151
+ property_name: property_name
1152
+ )
1153
+ ],
1154
+ value: desired_value_config
1155
+ )
1156
+ ]
1157
+ )
1158
+ ]
1159
+ )
1160
+ end
1161
+
1162
+ def build_config_value(value)
1163
+ case value
1164
+ when Integer
1165
+ PrefabProto::ConfigValue.new(int: value)
1166
+ when Float
1167
+ PrefabProto::ConfigValue.new(double: value)
1168
+ when String
1169
+ PrefabProto::ConfigValue.new(string: value)
1170
+ when Array
1171
+ if value.all? { |v| v.is_a?(String) }
1172
+ PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: value))
1173
+ else
1174
+ raise ArgumentError, "Unsupported array type: Only arrays of strings are supported."
1175
+ end
1176
+ else
1177
+ raise ArgumentError, "Unsupported value type: #{value.class}"
1178
+ end
1179
+ end
1180
+ end