quonfig 0.0.10 → 0.0.12
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/CHANGELOG.md +30 -0
- data/README.md +94 -0
- data/lib/quonfig/caching_http_connection.rb +3 -3
- data/lib/quonfig/client.rb +22 -27
- data/lib/quonfig/config_loader.rb +5 -1
- data/lib/quonfig/config_store.rb +10 -6
- data/lib/quonfig/context.rb +5 -4
- data/lib/quonfig/datadir.rb +4 -10
- data/lib/quonfig/dev_context.rb +4 -2
- data/lib/quonfig/duration.rb +2 -2
- data/lib/quonfig/encryption.rb +12 -16
- data/lib/quonfig/errors/invalid_environment_error.rb +1 -3
- data/lib/quonfig/errors/invalid_sdk_key_error.rb +6 -7
- data/lib/quonfig/errors/missing_env_var_error.rb +0 -3
- data/lib/quonfig/errors/missing_environment_error.rb +1 -1
- data/lib/quonfig/errors/uninitialized_error.rb +1 -1
- data/lib/quonfig/evaluation.rb +11 -8
- data/lib/quonfig/evaluator.rb +34 -37
- data/lib/quonfig/fixed_size_hash.rb +1 -0
- data/lib/quonfig/http_connection.rb +2 -4
- data/lib/quonfig/internal_logger.rb +63 -27
- data/lib/quonfig/murmer3.rb +2 -2
- data/lib/quonfig/options.rb +62 -75
- data/lib/quonfig/periodic_sync.rb +1 -1
- data/lib/quonfig/quonfig.rb +3 -3
- data/lib/quonfig/reason.rb +2 -1
- data/lib/quonfig/resolver.rb +8 -9
- data/lib/quonfig/semantic_logger_filter.rb +4 -3
- data/lib/quonfig/semver.rb +6 -8
- data/lib/quonfig/sse_config_client.rb +14 -15
- data/lib/quonfig/stdlib_formatter.rb +3 -3
- data/lib/quonfig/telemetry/context_shape_aggregator.rb +2 -3
- data/lib/quonfig/telemetry/example_contexts_aggregator.rb +1 -1
- data/lib/quonfig/telemetry/telemetry_reporter.rb +1 -0
- data/lib/quonfig/time_helpers.rb +2 -0
- data/lib/quonfig/version.rb +5 -0
- data/lib/quonfig.rb +2 -1
- data/quonfig.gemspec +29 -165
- metadata +24 -193
- data/.claude/rules/constitution.md +0 -81
- data/.claude/rules/git-safety.md +0 -11
- data/.claude/rules/issue-tracking.md +0 -13
- data/.claude/rules/testing-workflow.md +0 -28
- data/.envrc.sample +0 -3
- data/.github/CODEOWNERS +0 -2
- data/.github/pull_request_template.md +0 -8
- data/.github/workflows/release.yml +0 -49
- data/.github/workflows/ruby.yml +0 -60
- data/.github/workflows/test.yaml +0 -40
- data/.rubocop.yml +0 -13
- data/.tool-versions +0 -1
- data/CLAUDE.md +0 -29
- data/CODEOWNERS +0 -1
- data/Gemfile +0 -26
- data/Gemfile.lock +0 -177
- data/Rakefile +0 -64
- data/VERSION +0 -1
- data/dev/allocation_stats +0 -60
- data/dev/benchmark +0 -40
- data/dev/console +0 -12
- data/dev/script_setup.rb +0 -18
- data/test/fixtures/datafile.json +0 -87
- data/test/integration/test_context_precedence.rb +0 -112
- data/test/integration/test_datadir_environment.rb +0 -54
- data/test/integration/test_dev_overrides.rb +0 -40
- data/test/integration/test_enabled.rb +0 -478
- data/test/integration/test_enabled_with_contexts.rb +0 -64
- data/test/integration/test_get.rb +0 -136
- data/test/integration/test_get_feature_flag.rb +0 -28
- data/test/integration/test_get_or_raise.rb +0 -60
- data/test/integration/test_get_weighted_values.rb +0 -34
- data/test/integration/test_helpers.rb +0 -667
- data/test/integration/test_helpers_test.rb +0 -73
- data/test/integration/test_post.rb +0 -44
- data/test/integration/test_telemetry.rb +0 -170
- data/test/support/common_helpers.rb +0 -106
- data/test/support/mock_base_client.rb +0 -27
- data/test/support/mock_config_loader.rb +0 -1
- data/test/test_bound_client.rb +0 -109
- data/test/test_caching_http_connection.rb +0 -218
- data/test/test_client.rb +0 -255
- data/test/test_client_network_mode.rb +0 -136
- data/test/test_client_telemetry.rb +0 -175
- data/test/test_config_loader.rb +0 -70
- data/test/test_context.rb +0 -139
- data/test/test_context_shape.rb +0 -37
- data/test/test_context_shape_aggregator.rb +0 -126
- data/test/test_datadir.rb +0 -203
- data/test/test_details_getters.rb +0 -242
- data/test/test_dev_context.rb +0 -163
- data/test/test_duration.rb +0 -37
- data/test/test_encryption.rb +0 -16
- data/test/test_evaluation_summaries_aggregator.rb +0 -180
- data/test/test_evaluator.rb +0 -285
- data/test/test_example_contexts_aggregator.rb +0 -119
- data/test/test_exponential_backoff.rb +0 -44
- data/test/test_fixed_size_hash.rb +0 -119
- data/test/test_helper.rb +0 -17
- data/test/test_http_connection.rb +0 -81
- data/test/test_internal_logger.rb +0 -34
- data/test/test_options.rb +0 -198
- data/test/test_rate_limit_cache.rb +0 -44
- data/test/test_reason.rb +0 -79
- data/test/test_rename.rb +0 -65
- data/test/test_resolver.rb +0 -291
- data/test/test_semantic_logger_filter.rb +0 -144
- data/test/test_semver.rb +0 -108
- data/test/test_should_log.rb +0 -186
- data/test/test_sse_config_client.rb +0 -297
- data/test/test_stdlib_formatter.rb +0 -195
- data/test/test_telemetry_reporter.rb +0 -209
- data/test/test_typed_getters.rb +0 -131
- data/test/test_types.rb +0 -141
- data/test/test_weighted_value_resolver.rb +0 -84
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'minitest/autorun'
|
|
4
|
-
require 'test_helper'
|
|
5
|
-
|
|
6
|
-
module Quonfig
|
|
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_helper.rb
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'minitest/autorun'
|
|
4
|
-
require 'minitest/focus'
|
|
5
|
-
require 'minitest/reporters'
|
|
6
|
-
Minitest::Reporters.use! unless ENV['RM_INFO']
|
|
7
|
-
|
|
8
|
-
require 'quonfig'
|
|
9
|
-
|
|
10
|
-
Dir.glob(File.join(File.dirname(__FILE__), 'support', '**', '*.rb')).each do |file|
|
|
11
|
-
require file
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
Minitest::Test.class_eval do
|
|
15
|
-
include CommonHelpers
|
|
16
|
-
extend CommonHelpers
|
|
17
|
-
end
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'test_helper'
|
|
4
|
-
require 'base64'
|
|
5
|
-
require 'json'
|
|
6
|
-
|
|
7
|
-
module Quonfig
|
|
8
|
-
class HttpConnectionTest < Minitest::Test
|
|
9
|
-
URI = 'https://primary.quonfig.com'
|
|
10
|
-
SDK_KEY = 'abc.def.123'
|
|
11
|
-
|
|
12
|
-
def test_uses_basic_auth_with_username_1
|
|
13
|
-
conn = HttpConnection.new(URI, SDK_KEY).connection
|
|
14
|
-
expected = 'Basic ' + Base64.strict_encode64("1:#{SDK_KEY}")
|
|
15
|
-
assert_equal expected, conn.headers['Authorization']
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def test_json_content_type_and_accept_headers
|
|
19
|
-
conn = HttpConnection.new(URI, SDK_KEY).connection
|
|
20
|
-
assert_equal 'application/json', conn.headers['Content-Type']
|
|
21
|
-
assert_equal 'application/json', conn.headers['Accept']
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def test_x_quonfig_sdk_version_header
|
|
25
|
-
conn = HttpConnection.new(URI, SDK_KEY).connection
|
|
26
|
-
assert_equal "ruby-#{Quonfig::VERSION}", conn.headers['X-Quonfig-SDK-Version']
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def test_no_protobuf_content_type
|
|
30
|
-
conn = HttpConnection.new(URI, SDK_KEY).connection
|
|
31
|
-
refute_equal 'application/x-protobuf', conn.headers['Content-Type']
|
|
32
|
-
refute_equal 'application/x-protobuf', conn.headers['Accept']
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def test_post_serializes_body_as_json
|
|
36
|
-
stubs = Faraday::Adapter::Test::Stubs.new
|
|
37
|
-
captured_body = nil
|
|
38
|
-
captured_content_type = nil
|
|
39
|
-
stubs.post('/telemetry') do |env|
|
|
40
|
-
captured_body = env.body
|
|
41
|
-
captured_content_type = env.request_headers['Content-Type']
|
|
42
|
-
[200, {}, '']
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
http = HttpConnection.new(URI, SDK_KEY)
|
|
46
|
-
http.define_singleton_method(:connection) do |headers = {}|
|
|
47
|
-
Faraday.new(URI) do |conn|
|
|
48
|
-
conn.headers.merge!(HttpConnection::JSON_HEADERS.merge(headers))
|
|
49
|
-
conn.adapter :test, stubs
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
body = { hello: 'world', n: 3 }
|
|
54
|
-
http.post('/telemetry', body)
|
|
55
|
-
|
|
56
|
-
assert_equal 'application/json', captured_content_type
|
|
57
|
-
assert_equal body.to_json, captured_body
|
|
58
|
-
stubs.verify_stubbed_calls
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
class OptionsDefaultApiUrlsTest < Minitest::Test
|
|
63
|
-
def test_default_api_urls_use_quonfig_domain
|
|
64
|
-
assert_includes Options::DEFAULT_API_URLS, 'https://primary.quonfig.com'
|
|
65
|
-
refute(Options::DEFAULT_API_URLS.any? { |s| s.include?('reforge.com') })
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def test_telemetry_destination_honors_quonfig_domain_env
|
|
69
|
-
with_env('QUONFIG_DOMAIN', 'quonfig-staging.com') do
|
|
70
|
-
assert_equal 'https://telemetry.quonfig-staging.com',
|
|
71
|
-
Options.new.telemetry_destination
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def test_telemetry_destination_default
|
|
76
|
-
with_env('QUONFIG_DOMAIN', nil) do
|
|
77
|
-
assert_equal 'https://telemetry.quonfig.com', Options.new.telemetry_destination
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'test_helper'
|
|
4
|
-
|
|
5
|
-
class TestInternalLogger < Minitest::Test
|
|
6
|
-
|
|
7
|
-
def teardown
|
|
8
|
-
# using_quonfig_log_filter! mutates the shared @@instances list — restore
|
|
9
|
-
# the default :warn level so it doesn't bleed into other tests' $logs.
|
|
10
|
-
Quonfig::InternalLogger.class_variable_get(:@@instances).each do |logger|
|
|
11
|
-
logger.level = :warn
|
|
12
|
-
end
|
|
13
|
-
super
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def test_levels
|
|
17
|
-
logger_a = Quonfig::InternalLogger.new(A)
|
|
18
|
-
logger_b = Quonfig::InternalLogger.new(B)
|
|
19
|
-
|
|
20
|
-
assert_equal :warn, logger_a.level
|
|
21
|
-
assert_equal :warn, logger_b.level
|
|
22
|
-
|
|
23
|
-
Quonfig::InternalLogger.using_quonfig_log_filter!
|
|
24
|
-
assert_equal :trace, logger_a.level
|
|
25
|
-
assert_equal :trace, logger_b.level
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
class A
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
class B
|
|
34
|
-
end
|
data/test/test_options.rb
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'test_helper'
|
|
4
|
-
|
|
5
|
-
class TestOptions < Minitest::Test
|
|
6
|
-
API_KEY = 'abcdefg'
|
|
7
|
-
|
|
8
|
-
def test_default_api_urls_point_to_quonfig
|
|
9
|
-
# DEFAULT_API_URLS is the hardcoded fallback when neither QUONFIG_DOMAIN
|
|
10
|
-
# nor an explicit api_urls: kwarg is set. Domain-derivation happens at
|
|
11
|
-
# construction time, not at constant load time — see derive_api_urls.
|
|
12
|
-
assert_equal [
|
|
13
|
-
'https://primary.quonfig.com',
|
|
14
|
-
'https://secondary.quonfig.com',
|
|
15
|
-
], Quonfig::Options::DEFAULT_API_URLS
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def test_overriding_api_urls
|
|
19
|
-
assert_equal Quonfig::Options::DEFAULT_API_URLS, Quonfig::Options.new.api_urls
|
|
20
|
-
|
|
21
|
-
# a plain string ends up wrapped in an array
|
|
22
|
-
api_url = 'https://example.com'
|
|
23
|
-
assert_equal [api_url], Quonfig::Options.new(api_urls: api_url).api_urls
|
|
24
|
-
|
|
25
|
-
api_urls = ['https://example.com', 'https://example2.com']
|
|
26
|
-
assert_equal api_urls, Quonfig::Options.new(api_urls: api_urls).api_urls
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def test_derive_stream_url_prepends_stream_to_hostname
|
|
30
|
-
assert_equal 'https://stream.primary.quonfig.com',
|
|
31
|
-
Quonfig::Options.derive_stream_url('https://primary.quonfig.com')
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def test_derive_stream_url_preserves_port
|
|
35
|
-
assert_equal 'http://stream.localhost:6550',
|
|
36
|
-
Quonfig::Options.derive_stream_url('http://localhost:6550')
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def test_derive_stream_url_preserves_scheme_and_path
|
|
40
|
-
assert_equal 'http://stream.api.example.com/base',
|
|
41
|
-
Quonfig::Options.derive_stream_url('http://api.example.com/base')
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def test_derive_stream_url_with_eu_subdomain
|
|
45
|
-
assert_equal 'https://stream.primary.eu.quonfig.com',
|
|
46
|
-
Quonfig::Options.derive_stream_url('https://primary.eu.quonfig.com')
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def test_works_with_named_arguments
|
|
50
|
-
assert_equal API_KEY, Quonfig::Options.new(sdk_key: API_KEY).sdk_key
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def test_works_with_hash
|
|
54
|
-
assert_equal API_KEY, Quonfig::Options.new({ sdk_key: API_KEY }).sdk_key
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def test_sdk_key_reads_from_quonfig_backend_sdk_key
|
|
58
|
-
with_env('QUONFIG_BACKEND_SDK_KEY', 'env-key') do
|
|
59
|
-
assert_equal 'env-key', Quonfig::Options.new.sdk_key
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def test_environment_reads_from_quonfig_environment
|
|
64
|
-
with_env('QUONFIG_ENVIRONMENT', 'staging') do
|
|
65
|
-
assert_equal 'staging', Quonfig::Options.new.environment
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def test_environment_explicit_overrides_env_var
|
|
70
|
-
with_env('QUONFIG_ENVIRONMENT', 'staging') do
|
|
71
|
-
assert_equal 'production', Quonfig::Options.new(environment: 'production').environment
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def test_enable_sse_defaults_true
|
|
76
|
-
assert_equal true, Quonfig::Options.new.enable_sse
|
|
77
|
-
assert_equal false, Quonfig::Options.new(enable_sse: false).enable_sse
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def test_enable_polling_defaults_true
|
|
81
|
-
assert_equal true, Quonfig::Options.new.enable_polling
|
|
82
|
-
assert_equal false, Quonfig::Options.new(enable_polling: false).enable_polling
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def test_datadir_reads_from_quonfig_dir_env
|
|
86
|
-
with_env('QUONFIG_DIR', '/tmp/some/workspace') do
|
|
87
|
-
assert_equal '/tmp/some/workspace', Quonfig::Options.new.datadir
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def test_datadir_explicit_overrides_env_var
|
|
92
|
-
with_env('QUONFIG_DIR', '/tmp/env/workspace') do
|
|
93
|
-
assert_equal '/tmp/explicit', Quonfig::Options.new(datadir: '/tmp/explicit').datadir
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def test_datadir_predicate
|
|
98
|
-
assert_equal false, Quonfig::Options.new.datadir?
|
|
99
|
-
assert_equal true, Quonfig::Options.new(datadir: '/tmp/ws').datadir?
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def test_local_only_uses_datadir_presence
|
|
103
|
-
refute Quonfig::Options.new.local_only?
|
|
104
|
-
assert Quonfig::Options.new(datadir: '/tmp/ws').local_only?
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def test_collect_max_paths
|
|
108
|
-
assert_equal 1000, Quonfig::Options.new.collect_max_paths
|
|
109
|
-
assert_equal 100, Quonfig::Options.new(collect_max_paths: 100).collect_max_paths
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def test_collect_max_evaluation_summaries
|
|
113
|
-
assert_equal 100_000, Quonfig::Options.new.collect_max_evaluation_summaries
|
|
114
|
-
assert_equal 0, Quonfig::Options.new(collect_evaluation_summaries: false).collect_max_evaluation_summaries
|
|
115
|
-
assert_equal 3,
|
|
116
|
-
Quonfig::Options.new(collect_max_evaluation_summaries: 3).collect_max_evaluation_summaries
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def test_context_upload_mode_periodic
|
|
120
|
-
options = Quonfig::Options.new(context_upload_mode: :periodic_example, context_max_size: 100)
|
|
121
|
-
assert_equal 100, options.collect_max_example_contexts
|
|
122
|
-
|
|
123
|
-
options = Quonfig::Options.new(context_upload_mode: :none)
|
|
124
|
-
assert_equal 0, options.collect_max_example_contexts
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def test_context_upload_mode_shapes_only
|
|
128
|
-
options = Quonfig::Options.new(context_upload_mode: :shapes_only, context_max_size: 100)
|
|
129
|
-
assert_equal 100, options.collect_max_shapes
|
|
130
|
-
|
|
131
|
-
options = Quonfig::Options.new(context_upload_mode: :none)
|
|
132
|
-
assert_equal 0, options.collect_max_shapes
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def test_context_upload_mode_none
|
|
136
|
-
options = Quonfig::Options.new(context_upload_mode: :none)
|
|
137
|
-
assert_equal 0, options.collect_max_example_contexts
|
|
138
|
-
|
|
139
|
-
options = Quonfig::Options.new(context_upload_mode: :none)
|
|
140
|
-
assert_equal 0, options.collect_max_shapes
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# ---- QUONFIG_DOMAIN tests (qfg-w6gg) ----
|
|
144
|
-
# A single env var `QUONFIG_DOMAIN` governs api, sse, and telemetry URL
|
|
145
|
-
# derivation. Resolution order (highest wins): explicit kwargs >
|
|
146
|
-
# QUONFIG_DOMAIN > hardcoded default 'quonfig.com'.
|
|
147
|
-
|
|
148
|
-
def test_default_domain_is_quonfig_com
|
|
149
|
-
with_env('QUONFIG_DOMAIN', nil) do
|
|
150
|
-
options = Quonfig::Options.new
|
|
151
|
-
assert_equal [
|
|
152
|
-
'https://primary.quonfig.com',
|
|
153
|
-
'https://secondary.quonfig.com',
|
|
154
|
-
], options.api_urls
|
|
155
|
-
assert_equal 'https://telemetry.quonfig.com', options.telemetry_destination
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def test_quonfig_domain_env_var_derives_all_urls
|
|
160
|
-
with_env('QUONFIG_DOMAIN', 'quonfig-staging.com') do
|
|
161
|
-
options = Quonfig::Options.new
|
|
162
|
-
assert_equal [
|
|
163
|
-
'https://primary.quonfig-staging.com',
|
|
164
|
-
'https://secondary.quonfig-staging.com',
|
|
165
|
-
], options.api_urls
|
|
166
|
-
assert_equal [
|
|
167
|
-
'https://stream.primary.quonfig-staging.com',
|
|
168
|
-
'https://stream.secondary.quonfig-staging.com',
|
|
169
|
-
], options.sse_api_urls
|
|
170
|
-
assert_equal 'https://telemetry.quonfig-staging.com', options.telemetry_destination
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def test_explicit_api_urls_override_quonfig_domain
|
|
175
|
-
with_env('QUONFIG_DOMAIN', 'quonfig-staging.com') do
|
|
176
|
-
options = Quonfig::Options.new(api_urls: ['http://localhost:8080'])
|
|
177
|
-
assert_equal ['http://localhost:8080'], options.api_urls
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def test_explicit_telemetry_url_overrides_quonfig_domain
|
|
182
|
-
with_env('QUONFIG_DOMAIN', 'quonfig-staging.com') do
|
|
183
|
-
options = Quonfig::Options.new(telemetry_url: 'http://localhost:6555')
|
|
184
|
-
assert_equal 'http://localhost:6555', options.telemetry_destination
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def test_quonfig_telemetry_url_env_var_no_longer_read
|
|
189
|
-
# QUONFIG_TELEMETRY_URL has been removed. Setting it must not affect
|
|
190
|
-
# anything; the default (quonfig.com) wins.
|
|
191
|
-
with_env('QUONFIG_DOMAIN', nil) do
|
|
192
|
-
with_env('QUONFIG_TELEMETRY_URL', 'https://should-be-ignored.example.com') do
|
|
193
|
-
assert_equal 'https://telemetry.quonfig.com',
|
|
194
|
-
Quonfig::Options.new.telemetry_destination
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
end
|
|
@@ -1,44 +0,0 @@
|
|
|
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 = Quonfig::RateLimitCache.new(5)
|
|
9
|
-
cache.set('key')
|
|
10
|
-
assert cache.fresh?('key')
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def test_fresh_with_no_set
|
|
14
|
-
cache = Quonfig::RateLimitCache.new(5)
|
|
15
|
-
refute cache.fresh?('key')
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def test_get_after_expiration
|
|
19
|
-
cache = Quonfig::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 = Quonfig::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_reason.rb
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'minitest/autorun'
|
|
4
|
-
require 'quonfig/reason'
|
|
5
|
-
|
|
6
|
-
# Tests Quonfig::Reason in isolation — uses plain Structs so the test does not
|
|
7
|
-
# depend on PrefabProto (which is not loaded by the current bootstrap).
|
|
8
|
-
class TestReason < Minitest::Test
|
|
9
|
-
FakeCriterion = Struct.new(:operator)
|
|
10
|
-
FakeConditionalValue = Struct.new(:criteria)
|
|
11
|
-
FakeRow = Struct.new(:values, :project_env_id)
|
|
12
|
-
FakeConfig = Struct.new(:rows)
|
|
13
|
-
|
|
14
|
-
ALWAYS_TRUE = FakeCriterion.new(:ALWAYS_TRUE)
|
|
15
|
-
PROP_MATCH = FakeCriterion.new(:PROP_IS_ONE_OF)
|
|
16
|
-
|
|
17
|
-
DEFAULT_CV = FakeConditionalValue.new([])
|
|
18
|
-
ALWAYS_TRUE_CV = FakeConditionalValue.new([ALWAYS_TRUE])
|
|
19
|
-
RULE_CV = FakeConditionalValue.new([PROP_MATCH])
|
|
20
|
-
|
|
21
|
-
def default_only_config
|
|
22
|
-
FakeConfig.new([FakeRow.new([DEFAULT_CV], 0)])
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def targeted_config
|
|
26
|
-
FakeConfig.new([
|
|
27
|
-
FakeRow.new([DEFAULT_CV], 0),
|
|
28
|
-
FakeRow.new([RULE_CV, ALWAYS_TRUE_CV], 1)
|
|
29
|
-
])
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def test_default_for_default_only_config
|
|
33
|
-
reason = Quonfig::Reason.compute(
|
|
34
|
-
config: default_only_config,
|
|
35
|
-
conditional_value: DEFAULT_CV
|
|
36
|
-
)
|
|
37
|
-
assert_equal :DEFAULT, reason
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def test_rule_match_when_targeting_rule_matched
|
|
41
|
-
reason = Quonfig::Reason.compute(
|
|
42
|
-
config: targeted_config,
|
|
43
|
-
conditional_value: RULE_CV
|
|
44
|
-
)
|
|
45
|
-
assert_equal :RULE_MATCH, reason
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def test_rule_match_when_falling_back_to_always_true_in_targeted_config
|
|
49
|
-
reason = Quonfig::Reason.compute(
|
|
50
|
-
config: targeted_config,
|
|
51
|
-
conditional_value: ALWAYS_TRUE_CV
|
|
52
|
-
)
|
|
53
|
-
assert_equal :RULE_MATCH, reason
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def test_split_when_weighted_value_index_positive
|
|
57
|
-
reason = Quonfig::Reason.compute(
|
|
58
|
-
config: default_only_config,
|
|
59
|
-
conditional_value: DEFAULT_CV,
|
|
60
|
-
weighted_value_index: 2
|
|
61
|
-
)
|
|
62
|
-
assert_equal :SPLIT, reason
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def test_weighted_value_index_zero_is_not_split
|
|
66
|
-
reason = Quonfig::Reason.compute(
|
|
67
|
-
config: default_only_config,
|
|
68
|
-
conditional_value: DEFAULT_CV,
|
|
69
|
-
weighted_value_index: 0
|
|
70
|
-
)
|
|
71
|
-
assert_equal :DEFAULT, reason
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def test_default_when_only_always_true_criteria_and_no_targeting_rules
|
|
75
|
-
config = FakeConfig.new([FakeRow.new([ALWAYS_TRUE_CV], 0)])
|
|
76
|
-
reason = Quonfig::Reason.compute(config: config, conditional_value: ALWAYS_TRUE_CV)
|
|
77
|
-
assert_equal :DEFAULT, reason
|
|
78
|
-
end
|
|
79
|
-
end
|
data/test/test_rename.rb
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'test_helper'
|
|
4
|
-
|
|
5
|
-
class TestRename < Minitest::Test
|
|
6
|
-
def test_quonfig_module_is_defined
|
|
7
|
-
assert defined?(Quonfig), 'Quonfig module must be defined after rename'
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def test_reforge_module_is_gone
|
|
11
|
-
# NOTE: 'Reforge' intentionally not renamed — this test guards against the old constant leaking back in.
|
|
12
|
-
refute Object.const_defined?(:Reforge), 'Reforge constant must be removed after rename'
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def test_quonfig_options_exists
|
|
16
|
-
assert defined?(Quonfig::Options), 'Quonfig::Options must be defined'
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def test_quonfig_client_exists
|
|
20
|
-
assert defined?(Quonfig::Client), 'Quonfig::Client must be defined'
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def test_quonfig_sdk_key_env_var
|
|
24
|
-
# NOTE: the REFORGE_/PREFAB_ keys below are intentionally spelled out in string literals
|
|
25
|
-
# so the bulk rename tool does not touch them — we are asserting that the NEW env var name
|
|
26
|
-
# is the one the SDK reads.
|
|
27
|
-
old_key_a = 'REFORGE_' + 'BACKEND_SDK_KEY'
|
|
28
|
-
old_key_b = 'PREFAB_' + 'API_KEY'
|
|
29
|
-
original = ENV.to_h.slice('QUONFIG_BACKEND_SDK_KEY', old_key_a, old_key_b)
|
|
30
|
-
ENV.delete(old_key_a)
|
|
31
|
-
ENV.delete(old_key_b)
|
|
32
|
-
ENV['QUONFIG_BACKEND_SDK_KEY'] = 'quonfig-test-key-123'
|
|
33
|
-
options = Quonfig::Options.new
|
|
34
|
-
assert_equal 'quonfig-test-key-123', options.sdk_key
|
|
35
|
-
ensure
|
|
36
|
-
ENV.delete('QUONFIG_BACKEND_SDK_KEY')
|
|
37
|
-
original&.each { |k, v| ENV[k] = v }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def test_gemspec_file_is_quonfig
|
|
41
|
-
root = File.expand_path('..', __dir__)
|
|
42
|
-
assert File.exist?(File.join(root, 'quonfig.gemspec')),
|
|
43
|
-
'quonfig.gemspec must exist at repo root'
|
|
44
|
-
refute File.exist?(File.join(root, 'sdk-reforge.gemspec')),
|
|
45
|
-
'sdk-reforge.gemspec must be removed'
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def test_gemspec_name_is_quonfig
|
|
49
|
-
root = File.expand_path('..', __dir__)
|
|
50
|
-
spec = Gem::Specification.load(File.join(root, 'quonfig.gemspec'))
|
|
51
|
-
assert_equal 'quonfig', spec.name
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def test_lib_entrypoint_renamed
|
|
55
|
-
root = File.expand_path('..', __dir__)
|
|
56
|
-
assert File.exist?(File.join(root, 'lib', 'quonfig.rb')),
|
|
57
|
-
'lib/quonfig.rb must exist'
|
|
58
|
-
assert Dir.exist?(File.join(root, 'lib', 'quonfig')),
|
|
59
|
-
'lib/quonfig/ directory must exist'
|
|
60
|
-
refute File.exist?(File.join(root, 'lib', 'sdk-reforge.rb')),
|
|
61
|
-
'lib/sdk-reforge.rb must be removed'
|
|
62
|
-
refute Dir.exist?(File.join(root, 'lib', 'reforge')),
|
|
63
|
-
'lib/reforge/ directory must be removed'
|
|
64
|
-
end
|
|
65
|
-
end
|