prefab-cloud-ruby 1.8.8.pre.1 → 1.8.9

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.
@@ -11,7 +11,6 @@ module CommonHelpers
11
11
  Prefab::Context.default_context.clear
12
12
  SemanticLogger.add_appender(io: $logs, filter: Prefab.log_filter)
13
13
  SemanticLogger.sync!
14
- Timecop.freeze('2023-08-09 15:18:12 -0400')
15
14
  end
16
15
 
17
16
  def teardown
@@ -26,14 +25,19 @@ module CommonHelpers
26
25
  end
27
26
  end
28
27
 
29
- if $stderr != $oldstderr && !$stderr.string.empty?
28
+ #note this skips the output check in environments like rubymine that hijack the output. Alternative is a method missing error on string
29
+
30
+ if $stderr != $oldstderr && $stderr.respond_to?(:string) && !$stderr.string.empty?
30
31
  # we ignore 2.X because of the number of `instance variable @xyz not initialized` warnings
31
32
  if !RUBY_VERSION.start_with?('2.')
32
33
  raise "Unexpected stderr. Handle stderr with assert_stderr\n\n#{$stderr.string}"
33
34
  end
34
35
  end
35
36
 
36
- $stderr = $oldstderr
37
+ # Only restore stderr if we have a valid oldstderr
38
+ if $oldstderr
39
+ $stderr = $oldstderr
40
+ end
37
41
 
38
42
  Timecop.return
39
43
  end
@@ -174,18 +178,18 @@ module CommonHelpers
174
178
  end
175
179
 
176
180
  def assert_stderr(expected)
177
- assert ($stderr.string.split("\n").uniq & expected).size > 0
178
-
179
- # Ruby 2.X has a lot of warnings about instance variables not being
180
- # initialized so we don't try to assert on stderr for those versions.
181
- # Instead we just stop after asserting that our expected errors are
182
- # included in the output.
183
- if RUBY_VERSION.start_with?('2.')
184
- puts $stderr.string
185
- return
181
+ skip "Cannot verify stderr in current environment" unless $stderr.respond_to?(:string)
182
+ $stderr.string.split("\n").uniq.each do |line|
183
+ matched = false
184
+
185
+ expected.reject! do |expectation|
186
+ matched = true if line.include?(expectation)
187
+ end
188
+
189
+ assert(matched, "expectation: #{expected}, got: #{line}")
186
190
  end
187
191
 
188
- assert_equal expected.uniq, $stderr.string.split("\n").uniq
192
+ assert expected.empty?, "Expected stderr to include: #{expected}, but it did not"
189
193
 
190
194
  # restore since we've handled it
191
195
  $stderr = $oldstderr
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ module Prefab
6
+ class CachingHttpConnectionTest < Minitest::Test
7
+ def setup
8
+ @uri = 'https://api.example.com'
9
+ @api_key = 'test-key'
10
+ @path = '/some/path'
11
+
12
+ # Reset the cache before each test
13
+ CachingHttpConnection.reset_cache!
14
+
15
+ # Setup the mock HTTP connection
16
+ @http_connection = Minitest::Mock.new
17
+ @http_connection.expect(:uri, @uri)
18
+
19
+ # Stub the HttpConnection constructor
20
+ HttpConnection.stub :new, @http_connection do
21
+ @subject = CachingHttpConnection.new(@uri, @api_key)
22
+ end
23
+ end
24
+
25
+ def test_caches_responses_with_etag_and_max_age
26
+ response_body = 'response data'
27
+ response = Faraday::Response.new(
28
+ status: 200,
29
+ body: response_body,
30
+ response_headers: {
31
+ 'ETag' => 'abc123',
32
+ 'Cache-Control' => 'max-age=60'
33
+ }
34
+ )
35
+
36
+ # Expect two calls to uri (one for each request) and one call to get
37
+ @http_connection.expect(:uri, @uri)
38
+ @http_connection.expect(:get, response, [@path])
39
+
40
+ HttpConnection.stub :new, @http_connection do
41
+ # First request should miss cache
42
+ first_response = @subject.get(@path)
43
+ assert_equal response_body, first_response.body
44
+ assert_equal 'MISS', first_response.headers['X-Cache']
45
+
46
+ # Second request should hit cache
47
+ second_response = @subject.get(@path)
48
+ assert_equal response_body, second_response.body
49
+ assert_equal 'HIT', second_response.headers['X-Cache']
50
+ end
51
+
52
+ @http_connection.verify
53
+ end
54
+
55
+ def test_respects_max_age_directive
56
+ response = Faraday::Response.new(
57
+ status: 200,
58
+ body: 'fresh data',
59
+ response_headers: {
60
+ 'ETag' => 'abc123',
61
+ 'Cache-Control' => 'max-age=60'
62
+ }
63
+ )
64
+
65
+ mock = Minitest::Mock.new
66
+ def mock.uri
67
+ 'https://api.example.com'
68
+ end
69
+
70
+ # First request
71
+ mock.expect(:get, response, [@path])
72
+ # After max-age expires, new request with etag
73
+ mock.expect(:get, response, [@path, { 'If-None-Match' => 'abc123' }])
74
+
75
+ Timecop.freeze do
76
+ subject = CachingHttpConnection.new(@uri, @api_key)
77
+ subject.instance_variable_set('@connection', mock)
78
+
79
+ # Initial request
80
+ subject.get(@path)
81
+
82
+ # Within max-age window
83
+ Timecop.travel(59)
84
+ cached_response = subject.get(@path)
85
+ assert_equal 'HIT', cached_response.headers['X-Cache']
86
+
87
+ # After max-age window
88
+ Timecop.travel(61)
89
+ new_response = subject.get(@path)
90
+ assert_equal 'MISS', new_response.headers['X-Cache']
91
+ end
92
+
93
+ mock.verify
94
+ end
95
+ def test_handles_304_not_modified
96
+ initial_response = Faraday::Response.new(
97
+ status: 200,
98
+ body: 'cached data',
99
+ response_headers: { 'ETag' => 'abc123' }
100
+ )
101
+
102
+ not_modified_response = Faraday::Response.new(
103
+ status: 304,
104
+ body: '',
105
+ response_headers: { 'ETag' => 'abc123' }
106
+ )
107
+
108
+ mock = Minitest::Mock.new
109
+ def mock.uri
110
+ 'https://api.example.com'
111
+ end
112
+
113
+ # First request with single arg
114
+ mock.expect(:get, initial_response, [@path])
115
+
116
+ # Second request with both path and headers
117
+ mock.expect(:get, not_modified_response, [@path, { 'If-None-Match' => 'abc123' }])
118
+
119
+ subject = CachingHttpConnection.new(@uri, @api_key)
120
+ subject.instance_variable_set('@connection', mock)
121
+
122
+ # Initial request to populate cache
123
+ first_response = subject.get(@path)
124
+ assert_equal 'cached data', first_response.body
125
+ assert_equal 'MISS', first_response.headers['X-Cache']
126
+
127
+ # Subsequent request gets 304
128
+ cached_response = subject.get(@path)
129
+ assert_equal 'cached data', cached_response.body
130
+ assert_equal 200, cached_response.status
131
+ assert_equal 'HIT', cached_response.headers['X-Cache']
132
+
133
+ mock.verify
134
+ end
135
+
136
+ def test_does_not_cache_no_store_responses
137
+ response = Faraday::Response.new(
138
+ status: 200,
139
+ body: 'uncacheable data',
140
+ response_headers: { 'Cache-Control' => 'no-store' }
141
+ )
142
+
143
+ mock = Minitest::Mock.new
144
+ def mock.uri
145
+ 'https://api.example.com'
146
+ end
147
+ # Both gets with single arg
148
+ mock.expect(:get, response, [@path])
149
+ mock.expect(:get, response, [@path])
150
+
151
+ subject = CachingHttpConnection.new(@uri, @api_key)
152
+ subject.instance_variable_set('@connection', mock)
153
+
154
+ 2.times do
155
+ result = subject.get(@path)
156
+ assert_equal 'MISS', result.headers['X-Cache']
157
+ end
158
+
159
+ mock.verify
160
+ end
161
+ def test_cache_is_shared_across_instances
162
+ HttpConnection.stub :new, @http_connection do
163
+ instance1 = CachingHttpConnection.new(@uri, @api_key)
164
+ instance2 = CachingHttpConnection.new(@uri, @api_key)
165
+
166
+ assert_same instance1.class.cache, instance2.class.cache
167
+ end
168
+ end
169
+
170
+ def test_cache_can_be_reset
171
+ old_cache = CachingHttpConnection.cache
172
+ CachingHttpConnection.reset_cache!
173
+ refute_same CachingHttpConnection.cache, old_cache
174
+ end
175
+
176
+ def test_adds_if_none_match_header_when_cached
177
+ # First response to be cached
178
+ initial_response = Faraday::Response.new(
179
+ status: 200,
180
+ body: 'cached data',
181
+ response_headers: { 'ETag' => 'abc123' }
182
+ )
183
+
184
+ # Second request should have If-None-Match header
185
+ not_modified_response = Faraday::Response.new(
186
+ status: 304,
187
+ body: '',
188
+ response_headers: { 'ETag' => 'abc123' }
189
+ )
190
+
191
+ mock = Minitest::Mock.new
192
+ def mock.uri
193
+ 'https://api.example.com'
194
+ end
195
+
196
+ # First request should not have If-None-Match
197
+ mock.expect(:get, initial_response, [@path])
198
+
199
+ # Second request should have If-None-Match header
200
+ mock.expect(:get, not_modified_response, [@path, { 'If-None-Match' => 'abc123' }])
201
+
202
+ subject = CachingHttpConnection.new(@uri, @api_key)
203
+ subject.instance_variable_set('@connection', mock)
204
+
205
+ # Initial request to populate cache
206
+ first_response = subject.get(@path)
207
+ assert_equal 'cached data', first_response.body
208
+ assert_equal 'MISS', first_response.headers['X-Cache']
209
+
210
+ # Second request should use If-None-Match
211
+ cached_response = subject.get(@path)
212
+ assert_equal 'cached data', cached_response.body
213
+ assert_equal 'HIT', cached_response.headers['X-Cache']
214
+
215
+ mock.verify
216
+ end
217
+ end
218
+ end
data/test/test_context.rb CHANGED
@@ -141,14 +141,6 @@ class TestContext < Minitest::Test
141
141
  "id" => PrefabProto::ConfigValue.new(int: 2),
142
142
  "name" => PrefabProto::ConfigValue.new(string: "team-name")
143
143
  }
144
- ),
145
-
146
- PrefabProto::Context.new(
147
- type: "prefab",
148
- values: {
149
- 'current-time' => PrefabProto::ConfigValue.new(int: Prefab::TimeHelpers.now_in_ms),
150
- 'namespace' => PrefabProto::ConfigValue.new(string: namespace)
151
- }
152
144
  )
153
145
  ]
154
146
  ), contexts.to_proto(namespace)
@@ -226,13 +218,6 @@ class TestContext < Minitest::Test
226
218
  "name" => PrefabProto::ConfigValue.new(string: "team-name")
227
219
  }
228
220
  ),
229
- # via to_proto
230
- PrefabProto::Context.new(
231
- type: "prefab",
232
- values: {
233
- 'current-time' => PrefabProto::ConfigValue.new(int: Prefab::TimeHelpers.now_in_ms),
234
- }
235
- )
236
221
  ]
237
222
  )
238
223
 
@@ -36,7 +36,7 @@ class TestContextShape < Minitest::Test
36
36
 
37
37
  # If this test fails, it means that we've added a new type to the ConfigValue
38
38
  def test_mapping_is_exhaustive
39
- unsupported = [:bytes, :limit_definition, :log_level, :weighted_values, :int_range, :provided, :duration, :json]
39
+ unsupported = [:bytes, :limit_definition, :log_level, :weighted_values, :int_range, :provided, :duration, :json, :schema]
40
40
  type_fields = PrefabProto::ConfigValue.descriptor.lookup_oneof("type").entries
41
41
  supported = type_fields.entries.reject do |entry|
42
42
  unsupported.include?(entry.name.to_sym)
@@ -808,6 +808,288 @@ class TestCriteriaEvaluator < Minitest::Test
808
808
  assert_equal 'ghi', evaluator.evaluate(context).unwrapped_value
809
809
  end
810
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
+
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 = Prefab::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 = Prefab::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
+
811
1093
  private
812
1094
 
813
1095
  def string_list(values)
@@ -844,4 +1126,55 @@ class TestCriteriaEvaluator < Minitest::Test
844
1126
  'fake-base-client-instance_hash'
845
1127
  end
846
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
847
1180
  end