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,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestInternalLogger < Minitest::Test
|
6
|
+
|
7
|
+
def test_levels
|
8
|
+
logger_a = Reforge::InternalLogger.new(A)
|
9
|
+
logger_b = Reforge::InternalLogger.new(B)
|
10
|
+
|
11
|
+
assert_equal :warn, logger_a.level
|
12
|
+
assert_equal :warn, logger_b.level
|
13
|
+
|
14
|
+
Reforge::InternalLogger.using_reforge_log_filter!
|
15
|
+
assert_equal :trace, logger_a.level
|
16
|
+
assert_equal :trace, logger_b.level
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class A
|
22
|
+
end
|
23
|
+
|
24
|
+
class B
|
25
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class JavascriptStubTest < Minitest::Test
|
6
|
+
PROJECT_ENV_ID = 1
|
7
|
+
DEFAULT_VALUE = 'default_value'
|
8
|
+
DEFAULT_VALUE_CONFIG = PrefabProto::ConfigValue.new(string: DEFAULT_VALUE)
|
9
|
+
TRUE_CONFIG = PrefabProto::ConfigValue.new(bool: true)
|
10
|
+
FALSE_CONFIG = PrefabProto::ConfigValue.new(bool: false)
|
11
|
+
DEFAULT_ROW = PrefabProto::ConfigRow.new(
|
12
|
+
values: [
|
13
|
+
PrefabProto::ConditionalValue.new(value: DEFAULT_VALUE_CONFIG)
|
14
|
+
]
|
15
|
+
)
|
16
|
+
|
17
|
+
def setup
|
18
|
+
super
|
19
|
+
|
20
|
+
log_level = PrefabProto::Config.new(
|
21
|
+
id: 999,
|
22
|
+
key: 'log-level',
|
23
|
+
config_type: PrefabProto::ConfigType::LOG_LEVEL,
|
24
|
+
rows: [
|
25
|
+
PrefabProto::ConfigRow.new(
|
26
|
+
values: [
|
27
|
+
PrefabProto::ConditionalValue.new(
|
28
|
+
criteria: [],
|
29
|
+
value: PrefabProto::ConfigValue.new(log_level: PrefabProto::LogLevel::INFO)
|
30
|
+
)
|
31
|
+
]
|
32
|
+
)
|
33
|
+
]
|
34
|
+
)
|
35
|
+
|
36
|
+
config_for_sdk = PrefabProto::Config.new(
|
37
|
+
id: 123,
|
38
|
+
key: 'basic-config',
|
39
|
+
config_type: PrefabProto::ConfigType::CONFIG,
|
40
|
+
rows: [DEFAULT_ROW],
|
41
|
+
send_to_client_sdk: true
|
42
|
+
)
|
43
|
+
|
44
|
+
config_not_for_sdk = PrefabProto::Config.new(
|
45
|
+
id: 787,
|
46
|
+
key: 'non-sdk-basic-config',
|
47
|
+
config_type: PrefabProto::ConfigType::CONFIG,
|
48
|
+
rows: [DEFAULT_ROW]
|
49
|
+
)
|
50
|
+
|
51
|
+
json_config = PrefabProto::Config.new(
|
52
|
+
id: 234,
|
53
|
+
key: 'json-config',
|
54
|
+
config_type: PrefabProto::ConfigType::CONFIG,
|
55
|
+
send_to_client_sdk: true,
|
56
|
+
rows: [
|
57
|
+
PrefabProto::ConfigRow.new(
|
58
|
+
values: [
|
59
|
+
PrefabProto::ConditionalValue.new(value: PrefabProto::ConfigValue.new(json: PrefabProto::Json.new(json: '{"key":"value"}')))
|
60
|
+
]
|
61
|
+
)
|
62
|
+
]
|
63
|
+
)
|
64
|
+
|
65
|
+
duration_config = PrefabProto::Config.new(
|
66
|
+
id: 236,
|
67
|
+
key: 'duration-config',
|
68
|
+
config_type: PrefabProto::ConfigType::CONFIG,
|
69
|
+
send_to_client_sdk: true,
|
70
|
+
rows: [
|
71
|
+
PrefabProto::ConfigRow.new(
|
72
|
+
values: [
|
73
|
+
PrefabProto::ConditionalValue.new(value: PrefabProto::ConfigValue.new(duration: PrefabProto::IsoDuration.new(definition: "P4DT12H30M5S")))
|
74
|
+
]
|
75
|
+
)
|
76
|
+
]
|
77
|
+
)
|
78
|
+
|
79
|
+
ff = PrefabProto::Config.new(
|
80
|
+
id: 456,
|
81
|
+
key: 'feature-flag',
|
82
|
+
config_type: PrefabProto::ConfigType::FEATURE_FLAG,
|
83
|
+
rows: [
|
84
|
+
PrefabProto::ConfigRow.new(
|
85
|
+
values: [
|
86
|
+
PrefabProto::ConditionalValue.new(
|
87
|
+
value: TRUE_CONFIG,
|
88
|
+
criteria: [
|
89
|
+
PrefabProto::Criterion.new(
|
90
|
+
operator: PrefabProto::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
|
91
|
+
value_to_match: string_list(['hotmail.com', 'gmail.com']),
|
92
|
+
property_name: 'user.email'
|
93
|
+
)
|
94
|
+
]
|
95
|
+
),
|
96
|
+
PrefabProto::ConditionalValue.new(value: FALSE_CONFIG)
|
97
|
+
]
|
98
|
+
)
|
99
|
+
]
|
100
|
+
)
|
101
|
+
|
102
|
+
@client = new_client(
|
103
|
+
config: [log_level, config_for_sdk, config_not_for_sdk, ff, json_config, duration_config],
|
104
|
+
project_env_id: PROJECT_ENV_ID,
|
105
|
+
collect_evaluation_summaries: true,
|
106
|
+
context_upload_mode: :periodic_example,
|
107
|
+
allow_telemetry_in_local_mode: true
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_bootstrap
|
112
|
+
result = Reforge::JavaScriptStub.new(@client).bootstrap({})
|
113
|
+
|
114
|
+
|
115
|
+
File.open('/tmp/prefab_config.json', 'w') do |f|
|
116
|
+
f.write(result)
|
117
|
+
end
|
118
|
+
assert_equal %(
|
119
|
+
window._prefabBootstrap = {
|
120
|
+
evaluations: {"log-level":{"value":{"logLevel":"INFO"}},"basic-config":{"value":{"string":"default_value"}},"feature-flag":{"value":{"bool":false}},"json-config":{"value":{"json":"{\\"key\\":\\"value\\"}"}},"duration-config":{"value":{"duration":{"ms":390605000.0,"seconds":390605.0}}}},
|
121
|
+
context: {}
|
122
|
+
}
|
123
|
+
).strip, result.strip
|
124
|
+
|
125
|
+
result = Reforge::JavaScriptStub.new(@client).bootstrap({ user: { email: 'gmail.com' } })
|
126
|
+
|
127
|
+
File.open('/tmp/prefab_config.json', 'w') do |f|
|
128
|
+
f.write(result)
|
129
|
+
end
|
130
|
+
|
131
|
+
assert_equal %(
|
132
|
+
window._prefabBootstrap = {
|
133
|
+
evaluations: {"log-level":{"value":{"logLevel":"INFO"}},"basic-config":{"value":{"string":"default_value"}},"feature-flag":{"value":{"bool":true}},"json-config":{"value":{"json":"{\\"key\\":\\"value\\"}"}},"duration-config":{"value":{"duration":{"ms":390605000.0,"seconds":390605.0}}}},
|
134
|
+
context: {"user":{"email":"gmail.com"}}
|
135
|
+
}
|
136
|
+
|
137
|
+
).strip, result.strip
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_generate_stub
|
141
|
+
result = Reforge::JavaScriptStub.new(@client).generate_stub({})
|
142
|
+
|
143
|
+
assert_equal %(
|
144
|
+
window.prefab = window.prefab || {};
|
145
|
+
window.prefab.config = {"log-level":"INFO","basic-config":"default_value","feature-flag":false,"json-config":"{\\"key\\":\\"value\\"}","duration-config":{"ms":390605000.0,"seconds":390605.0}};
|
146
|
+
window.prefab.get = function(key) {
|
147
|
+
var value = window.prefab.config[key];
|
148
|
+
|
149
|
+
return value;
|
150
|
+
};
|
151
|
+
window.prefab.isEnabled = function(key) {
|
152
|
+
var value = window.prefab.config[key] === true;
|
153
|
+
|
154
|
+
return value;
|
155
|
+
};
|
156
|
+
).strip, result.strip
|
157
|
+
|
158
|
+
result = Reforge::JavaScriptStub.new(@client).generate_stub({ user: { email: 'gmail.com' } }, 'myEvalCallback')
|
159
|
+
|
160
|
+
assert_equal %(
|
161
|
+
window.prefab = window.prefab || {};
|
162
|
+
window.prefab.config = {"log-level":"INFO","basic-config":"default_value","feature-flag":true,"json-config":"{\\"key\\":\\"value\\"}","duration-config":{"ms":390605000.0,"seconds":390605.0}};
|
163
|
+
window.prefab.get = function(key) {
|
164
|
+
var value = window.prefab.config[key];
|
165
|
+
myEvalCallback(key, value);
|
166
|
+
return value;
|
167
|
+
};
|
168
|
+
window.prefab.isEnabled = function(key) {
|
169
|
+
var value = window.prefab.config[key] === true;
|
170
|
+
myEvalCallback(key, value);
|
171
|
+
return value;
|
172
|
+
};
|
173
|
+
|
174
|
+
).strip, result.strip
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestLocalConfigParser < Minitest::Test
|
6
|
+
FILE_NAME = 'example-config.yaml'
|
7
|
+
DEFAULT_MATCH = 'default'
|
8
|
+
|
9
|
+
def setup
|
10
|
+
super
|
11
|
+
@mock_resolver = MockResolver.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_parse_int_config
|
15
|
+
key = :sample_int
|
16
|
+
parsed = Reforge::LocalConfigParser.parse(key, 123, {}, FILE_NAME)[key]
|
17
|
+
config = parsed[:config]
|
18
|
+
|
19
|
+
assert_equal FILE_NAME, parsed[:source]
|
20
|
+
assert_equal DEFAULT_MATCH, parsed[:match]
|
21
|
+
assert_equal :CONFIG, config.config_type
|
22
|
+
assert_equal key.to_s, config.key
|
23
|
+
assert_equal 1, config.rows.size
|
24
|
+
assert_equal 1, config.rows[0].values.size
|
25
|
+
assert_equal 123, config.rows[0].values[0].value.int
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_flag_with_a_value
|
29
|
+
key = :flag_with_a_value
|
30
|
+
value = stringify_keys({ feature_flag: true, value: 'all-features' })
|
31
|
+
parsed = Reforge::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
32
|
+
config = parsed[:config]
|
33
|
+
|
34
|
+
assert_equal FILE_NAME, parsed[:source]
|
35
|
+
assert_equal key, parsed[:match]
|
36
|
+
assert_equal :FEATURE_FLAG, config.config_type
|
37
|
+
assert_equal key.to_s, config.key
|
38
|
+
assert_equal 1, config.rows.size
|
39
|
+
assert_equal 1, config.rows[0].values.size
|
40
|
+
|
41
|
+
value_row = config.rows[0].values[0]
|
42
|
+
assert_equal 'all-features', Reforge::ConfigValueUnwrapper.deepest_value(value_row.value, key, {}, @mock_resolver).unwrap
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_flag_in_user_key
|
46
|
+
key = :flag_in_user_key
|
47
|
+
value = stringify_keys({ 'feature_flag': 'true', value: true,
|
48
|
+
criterion: { operator: 'PROP_IS_ONE_OF', property: 'user.key', values: %w[abc123 xyz987] } })
|
49
|
+
parsed = Reforge::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
50
|
+
config = parsed[:config]
|
51
|
+
|
52
|
+
assert_equal FILE_NAME, parsed[:source]
|
53
|
+
assert_equal key, parsed[:match]
|
54
|
+
assert_equal :FEATURE_FLAG, config.config_type
|
55
|
+
assert_equal key.to_s, config.key
|
56
|
+
assert_equal 1, config.rows.size
|
57
|
+
assert_equal 1, config.rows[0].values.size
|
58
|
+
assert_equal 1, config.rows[0].values[0].criteria.size
|
59
|
+
|
60
|
+
value_row = config.rows[0].values[0]
|
61
|
+
assert_equal true, Reforge::ConfigValueUnwrapper.deepest_value(value_row.value, key, {}, @mock_resolver).unwrap
|
62
|
+
|
63
|
+
assert_equal 'user.key', value_row.criteria[0].property_name
|
64
|
+
assert_equal :PROP_IS_ONE_OF, value_row.criteria[0].operator
|
65
|
+
assert_equal %w[abc123 xyz987], value_row.criteria[0].value_to_match.string_list.values
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_provided_values
|
69
|
+
with_env('LOOKUP_ENV', 'from env') do
|
70
|
+
key = :test_provided
|
71
|
+
value = stringify_keys({type: 'provided', source: 'ENV_VAR', lookup: 'LOOKUP_ENV'})
|
72
|
+
parsed = Reforge::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
73
|
+
config = parsed[:config]
|
74
|
+
|
75
|
+
assert_equal FILE_NAME, parsed[:source]
|
76
|
+
assert_equal 'LOOKUP_ENV', parsed[:match]
|
77
|
+
assert_equal :CONFIG, config.config_type
|
78
|
+
assert_equal key.to_s, config.key
|
79
|
+
assert_equal 1, config.rows.size
|
80
|
+
assert_equal 1, config.rows[0].values.size
|
81
|
+
|
82
|
+
value_row = config.rows[0].values[0]
|
83
|
+
provided = value_row.value.provided
|
84
|
+
assert_equal :ENV_VAR, provided.source
|
85
|
+
assert_equal 'LOOKUP_ENV', provided.lookup
|
86
|
+
assert_equal 'from env', Reforge::ConfigValueUnwrapper.deepest_value(value_row.value, config, {}, @mock_resolver).unwrap
|
87
|
+
reportable_value = Reforge::ConfigValueUnwrapper.deepest_value(value_row.value, config, {}, @mock_resolver).reportable_value
|
88
|
+
assert_equal 'from env', reportable_value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_confidential_provided_values
|
93
|
+
with_env('LOOKUP_ENV', 'from env') do
|
94
|
+
key = :test_provided
|
95
|
+
value = stringify_keys({type: 'provided', source: 'ENV_VAR', lookup: 'LOOKUP_ENV', confidential: true})
|
96
|
+
parsed = Reforge::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
97
|
+
config = parsed[:config]
|
98
|
+
|
99
|
+
value_row = config.rows[0].values[0]
|
100
|
+
provided = value_row.value.provided
|
101
|
+
assert_equal :ENV_VAR, provided.source
|
102
|
+
assert_equal 'LOOKUP_ENV', provided.lookup
|
103
|
+
assert_equal 'from env', Reforge::ConfigValueUnwrapper.deepest_value(value_row.value, config, {}, @mock_resolver).unwrap
|
104
|
+
reportable_value = Reforge::ConfigValueUnwrapper.deepest_value(value_row.value, config, {}, @mock_resolver).reportable_value
|
105
|
+
assert reportable_value.start_with? Reforge::ConfigValueUnwrapper::CONFIDENTIAL_PREFIX
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_confidential_values
|
110
|
+
key = :test_confidential
|
111
|
+
value = stringify_keys({value: 'a confidential string', confidential: true})
|
112
|
+
parsed = Reforge::LocalConfigParser.parse(key, value, {}, FILE_NAME)[key]
|
113
|
+
config = parsed[:config]
|
114
|
+
|
115
|
+
assert_equal FILE_NAME, parsed[:source]
|
116
|
+
assert_equal :CONFIG, config.config_type
|
117
|
+
assert_equal key.to_s, config.key
|
118
|
+
assert_equal 1, config.rows.size
|
119
|
+
assert_equal 1, config.rows[0].values.size
|
120
|
+
|
121
|
+
value_row = config.rows[0].values[0]
|
122
|
+
config_value = value_row.value
|
123
|
+
assert_equal 'a confidential string', Reforge::ConfigValueUnwrapper.deepest_value(config_value, key, {}, @mock_resolver).unwrap
|
124
|
+
reportable_value = Reforge::ConfigValueUnwrapper.deepest_value(config_value, key, {}, @mock_resolver).reportable_value
|
125
|
+
assert reportable_value.start_with? Reforge::ConfigValueUnwrapper::CONFIDENTIAL_PREFIX
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def stringify_keys(hash)
|
131
|
+
deep_transform_keys(hash, &:to_s)
|
132
|
+
end
|
133
|
+
|
134
|
+
def deep_transform_keys(hash, &block)
|
135
|
+
result = {}
|
136
|
+
hash.each do |key, value|
|
137
|
+
result[yield(key)] = value.is_a?(Hash) ? deep_transform_keys(value, &block) : value
|
138
|
+
end
|
139
|
+
result
|
140
|
+
end
|
141
|
+
|
142
|
+
class MockResolver
|
143
|
+
def get(key)
|
144
|
+
raise "unexpected key"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class TestOptions < Minitest::Test
|
6
|
+
API_KEY = 'abcdefg'
|
7
|
+
|
8
|
+
def test_api_override_env_var
|
9
|
+
assert_equal Reforge::Options::DEFAULT_SOURCES, Reforge::Options.new.sources
|
10
|
+
|
11
|
+
# blank doesn't take effect
|
12
|
+
with_env('PREFAB_API_URL_OVERRIDE', '') do
|
13
|
+
assert_equal Reforge::Options::DEFAULT_SOURCES, Reforge::Options.new.sources
|
14
|
+
end
|
15
|
+
|
16
|
+
# non-blank does take effect
|
17
|
+
with_env('PREFAB_API_URL_OVERRIDE', 'https://override.example.com') do
|
18
|
+
assert_equal ["https://override.example.com"], Reforge::Options.new.sources
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_overriding_sources
|
23
|
+
assert_equal Reforge::Options::DEFAULT_SOURCES, Reforge::Options.new.sources
|
24
|
+
|
25
|
+
# a plain string ends up wrapped in an array
|
26
|
+
source = 'https://example.com'
|
27
|
+
assert_equal [source], Reforge::Options.new(sources: source).sources
|
28
|
+
|
29
|
+
sources = ['https://example.com', 'https://example2.com']
|
30
|
+
assert_equal sources, Reforge::Options.new(sources: sources).sources
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_works_with_named_arguments
|
34
|
+
assert_equal API_KEY, Reforge::Options.new(sdk_key: API_KEY).sdk_key
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_works_with_hash
|
38
|
+
assert_equal API_KEY, Reforge::Options.new({ sdk_key: API_KEY }).sdk_key
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_collect_max_paths
|
42
|
+
assert_equal 1000, Reforge::Options.new.collect_max_paths
|
43
|
+
assert_equal 100, Reforge::Options.new(collect_max_paths: 100).collect_max_paths
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_collect_max_paths_with_local_only
|
47
|
+
options = Reforge::Options.new(collect_max_paths: 100,
|
48
|
+
prefab_datasources: Reforge::Options::DATASOURCES::LOCAL_ONLY)
|
49
|
+
assert_equal 0, options.collect_max_paths
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_collect_max_paths_with_collect_logger_counts_false
|
53
|
+
options = Reforge::Options.new(collect_max_paths: 100,
|
54
|
+
collect_logger_counts: false)
|
55
|
+
assert_equal 0, options.collect_max_paths
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_collect_max_evaluation_summaries
|
59
|
+
assert_equal 100_000, Reforge::Options.new.collect_max_evaluation_summaries
|
60
|
+
assert_equal 0, Reforge::Options.new(collect_evaluation_summaries: false).collect_max_evaluation_summaries
|
61
|
+
assert_equal 3,
|
62
|
+
Reforge::Options.new(collect_max_evaluation_summaries: 3).collect_max_evaluation_summaries
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_context_upload_mode_periodic
|
66
|
+
options = Reforge::Options.new(context_upload_mode: :periodic_example, context_max_size: 100)
|
67
|
+
assert_equal 100, options.collect_max_example_contexts
|
68
|
+
|
69
|
+
options = Reforge::Options.new(context_upload_mode: :none)
|
70
|
+
assert_equal 0, options.collect_max_example_contexts
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_context_upload_mode_shape_only
|
74
|
+
options = Reforge::Options.new(context_upload_mode: :shape_only, context_max_size: 100)
|
75
|
+
assert_equal 100, options.collect_max_shapes
|
76
|
+
|
77
|
+
options = Reforge::Options.new(context_upload_mode: :none)
|
78
|
+
assert_equal 0, options.collect_max_shapes
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_context_upload_mode_none
|
82
|
+
options = Reforge::Options.new(context_upload_mode: :none)
|
83
|
+
assert_equal 0, options.collect_max_example_contexts
|
84
|
+
|
85
|
+
options = Reforge::Options.new(context_upload_mode: :none)
|
86
|
+
assert_equal 0, options.collect_max_shapes
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_loading_a_datafile
|
90
|
+
options = Reforge::Options.new(datafile: "#{Dir.pwd}/test/fixtures/datafile.json")
|
91
|
+
assert_equal "#{Dir.pwd}/test/fixtures/datafile.json", options.datafile
|
92
|
+
end
|
93
|
+
end
|
data/test/test_prefab.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
class RateLimitCacheTest < Minitest::Test
|
7
|
+
def test_set_and_fresh
|
8
|
+
cache = Reforge::RateLimitCache.new(5)
|
9
|
+
cache.set('key')
|
10
|
+
assert cache.fresh?('key')
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_fresh_with_no_set
|
14
|
+
cache = Reforge::RateLimitCache.new(5)
|
15
|
+
refute cache.fresh?('key')
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_get_after_expiration
|
19
|
+
cache = Reforge::RateLimitCache.new(5)
|
20
|
+
|
21
|
+
Timecop.freeze(Time.now - 6) do
|
22
|
+
cache.set('key')
|
23
|
+
assert cache.fresh?('key')
|
24
|
+
end
|
25
|
+
|
26
|
+
refute cache.fresh?('key')
|
27
|
+
|
28
|
+
# but the data is still there
|
29
|
+
assert cache.data.get('key')
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_prune
|
33
|
+
cache = Reforge::RateLimitCache.new(5)
|
34
|
+
|
35
|
+
Timecop.freeze(Time.now - 6) do
|
36
|
+
cache.set('key')
|
37
|
+
assert cache.fresh?('key')
|
38
|
+
end
|
39
|
+
|
40
|
+
cache.prune
|
41
|
+
|
42
|
+
refute cache.fresh?('key')
|
43
|
+
end
|
44
|
+
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
|