prefab-cloud-ruby 1.8.7 → 1.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/pull_request_template.md +8 -0
- data/.github/workflows/ruby.yml +1 -1
- data/.tool-versions +1 -1
- data/CHANGELOG.md +10 -0
- data/VERSION +1 -1
- data/lib/prefab/caching_http_connection.rb +95 -0
- data/lib/prefab/config_client.rb +7 -8
- data/lib/prefab/criteria_evaluator.rb +205 -2
- data/lib/prefab/fixed_size_hash.rb +14 -0
- data/lib/prefab/http_connection.rb +10 -6
- data/lib/prefab/semver.rb +132 -0
- data/lib/prefab-cloud-ruby.rb +3 -0
- data/lib/prefab_pb.rb +1 -1
- data/prefab-cloud-ruby.gemspec +26 -38
- data/test/support/common_helpers.rb +17 -12
- data/test/test_caching_http_connection.rb +218 -0
- data/test/test_criteria_evaluator.rb +436 -0
- data/test/test_fixed_size_hash.rb +119 -0
- data/test/test_semver.rb +108 -0
- data/test/test_sse_config_client.rb +1 -0
- metadata +10 -3
@@ -238,6 +238,131 @@ class TestCriteriaEvaluator < Minitest::Test
|
|
238
238
|
assert_equal DEFAULT_VALUE, evaluator.evaluate(context(user: { email: 'example@hotmail.com' })).unwrapped_value
|
239
239
|
end
|
240
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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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
|
+
|
241
366
|
def test_in_seg
|
242
367
|
segment_key = 'segment_key'
|
243
368
|
|
@@ -683,6 +808,266 @@ class TestCriteriaEvaluator < Minitest::Test
|
|
683
808
|
assert_equal 'ghi', evaluator.evaluate(context).unwrapped_value
|
684
809
|
end
|
685
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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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 = Prefab::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
|
+
|
686
1071
|
private
|
687
1072
|
|
688
1073
|
def string_list(values)
|
@@ -719,4 +1104,55 @@ class TestCriteriaEvaluator < Minitest::Test
|
|
719
1104
|
'fake-base-client-instance_hash'
|
720
1105
|
end
|
721
1106
|
end
|
1107
|
+
|
1108
|
+
|
1109
|
+
def create_prefab_config(
|
1110
|
+
key: KEY,
|
1111
|
+
project_env_id: PROJECT_ENV_ID,
|
1112
|
+
operator:,
|
1113
|
+
value_to_match:,
|
1114
|
+
property_name:,
|
1115
|
+
desired_value_config: DESIRED_VALUE_CONFIG
|
1116
|
+
)
|
1117
|
+
PrefabProto::Config.new(
|
1118
|
+
key: key,
|
1119
|
+
rows: [
|
1120
|
+
DEFAULT_ROW,
|
1121
|
+
PrefabProto::ConfigRow.new(
|
1122
|
+
project_env_id: project_env_id,
|
1123
|
+
values: [
|
1124
|
+
PrefabProto::ConditionalValue.new(
|
1125
|
+
criteria: [
|
1126
|
+
PrefabProto::Criterion.new(
|
1127
|
+
operator: operator,
|
1128
|
+
value_to_match: build_config_value(value_to_match),
|
1129
|
+
property_name: property_name
|
1130
|
+
)
|
1131
|
+
],
|
1132
|
+
value: desired_value_config
|
1133
|
+
)
|
1134
|
+
]
|
1135
|
+
)
|
1136
|
+
]
|
1137
|
+
)
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
def build_config_value(value)
|
1141
|
+
case value
|
1142
|
+
when Integer
|
1143
|
+
PrefabProto::ConfigValue.new(int: value)
|
1144
|
+
when Float
|
1145
|
+
PrefabProto::ConfigValue.new(double: value)
|
1146
|
+
when String
|
1147
|
+
PrefabProto::ConfigValue.new(string: value)
|
1148
|
+
when Array
|
1149
|
+
if value.all? { |v| v.is_a?(String) }
|
1150
|
+
PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: value))
|
1151
|
+
else
|
1152
|
+
raise ArgumentError, "Unsupported array type: Only arrays of strings are supported."
|
1153
|
+
end
|
1154
|
+
else
|
1155
|
+
raise ArgumentError, "Unsupported value type: #{value.class}"
|
1156
|
+
end
|
1157
|
+
end
|
722
1158
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'test_helper'
|
5
|
+
|
6
|
+
module Prefab
|
7
|
+
class FixedSizeHashTest < Minitest::Test
|
8
|
+
def setup
|
9
|
+
@max_size = 3
|
10
|
+
@hash = FixedSizeHash.new(@max_size)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_acts_like_a_regular_hash_when_under_max_size
|
14
|
+
@hash[:a] = 1
|
15
|
+
@hash[:b] = 2
|
16
|
+
|
17
|
+
assert_equal 1, @hash[:a]
|
18
|
+
assert_equal 2, @hash[:b]
|
19
|
+
assert_equal 2, @hash.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_enforces_max_size_by_evicting_first_added_item
|
23
|
+
@hash[:a] = 1
|
24
|
+
@hash[:b] = 2
|
25
|
+
@hash[:c] = 3
|
26
|
+
assert_equal @max_size, @hash.size
|
27
|
+
|
28
|
+
@hash[:d] = 4
|
29
|
+
assert_equal @max_size, @hash.size
|
30
|
+
assert_nil @hash[:a] # First item should be evicted
|
31
|
+
assert_equal 4, @hash[:d]
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_updating_existing_key_does_not_trigger_eviction
|
35
|
+
@hash[:a] = 1
|
36
|
+
@hash[:b] = 2
|
37
|
+
@hash[:c] = 3
|
38
|
+
|
39
|
+
@hash[:b] = 'new value' # Update existing key
|
40
|
+
|
41
|
+
assert_equal @max_size, @hash.size
|
42
|
+
assert_equal 1, @hash[:a] # First item should still be present
|
43
|
+
assert_equal 'new value', @hash[:b]
|
44
|
+
assert_equal 3, @hash[:c]
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_handles_nil_values
|
48
|
+
@hash[:a] = nil
|
49
|
+
@hash[:b] = 2
|
50
|
+
@hash[:c] = 3
|
51
|
+
@hash[:d] = 4
|
52
|
+
|
53
|
+
assert_nil @hash[:a] # First item should be evicted
|
54
|
+
assert_equal 4, @hash[:d]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_preserves_hash_methods
|
58
|
+
@hash[:a] = 1
|
59
|
+
@hash[:b] = 2
|
60
|
+
|
61
|
+
assert_equal [:a, :b], @hash.keys
|
62
|
+
assert_equal [1, 2], @hash.values
|
63
|
+
assert @hash.key?(:a)
|
64
|
+
refute @hash.key?(:z)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_handles_string_keys
|
68
|
+
@hash['a'] = 1
|
69
|
+
@hash['b'] = 2
|
70
|
+
@hash['c'] = 3
|
71
|
+
@hash['d'] = 4
|
72
|
+
|
73
|
+
assert_nil @hash['a'] # First item should be evicted
|
74
|
+
assert_equal 4, @hash['d']
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_handles_object_keys
|
78
|
+
key1 = Object.new
|
79
|
+
key2 = Object.new
|
80
|
+
key3 = Object.new
|
81
|
+
key4 = Object.new
|
82
|
+
|
83
|
+
@hash[key1] = 1
|
84
|
+
@hash[key2] = 2
|
85
|
+
@hash[key3] = 3
|
86
|
+
@hash[key4] = 4
|
87
|
+
|
88
|
+
assert_nil @hash[key1] # First item should be evicted
|
89
|
+
assert_equal 4, @hash[key4]
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_can_be_initialized_empty
|
93
|
+
assert_equal 0, @hash.size
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_enumerable_methods
|
97
|
+
@hash[:a] = 1
|
98
|
+
@hash[:b] = 2
|
99
|
+
|
100
|
+
mapped = @hash.map { |k, v| [k, v * 2] }.to_h
|
101
|
+
assert_equal({ a: 2, b: 4 }, mapped)
|
102
|
+
|
103
|
+
filtered = @hash.select { |_, v| v > 1 }
|
104
|
+
assert_equal({ b: 2 }, filtered.to_h)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_clear_maintains_max_size_constraint
|
108
|
+
@hash[:a] = 1
|
109
|
+
@hash[:b] = 2
|
110
|
+
@hash.clear
|
111
|
+
|
112
|
+
assert_equal 0, @hash.size
|
113
|
+
|
114
|
+
# Should still enforce max size after clear
|
115
|
+
(@max_size + 1).times { |i| @hash[i] = i }
|
116
|
+
assert_equal @max_size, @hash.size
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/test/test_semver.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
class TestSemanticVersion < Minitest::Test
|
3
|
+
def test_parse_valid_version
|
4
|
+
version = SemanticVersion.parse('1.2.3')
|
5
|
+
assert_equal 1, version.major
|
6
|
+
assert_equal 2, version.minor
|
7
|
+
assert_equal 3, version.patch
|
8
|
+
assert_nil version.prerelease
|
9
|
+
assert_nil version.build_metadata
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_parse_version_with_prerelease
|
13
|
+
version = SemanticVersion.parse('1.2.3-alpha.1')
|
14
|
+
assert_equal 1, version.major
|
15
|
+
assert_equal 2, version.minor
|
16
|
+
assert_equal 3, version.patch
|
17
|
+
assert_equal 'alpha.1', version.prerelease
|
18
|
+
assert_nil version.build_metadata
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_parse_version_with_build_metadata
|
22
|
+
version = SemanticVersion.parse('1.2.3+build.123')
|
23
|
+
assert_equal 1, version.major
|
24
|
+
assert_equal 2, version.minor
|
25
|
+
assert_equal 3, version.patch
|
26
|
+
assert_nil version.prerelease
|
27
|
+
assert_equal 'build.123', version.build_metadata
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_parse_full_version
|
31
|
+
version = SemanticVersion.parse('1.2.3-alpha.1+build.123')
|
32
|
+
assert_equal 1, version.major
|
33
|
+
assert_equal 2, version.minor
|
34
|
+
assert_equal 3, version.patch
|
35
|
+
assert_equal 'alpha.1', version.prerelease
|
36
|
+
assert_equal 'build.123', version.build_metadata
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_parse_invalid_version
|
40
|
+
assert_raises(ArgumentError) { SemanticVersion.parse('invalid') }
|
41
|
+
assert_raises(ArgumentError) { SemanticVersion.parse('1.2') }
|
42
|
+
assert_raises(ArgumentError) { SemanticVersion.parse('1.2.3.4') }
|
43
|
+
assert_raises(ArgumentError) { SemanticVersion.parse('') }
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_parse_quietly
|
47
|
+
assert_nil SemanticVersion.parse_quietly('invalid')
|
48
|
+
refute_nil SemanticVersion.parse_quietly('1.2.3')
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_to_string
|
52
|
+
assert_equal '1.2.3', SemanticVersion.parse('1.2.3').to_s
|
53
|
+
assert_equal '1.2.3-alpha.1', SemanticVersion.parse('1.2.3-alpha.1').to_s
|
54
|
+
assert_equal '1.2.3+build.123', SemanticVersion.parse('1.2.3+build.123').to_s
|
55
|
+
assert_equal '1.2.3-alpha.1+build.123', SemanticVersion.parse('1.2.3-alpha.1+build.123').to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_equality
|
59
|
+
v1 = SemanticVersion.parse('1.2.3')
|
60
|
+
v2 = SemanticVersion.parse('1.2.3')
|
61
|
+
v3 = SemanticVersion.parse('1.2.4')
|
62
|
+
v4 = SemanticVersion.parse('1.2.3-alpha')
|
63
|
+
v5 = SemanticVersion.parse('1.2.3+build.123')
|
64
|
+
|
65
|
+
assert_equal v1, v2
|
66
|
+
refute_equal v1, v3
|
67
|
+
refute_equal v1, v4
|
68
|
+
assert_equal v1, v5 # build metadata is ignored in equality
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_comparison
|
72
|
+
versions = [
|
73
|
+
'1.0.0-alpha',
|
74
|
+
'1.0.0-alpha.1',
|
75
|
+
'1.0.0-beta.2',
|
76
|
+
'1.0.0-beta.11',
|
77
|
+
'1.0.0-rc.1',
|
78
|
+
'1.0.0',
|
79
|
+
'2.0.0',
|
80
|
+
'2.1.0',
|
81
|
+
'2.1.1'
|
82
|
+
].map { |v| SemanticVersion.parse(v) }
|
83
|
+
|
84
|
+
# Test that each version is less than the next version
|
85
|
+
(versions.length - 1).times do |i|
|
86
|
+
assert versions[i] < versions[i + 1], "Expected #{versions[i]} < #{versions[i + 1]}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_prerelease_comparison
|
91
|
+
# Test specific prerelease comparison cases
|
92
|
+
cases = [
|
93
|
+
['1.0.0-alpha', '1.0.0-alpha.1', -1],
|
94
|
+
['1.0.0-alpha.1', '1.0.0-alpha.beta', -1],
|
95
|
+
['1.0.0-alpha.beta', '1.0.0-beta', -1],
|
96
|
+
['1.0.0-beta', '1.0.0-beta.2', -1],
|
97
|
+
['1.0.0-beta.2', '1.0.0-beta.11', -1],
|
98
|
+
['1.0.0-beta.11', '1.0.0-rc.1', -1],
|
99
|
+
['1.0.0-rc.1', '1.0.0', -1]
|
100
|
+
]
|
101
|
+
|
102
|
+
cases.each do |v1_str, v2_str, expected|
|
103
|
+
v1 = SemanticVersion.parse(v1_str)
|
104
|
+
v2 = SemanticVersion.parse(v2_str)
|
105
|
+
assert_equal expected, (v1 <=> v2), "Expected #{v1} <=> #{v2} to be #{expected}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|