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.
- checksums.yaml +7 -0
- data/.envrc.sample +3 -0
- data/.github/CODEOWNERS +2 -0
- data/.github/pull_request_template.md +8 -0
- data/.github/workflows/ruby.yml +48 -0
- data/.gitmodules +3 -0
- data/.rubocop.yml +13 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +257 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +182 -0
- data/LICENSE.txt +20 -0
- data/README.md +105 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/compile_protos.sh +20 -0
- data/dev/allocation_stats +60 -0
- data/dev/benchmark +40 -0
- data/dev/console +12 -0
- data/dev/script_setup.rb +18 -0
- data/lib/prefab_pb.rb +77 -0
- data/lib/reforge/caching_http_connection.rb +95 -0
- data/lib/reforge/client.rb +133 -0
- data/lib/reforge/config_client.rb +275 -0
- data/lib/reforge/config_client_presenter.rb +18 -0
- data/lib/reforge/config_loader.rb +67 -0
- data/lib/reforge/config_resolver.rb +84 -0
- data/lib/reforge/config_value_unwrapper.rb +123 -0
- data/lib/reforge/config_value_wrapper.rb +18 -0
- data/lib/reforge/context.rb +241 -0
- data/lib/reforge/context_shape.rb +20 -0
- data/lib/reforge/context_shape_aggregator.rb +70 -0
- data/lib/reforge/criteria_evaluator.rb +345 -0
- data/lib/reforge/duration.rb +58 -0
- data/lib/reforge/encryption.rb +65 -0
- data/lib/reforge/error.rb +6 -0
- data/lib/reforge/errors/env_var_parse_error.rb +11 -0
- data/lib/reforge/errors/initialization_timeout_error.rb +12 -0
- data/lib/reforge/errors/invalid_sdk_key_error.rb +19 -0
- data/lib/reforge/errors/missing_default_error.rb +13 -0
- data/lib/reforge/errors/missing_env_var_error.rb +11 -0
- data/lib/reforge/errors/uninitialized_error.rb +13 -0
- data/lib/reforge/evaluation.rb +53 -0
- data/lib/reforge/evaluation_summary_aggregator.rb +86 -0
- data/lib/reforge/example_contexts_aggregator.rb +77 -0
- data/lib/reforge/exponential_backoff.rb +21 -0
- data/lib/reforge/feature_flag_client.rb +43 -0
- data/lib/reforge/fixed_size_hash.rb +14 -0
- data/lib/reforge/http_connection.rb +45 -0
- data/lib/reforge/internal_logger.rb +43 -0
- data/lib/reforge/javascript_stub.rb +99 -0
- data/lib/reforge/local_config_parser.rb +151 -0
- data/lib/reforge/murmer3.rb +50 -0
- data/lib/reforge/options.rb +191 -0
- data/lib/reforge/periodic_sync.rb +74 -0
- data/lib/reforge/prefab.rb +120 -0
- data/lib/reforge/rate_limit_cache.rb +41 -0
- data/lib/reforge/resolved_config_presenter.rb +86 -0
- data/lib/reforge/semver.rb +132 -0
- data/lib/reforge/sse_config_client.rb +112 -0
- data/lib/reforge/time_helpers.rb +7 -0
- data/lib/reforge/weighted_value_resolver.rb +42 -0
- data/lib/reforge/yaml_config_parser.rb +34 -0
- data/lib/reforge-sdk.rb +57 -0
- data/test/fixtures/datafile.json +87 -0
- data/test/integration_test.rb +171 -0
- data/test/integration_test_helpers.rb +114 -0
- data/test/support/common_helpers.rb +201 -0
- data/test/support/mock_base_client.rb +41 -0
- data/test/support/mock_config_client.rb +19 -0
- data/test/support/mock_config_loader.rb +1 -0
- data/test/test_caching_http_connection.rb +218 -0
- data/test/test_client.rb +351 -0
- data/test/test_config_client.rb +84 -0
- data/test/test_config_loader.rb +82 -0
- data/test/test_config_resolver.rb +502 -0
- data/test/test_config_value_unwrapper.rb +270 -0
- data/test/test_config_value_wrapper.rb +42 -0
- data/test/test_context.rb +271 -0
- data/test/test_context_shape.rb +50 -0
- data/test/test_context_shape_aggregator.rb +150 -0
- data/test/test_criteria_evaluator.rb +1180 -0
- data/test/test_duration.rb +37 -0
- data/test/test_encryption.rb +16 -0
- data/test/test_evaluation_summary_aggregator.rb +162 -0
- data/test/test_example_contexts_aggregator.rb +233 -0
- data/test/test_exponential_backoff.rb +18 -0
- data/test/test_feature_flag_client.rb +16 -0
- data/test/test_fixed_size_hash.rb +119 -0
- data/test/test_helper.rb +17 -0
- data/test/test_integration.rb +75 -0
- data/test/test_internal_logger.rb +25 -0
- data/test/test_javascript_stub.rb +176 -0
- data/test/test_local_config_parser.rb +147 -0
- data/test/test_logger_initialization.rb +12 -0
- data/test/test_options.rb +93 -0
- data/test/test_prefab.rb +16 -0
- data/test/test_rate_limit_cache.rb +44 -0
- data/test/test_semver.rb +108 -0
- data/test/test_sse_config_client.rb +211 -0
- data/test/test_weighted_value_resolver.rb +71 -0
- metadata +345 -0
@@ -0,0 +1,270 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestConfigValueUnwrapper < Minitest::Test
|
6
|
+
CONFIG = PrefabProto::Config.new(
|
7
|
+
key: 'config_key'
|
8
|
+
)
|
9
|
+
EMPTY_CONTEXT = Reforge::Context.new()
|
10
|
+
DECRYPTION_KEY_NAME = "decryption.key"
|
11
|
+
DECRYPTION_KEY_VALUE = Reforge::Encryption.generate_new_hex_key
|
12
|
+
|
13
|
+
def setup
|
14
|
+
super
|
15
|
+
@mock_resolver = MockResolver.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_unwrapping_int
|
19
|
+
config_value = PrefabProto::ConfigValue.new(int: 123)
|
20
|
+
assert_equal 123, unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_unwrapping_string
|
24
|
+
config_value = PrefabProto::ConfigValue.new(string: 'abc')
|
25
|
+
assert_equal 'abc', unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
26
|
+
assert_equal 'abc', reportable_value(config_value, CONFIG, EMPTY_CONTEXT)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_unwrapping_double
|
30
|
+
config_value = PrefabProto::ConfigValue.new(double: 1.23)
|
31
|
+
assert_equal 1.23, unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_unwrapping_bool
|
35
|
+
config_value = PrefabProto::ConfigValue.new(bool: true)
|
36
|
+
assert_equal true, unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
37
|
+
|
38
|
+
config_value = PrefabProto::ConfigValue.new(bool: false)
|
39
|
+
assert_equal false, unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_unwrapping_log_level
|
43
|
+
config_value = PrefabProto::ConfigValue.new(log_level: :INFO)
|
44
|
+
assert_equal :INFO, unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_unwrapping_string_list
|
48
|
+
config_value = PrefabProto::ConfigValue.new(string_list: PrefabProto::StringList.new(values: %w[a b c]))
|
49
|
+
assert_equal %w[a b c], unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_unwrapping_duration
|
53
|
+
duration = PrefabProto::IsoDuration.new(definition: "PT1.5S")
|
54
|
+
config_value = PrefabProto::ConfigValue.new(duration: duration)
|
55
|
+
assert_equal 1.5, unwrap(config_value, CONFIG, EMPTY_CONTEXT).in_seconds
|
56
|
+
assert_equal 0.025, unwrap(config_value, CONFIG, EMPTY_CONTEXT).in_minutes
|
57
|
+
assert_in_delta 0.00041667, unwrap(config_value, CONFIG, EMPTY_CONTEXT).in_hours, 0.00001
|
58
|
+
|
59
|
+
duration = PrefabProto::IsoDuration.new(definition: "P4DT12H30M5S")
|
60
|
+
config_value = PrefabProto::ConfigValue.new(duration: duration)
|
61
|
+
assert_in_delta 0.6458, unwrap(config_value, CONFIG, EMPTY_CONTEXT).in_weeks, 0.0001
|
62
|
+
assert_in_delta 4.521, unwrap(config_value, CONFIG, EMPTY_CONTEXT).in_days, 0.001
|
63
|
+
assert_in_delta 108.5, unwrap(config_value, CONFIG, EMPTY_CONTEXT).in_hours, 0.1
|
64
|
+
assert_in_delta 6510.083, unwrap(config_value, CONFIG, EMPTY_CONTEXT).in_minutes, 0.001
|
65
|
+
assert_equal 390605 ,unwrap(config_value, CONFIG, EMPTY_CONTEXT).in_seconds
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_unwrapping_json
|
69
|
+
json = PrefabProto::Json.new(json: '{"a": 1, "b": "c"}')
|
70
|
+
config_value = PrefabProto::ConfigValue.new(json: json)
|
71
|
+
assert_equal({"a" => 1, "b" => "c"}, unwrap(config_value, CONFIG, EMPTY_CONTEXT))
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_unwrapping_json_with_symbolize_json_names_true
|
75
|
+
@mock_resolver = MockResolver.new(symbolize_json_names: true)
|
76
|
+
json = PrefabProto::Json.new(json: '{"a": 1, "b": "c"}')
|
77
|
+
config_value = PrefabProto::ConfigValue.new(json: json)
|
78
|
+
assert_equal({:a => 1, :b => "c"}, unwrap(config_value, CONFIG, EMPTY_CONTEXT))
|
79
|
+
|
80
|
+
json = PrefabProto::Json.new(json: '{"foo": { "bar": "baz"}, "cow": "moo"}')
|
81
|
+
config_value = PrefabProto::ConfigValue.new(json: json)
|
82
|
+
assert_equal({:foo=>{:bar=>"baz"}, :cow=>"moo"}, unwrap(config_value, CONFIG, EMPTY_CONTEXT))
|
83
|
+
|
84
|
+
json = PrefabProto::Json.new(json: '[{"foo": { "bar": "baz"}}, {"cow": "moo"}]')
|
85
|
+
config_value = PrefabProto::ConfigValue.new(json: json)
|
86
|
+
assert_equal([{:foo=>{:bar=>"baz"}}, {:cow=>"moo"}], unwrap(config_value, CONFIG, EMPTY_CONTEXT))
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_unwrapping_weighted_values
|
90
|
+
# single value
|
91
|
+
config_value = PrefabProto::ConfigValue.new(weighted_values: weighted_values([['abc', 1]]))
|
92
|
+
|
93
|
+
assert_equal 'abc', unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
94
|
+
|
95
|
+
# multiple values, evenly distributed
|
96
|
+
config_value = PrefabProto::ConfigValue.new(weighted_values: weighted_values([['abc', 1], ['def', 1], ['ghi', 1]]))
|
97
|
+
assert_equal 'def', unwrap(config_value, CONFIG, context_with_key('user:000'))
|
98
|
+
assert_equal 'ghi', unwrap(config_value, CONFIG, context_with_key('user:456'))
|
99
|
+
assert_equal 'abc', unwrap(config_value, CONFIG, context_with_key('user:789'))
|
100
|
+
assert_equal 'ghi', unwrap(config_value, CONFIG, context_with_key('user:888'))
|
101
|
+
|
102
|
+
# multiple values, unevenly distributed
|
103
|
+
config_value = PrefabProto::ConfigValue.new(weighted_values: weighted_values([['abc', 1], ['def', 99], ['ghi', 1]]))
|
104
|
+
assert_equal 'def', unwrap(config_value, CONFIG, context_with_key('user:123'))
|
105
|
+
assert_equal 'def', unwrap(config_value, CONFIG, context_with_key('user:456'))
|
106
|
+
assert_equal 'def', unwrap(config_value, CONFIG, context_with_key('user:789'))
|
107
|
+
assert_equal 'def', unwrap(config_value, CONFIG, context_with_key('user:012'))
|
108
|
+
assert_equal 'ghi', unwrap(config_value, CONFIG, context_with_key('user:428'))
|
109
|
+
assert_equal 'abc', unwrap(config_value, CONFIG, context_with_key('user:548'))
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_unwrapping_provided_values
|
113
|
+
with_env('ENV_VAR_NAME', 'unit test value')do
|
114
|
+
value = PrefabProto::Provided.new(
|
115
|
+
source: :ENV_VAR,
|
116
|
+
lookup: "ENV_VAR_NAME"
|
117
|
+
)
|
118
|
+
config_value = PrefabProto::ConfigValue.new(provided: value)
|
119
|
+
assert_equal 'unit test value', unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_unwrapping_provided_values_of_type_string_list
|
124
|
+
with_env('ENV_VAR_NAME', '["bob","cary"]')do
|
125
|
+
value = PrefabProto::Provided.new(
|
126
|
+
source: :ENV_VAR,
|
127
|
+
lookup: "ENV_VAR_NAME"
|
128
|
+
)
|
129
|
+
config_value = PrefabProto::ConfigValue.new(provided: value)
|
130
|
+
assert_equal ["bob", "cary"], unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_unwrapping_provided_values_coerces_to_int
|
135
|
+
with_env('ENV_VAR_NAME', '42')do
|
136
|
+
value = PrefabProto::Provided.new(
|
137
|
+
source: :ENV_VAR,
|
138
|
+
lookup: "ENV_VAR_NAME"
|
139
|
+
)
|
140
|
+
config_value = PrefabProto::ConfigValue.new(provided: value)
|
141
|
+
assert_equal 42, unwrap(config_value, config_of(PrefabProto::Config::ValueType::INT), EMPTY_CONTEXT)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_unwrapping_provided_values_when_value_type_mismatch
|
146
|
+
with_env('ENV_VAR_NAME', 'not an int')do
|
147
|
+
value = PrefabProto::Provided.new(
|
148
|
+
source: :ENV_VAR,
|
149
|
+
lookup: "ENV_VAR_NAME"
|
150
|
+
)
|
151
|
+
config_value = PrefabProto::ConfigValue.new(provided: value)
|
152
|
+
|
153
|
+
assert_raises Reforge::Errors::EnvVarParseError do
|
154
|
+
unwrap(config_value, config_of(PrefabProto::Config::ValueType::INT), EMPTY_CONTEXT)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_coerce
|
160
|
+
assert_equal "string", Reforge::ConfigValueUnwrapper.coerce_into_type("string", CONFIG, "ENV")
|
161
|
+
assert_equal 42, Reforge::ConfigValueUnwrapper.coerce_into_type("42", CONFIG, "ENV")
|
162
|
+
assert_equal false, Reforge::ConfigValueUnwrapper.coerce_into_type("false", CONFIG, "ENV")
|
163
|
+
assert_equal 42.42, Reforge::ConfigValueUnwrapper.coerce_into_type("42.42", CONFIG, "ENV")
|
164
|
+
assert_equal ["a","b"], Reforge::ConfigValueUnwrapper.coerce_into_type("['a','b']", CONFIG, "ENV")
|
165
|
+
|
166
|
+
assert_equal "string", Reforge::ConfigValueUnwrapper.coerce_into_type("string", config_of(PrefabProto::Config::ValueType::STRING),"ENV")
|
167
|
+
assert_equal "42", Reforge::ConfigValueUnwrapper.coerce_into_type("42", config_of(PrefabProto::Config::ValueType::STRING),"ENV")
|
168
|
+
assert_equal "42.42", Reforge::ConfigValueUnwrapper.coerce_into_type("42.42", config_of(PrefabProto::Config::ValueType::STRING),"ENV")
|
169
|
+
assert_equal 42, Reforge::ConfigValueUnwrapper.coerce_into_type("42", config_of(PrefabProto::Config::ValueType::INT),"ENV")
|
170
|
+
assert_equal false, Reforge::ConfigValueUnwrapper.coerce_into_type("false", config_of(PrefabProto::Config::ValueType::BOOL),"ENV")
|
171
|
+
assert_equal 42.42, Reforge::ConfigValueUnwrapper.coerce_into_type("42.42", config_of(PrefabProto::Config::ValueType::DOUBLE),"ENV")
|
172
|
+
assert_equal ["a","b"], Reforge::ConfigValueUnwrapper.coerce_into_type("['a','b']", config_of(PrefabProto::Config::ValueType::STRING_LIST),"ENV")
|
173
|
+
|
174
|
+
assert_raises Reforge::Errors::EnvVarParseError do
|
175
|
+
Reforge::ConfigValueUnwrapper.coerce_into_type("not an int", config_of(PrefabProto::Config::ValueType::INT), "ENV")
|
176
|
+
end
|
177
|
+
assert_raises Reforge::Errors::EnvVarParseError do
|
178
|
+
Reforge::ConfigValueUnwrapper.coerce_into_type("not bool", config_of(PrefabProto::Config::ValueType::BOOL), "ENV")
|
179
|
+
end
|
180
|
+
assert_raises Reforge::Errors::EnvVarParseError do
|
181
|
+
Reforge::ConfigValueUnwrapper.coerce_into_type("not a double", config_of(PrefabProto::Config::ValueType::DOUBLE), "ENV")
|
182
|
+
end
|
183
|
+
assert_raises Reforge::Errors::EnvVarParseError do
|
184
|
+
Reforge::ConfigValueUnwrapper.coerce_into_type("not a list", config_of(PrefabProto::Config::ValueType::STRING_LIST), "ENV")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_unwrapping_provided_values_with_missing_env_var
|
189
|
+
value = PrefabProto::Provided.new(
|
190
|
+
source: :ENV_VAR,
|
191
|
+
lookup: "NON_EXISTENT_ENV_VAR_NAME"
|
192
|
+
)
|
193
|
+
config_value = PrefabProto::ConfigValue.new(provided: value)
|
194
|
+
assert_raises(Reforge::Errors::MissingEnvVarError) do
|
195
|
+
unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_unwrapping_encrypted_values_decrypts
|
200
|
+
clear_text = "very secret stuff"
|
201
|
+
encrypted = Reforge::Encryption.new(DECRYPTION_KEY_VALUE).encrypt(clear_text)
|
202
|
+
config_value = PrefabProto::ConfigValue.new(string: encrypted, decrypt_with: "decryption.key")
|
203
|
+
assert_equal clear_text, unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
204
|
+
assert reportable_value(config_value, CONFIG, EMPTY_CONTEXT).start_with? Reforge::ConfigValueUnwrapper::CONFIDENTIAL_PREFIX
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_confidential
|
208
|
+
config_value = PrefabProto::ConfigValue.new(confidential: true, string: "something confidential")
|
209
|
+
assert reportable_value(config_value, CONFIG, EMPTY_CONTEXT).start_with? Reforge::ConfigValueUnwrapper::CONFIDENTIAL_PREFIX
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_unwrap_confiential_provided
|
213
|
+
with_env('PAAS_PASSWORD', "the password")do
|
214
|
+
value = PrefabProto::Provided.new(
|
215
|
+
source: :ENV_VAR,
|
216
|
+
lookup: "PAAS_PASSWORD"
|
217
|
+
)
|
218
|
+
config_value = PrefabProto::ConfigValue.new(provided: value, confidential: true)
|
219
|
+
assert_equal "the password", unwrap(config_value, CONFIG, EMPTY_CONTEXT)
|
220
|
+
assert reportable_value(config_value, CONFIG, EMPTY_CONTEXT).start_with? Reforge::ConfigValueUnwrapper::CONFIDENTIAL_PREFIX
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
def config_of(value_type)
|
227
|
+
PrefabProto::Config.new(
|
228
|
+
key: 'config-key',
|
229
|
+
value_type: value_type
|
230
|
+
)
|
231
|
+
end
|
232
|
+
|
233
|
+
def context_with_key(key)
|
234
|
+
Reforge::Context.new(user: { key: key })
|
235
|
+
end
|
236
|
+
|
237
|
+
def unwrap(config_value, config_key, context)
|
238
|
+
Reforge::ConfigValueUnwrapper.deepest_value(config_value, config_key, context, @mock_resolver).unwrap
|
239
|
+
end
|
240
|
+
|
241
|
+
def reportable_value(config_value, config_key, context)
|
242
|
+
Reforge::ConfigValueUnwrapper.deepest_value(config_value, config_key, context, @mock_resolver).reportable_value
|
243
|
+
end
|
244
|
+
|
245
|
+
class MockResolver
|
246
|
+
|
247
|
+
def initialize(symbolize_json_names: false)
|
248
|
+
@symbolize_json_names = symbolize_json_names
|
249
|
+
end
|
250
|
+
|
251
|
+
def symbolize_json_names?
|
252
|
+
@symbolize_json_names
|
253
|
+
end
|
254
|
+
|
255
|
+
def get(key)
|
256
|
+
if DECRYPTION_KEY_NAME == key
|
257
|
+
Reforge::Evaluation.new(config: PrefabProto::Config.new(key: key),
|
258
|
+
value: PrefabProto::ConfigValue.new(string: DECRYPTION_KEY_VALUE),
|
259
|
+
value_index: 0,
|
260
|
+
config_row_index: 0,
|
261
|
+
context: Reforge::Context.new,
|
262
|
+
resolver: self
|
263
|
+
)
|
264
|
+
|
265
|
+
else
|
266
|
+
raise "unexpected key"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestConfigValueWrapper < Minitest::Test
|
6
|
+
def test_wrap_integer
|
7
|
+
result = Reforge::ConfigValueWrapper.wrap(42)
|
8
|
+
assert_instance_of PrefabProto::ConfigValue, result
|
9
|
+
assert_equal 42, result.int
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_wrap_float
|
13
|
+
result = Reforge::ConfigValueWrapper.wrap(3.14)
|
14
|
+
assert_instance_of PrefabProto::ConfigValue, result
|
15
|
+
assert_equal 3.14, result.double
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_wrap_boolean_true
|
19
|
+
result = Reforge::ConfigValueWrapper.wrap(true)
|
20
|
+
assert_instance_of PrefabProto::ConfigValue, result
|
21
|
+
assert_equal true, result.bool
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_wrap_boolean_false
|
25
|
+
result = Reforge::ConfigValueWrapper.wrap(false)
|
26
|
+
assert_instance_of PrefabProto::ConfigValue, result
|
27
|
+
assert_equal false, result.bool
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_wrap_array
|
31
|
+
result = Reforge::ConfigValueWrapper.wrap(['one', 'two', 'three'])
|
32
|
+
assert_instance_of PrefabProto::ConfigValue, result
|
33
|
+
assert_instance_of PrefabProto::StringList, result.string_list
|
34
|
+
assert_equal ['one', 'two', 'three'], result.string_list.values
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_wrap_string
|
38
|
+
result = Reforge::ConfigValueWrapper.wrap('hello')
|
39
|
+
assert_instance_of PrefabProto::ConfigValue, result
|
40
|
+
assert_equal 'hello', result.string
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestContext < Minitest::Test
|
6
|
+
EXAMPLE_PROPERTIES = { user: { key: 'some-user-key', name: 'Ted' }, team: { key: 'abc', plan: 'pro' } }.freeze
|
7
|
+
|
8
|
+
def setup
|
9
|
+
super
|
10
|
+
Reforge::Context.current = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_initialize_with_empty_context
|
14
|
+
context = Reforge::Context.new({})
|
15
|
+
assert_empty context.contexts
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_initialize_with_hash
|
19
|
+
context = Reforge::Context.new(test: { foo: 'bar' })
|
20
|
+
assert_equal 1, context.contexts.size
|
21
|
+
assert_equal 'bar', context.get("test.foo")
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_initialize_with_multiple_hashes
|
25
|
+
context = Reforge::Context.new(test: { foo: 'bar' }, other: { foo: 'baz' })
|
26
|
+
assert_equal 2, context.contexts.size
|
27
|
+
assert_equal 'bar', context.get("test.foo")
|
28
|
+
assert_equal 'baz', context.get("other.foo")
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_initialize_with_invalid_argument
|
32
|
+
assert_raises(ArgumentError) { Reforge::Context.new([]) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_current
|
36
|
+
context = Reforge::Context.current
|
37
|
+
assert_instance_of Reforge::Context, context
|
38
|
+
assert_empty context.to_h
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_current_set
|
42
|
+
context = Reforge::Context.new(EXAMPLE_PROPERTIES)
|
43
|
+
Reforge::Context.current = context.to_h
|
44
|
+
assert_instance_of Reforge::Context, context
|
45
|
+
assert_equal stringify(EXAMPLE_PROPERTIES), context.to_h
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_with_context
|
49
|
+
Reforge::Context.with_context(EXAMPLE_PROPERTIES) do
|
50
|
+
context = Reforge::Context.current
|
51
|
+
assert_equal(stringify(EXAMPLE_PROPERTIES), context.to_h)
|
52
|
+
assert_equal('some-user-key', context.get('user.key'))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_with_context_nesting
|
57
|
+
Reforge::Context.with_context(EXAMPLE_PROPERTIES) do
|
58
|
+
Reforge::Context.with_context({ user: { key: 'abc', other: 'different' } }) do
|
59
|
+
context = Reforge::Context.current
|
60
|
+
assert_equal({ 'user' => { 'key' => 'abc', 'other' => 'different' } }, context.to_h)
|
61
|
+
end
|
62
|
+
|
63
|
+
context = Reforge::Context.current
|
64
|
+
assert_equal(stringify(EXAMPLE_PROPERTIES), context.to_h)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_with_context_merge_nesting
|
69
|
+
Reforge::Context.with_context(EXAMPLE_PROPERTIES) do
|
70
|
+
Reforge::Context.with_merged_context({ user: { key: 'hij', other: 'different' } }) do
|
71
|
+
context = Reforge::Context.current
|
72
|
+
assert_nil context.get('user.name')
|
73
|
+
assert_equal context.get('user.key'), 'hij'
|
74
|
+
assert_equal context.get('user.other'), 'different'
|
75
|
+
|
76
|
+
assert_equal context.get('team.key'), 'abc'
|
77
|
+
assert_equal context.get('team.plan'), 'pro'
|
78
|
+
end
|
79
|
+
|
80
|
+
context = Reforge::Context.current
|
81
|
+
assert_equal(stringify(EXAMPLE_PROPERTIES), context.to_h)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_setting
|
86
|
+
context = Reforge::Context.new({})
|
87
|
+
context.set('user', { key: 'value' })
|
88
|
+
context.set(:other, { key: 'different', something: 'other' })
|
89
|
+
assert_equal(stringify({ user: { key: 'value' }, other: { key: 'different', something: 'other' } }), context.to_h)
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_getting
|
93
|
+
context = Reforge::Context.new(EXAMPLE_PROPERTIES)
|
94
|
+
assert_equal('some-user-key', context.get('user.key'))
|
95
|
+
assert_equal('pro', context.get('team.plan'))
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_dot_notation_getting
|
99
|
+
context = Reforge::Context.new({ 'user' => { 'key' => 'value' } })
|
100
|
+
assert_equal('value', context.get('user.key'))
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_dot_notation_getting_with_symbols
|
104
|
+
context = Reforge::Context.new({ user: { key: 'value' } })
|
105
|
+
assert_equal('value', context.get('user.key'))
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_clear
|
109
|
+
context = Reforge::Context.new(EXAMPLE_PROPERTIES)
|
110
|
+
context.clear
|
111
|
+
|
112
|
+
assert_empty context.to_h
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_to_proto
|
116
|
+
namespace = "my.namespace"
|
117
|
+
|
118
|
+
contexts = Reforge::Context.new({
|
119
|
+
user: {
|
120
|
+
id: 1,
|
121
|
+
email: 'user-email'
|
122
|
+
},
|
123
|
+
team: {
|
124
|
+
id: 2,
|
125
|
+
name: 'team-name'
|
126
|
+
}
|
127
|
+
})
|
128
|
+
|
129
|
+
assert_equal PrefabProto::ContextSet.new(
|
130
|
+
contexts: [
|
131
|
+
PrefabProto::Context.new(
|
132
|
+
type: "user",
|
133
|
+
values: {
|
134
|
+
"id" => PrefabProto::ConfigValue.new(int: 1),
|
135
|
+
"email" => PrefabProto::ConfigValue.new(string: "user-email")
|
136
|
+
}
|
137
|
+
),
|
138
|
+
PrefabProto::Context.new(
|
139
|
+
type: "team",
|
140
|
+
values: {
|
141
|
+
"id" => PrefabProto::ConfigValue.new(int: 2),
|
142
|
+
"name" => PrefabProto::ConfigValue.new(string: "team-name")
|
143
|
+
}
|
144
|
+
)
|
145
|
+
]
|
146
|
+
), contexts.to_proto(namespace)
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_to_proto_with_parent
|
150
|
+
global_context = { cpu: { count: 4, speed: '2.4GHz' }, clock: { timezone: 'UTC' }, magic: { key: "global-key" } }
|
151
|
+
default_context = { 'prefab-api-key' => { 'user-id' => 123 } }
|
152
|
+
|
153
|
+
Reforge::Context.global_context = global_context
|
154
|
+
Reforge::Context.default_context = default_context
|
155
|
+
|
156
|
+
Reforge::Context.current = {
|
157
|
+
user: { id: 2, email: 'parent-email' },
|
158
|
+
magic: { key: 'parent-key', rabbits: 3 },
|
159
|
+
clock: { timezone: 'PST' }
|
160
|
+
}
|
161
|
+
|
162
|
+
contexts = Reforge::Context.join(hash: {
|
163
|
+
user: {
|
164
|
+
id: 1,
|
165
|
+
email: 'user-email'
|
166
|
+
},
|
167
|
+
team: {
|
168
|
+
id: 2,
|
169
|
+
name: 'team-name'
|
170
|
+
}
|
171
|
+
}, id: :jit, parent: Reforge::Context.current)
|
172
|
+
|
173
|
+
expected = PrefabProto::ContextSet.new(
|
174
|
+
contexts: [
|
175
|
+
# Via global
|
176
|
+
PrefabProto::Context.new(
|
177
|
+
type: "cpu",
|
178
|
+
values: {
|
179
|
+
"count" => PrefabProto::ConfigValue.new(int: 4),
|
180
|
+
"speed" => PrefabProto::ConfigValue.new(string: "2.4GHz")
|
181
|
+
}
|
182
|
+
),
|
183
|
+
# Via default
|
184
|
+
PrefabProto::Context.new(
|
185
|
+
type: "clock",
|
186
|
+
values: {
|
187
|
+
"timezone" => PrefabProto::ConfigValue.new(string: 'PST'),
|
188
|
+
}
|
189
|
+
),
|
190
|
+
# via current
|
191
|
+
PrefabProto::Context.new(
|
192
|
+
type: "magic",
|
193
|
+
values: {
|
194
|
+
"key" => PrefabProto::ConfigValue.new(string: 'parent-key'),
|
195
|
+
"rabbits" => PrefabProto::ConfigValue.new(int: 3)
|
196
|
+
}
|
197
|
+
),
|
198
|
+
# via default
|
199
|
+
PrefabProto::Context.new(
|
200
|
+
type: "prefab-api-key",
|
201
|
+
values: {
|
202
|
+
"user-id" => PrefabProto::ConfigValue.new(int: 123)
|
203
|
+
}
|
204
|
+
),
|
205
|
+
# via jit
|
206
|
+
PrefabProto::Context.new(
|
207
|
+
type: "user",
|
208
|
+
values: {
|
209
|
+
"id" => PrefabProto::ConfigValue.new(int: 1),
|
210
|
+
"email" => PrefabProto::ConfigValue.new(string: "user-email")
|
211
|
+
}
|
212
|
+
),
|
213
|
+
# via jit
|
214
|
+
PrefabProto::Context.new(
|
215
|
+
type: "team",
|
216
|
+
values: {
|
217
|
+
"id" => PrefabProto::ConfigValue.new(int: 2),
|
218
|
+
"name" => PrefabProto::ConfigValue.new(string: "team-name")
|
219
|
+
}
|
220
|
+
),
|
221
|
+
]
|
222
|
+
)
|
223
|
+
|
224
|
+
actual = contexts.to_proto("")
|
225
|
+
|
226
|
+
assert_equal expected, actual
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_parent_lookup
|
230
|
+
global_context = { cpu: { count: 4, speed: '2.4GHz' }, clock: { timezone: 'UTC' } }
|
231
|
+
default_context = { 'prefab-api-key' => { 'user-id' => 123 } }
|
232
|
+
local_context = { clock: { timezone: 'PST' }, user: { name: 'Ted', email: 'ted@example.com' } }
|
233
|
+
jit_context = { user: { name: 'Frank' } }
|
234
|
+
|
235
|
+
Reforge::Context.global_context = global_context
|
236
|
+
Reforge::Context.default_context = default_context
|
237
|
+
Reforge::Context.current = local_context
|
238
|
+
|
239
|
+
context = Reforge::Context.join(parent: Reforge::Context.current, hash: jit_context, id: :jit)
|
240
|
+
|
241
|
+
# This digs all the way to the global context
|
242
|
+
assert_equal 4, context.get('cpu.count')
|
243
|
+
assert_equal '2.4GHz', context.get('cpu.speed')
|
244
|
+
|
245
|
+
# This digs to the default context
|
246
|
+
assert_equal 123, context.get('prefab-api-key.user-id')
|
247
|
+
|
248
|
+
# This digs to the local context
|
249
|
+
assert_equal 'PST', context.get('clock.timezone')
|
250
|
+
|
251
|
+
# This uses the jit context
|
252
|
+
assert_equal 'Frank', context.get('user.name')
|
253
|
+
|
254
|
+
# This is nil in the jit context because `user` was clobbered
|
255
|
+
assert_nil context.get('user.email')
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
def stringify(hash)
|
261
|
+
hash.map { |k, v| [k.to_s, stringify_keys(v)] }.to_h
|
262
|
+
end
|
263
|
+
|
264
|
+
def stringify_keys(value)
|
265
|
+
if value.is_a?(Hash)
|
266
|
+
value.transform_keys(&:to_s)
|
267
|
+
else
|
268
|
+
value
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestContextShape < Minitest::Test
|
6
|
+
class Email; end
|
7
|
+
|
8
|
+
def test_field_type_number
|
9
|
+
[
|
10
|
+
[1, 1],
|
11
|
+
[99999999999999999999999999999999999999999999, 1],
|
12
|
+
[-99999999999999999999999999999999999999999999, 1],
|
13
|
+
|
14
|
+
['a', 2],
|
15
|
+
['99999999999999999999999999999999999999999999', 2],
|
16
|
+
|
17
|
+
[1.0, 4],
|
18
|
+
[99999999999999999999999999999999999999999999.0, 4],
|
19
|
+
[-99999999999999999999999999999999999999999999.0, 4],
|
20
|
+
|
21
|
+
[true, 5],
|
22
|
+
[false, 5],
|
23
|
+
|
24
|
+
[[], 10],
|
25
|
+
[[1, 2, 3], 10],
|
26
|
+
[['a', 'b', 'c'], 10],
|
27
|
+
|
28
|
+
[Email.new, 2],
|
29
|
+
].each do |value, expected|
|
30
|
+
actual = Reforge::ContextShape.field_type_number(value)
|
31
|
+
|
32
|
+
refute_nil actual, "Expected a value for input: #{value}"
|
33
|
+
assert_equal expected, actual, "Expected #{expected} for #{value}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# If this test fails, it means that we've added a new type to the ConfigValue
|
38
|
+
def test_mapping_is_exhaustive
|
39
|
+
unsupported = [:bytes, :limit_definition, :log_level, :weighted_values, :int_range, :provided, :duration, :json, :schema]
|
40
|
+
type_fields = PrefabProto::ConfigValue.descriptor.lookup_oneof("type").entries
|
41
|
+
supported = type_fields.entries.reject do |entry|
|
42
|
+
unsupported.include?(entry.name.to_sym)
|
43
|
+
end.map(&:number)
|
44
|
+
mapped = Reforge::ContextShape::MAPPING.values.uniq
|
45
|
+
|
46
|
+
unless mapped == supported
|
47
|
+
raise "ContextShape MAPPING needs update: #{mapped} != #{supported}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|