quonfig 0.0.9 → 0.0.11

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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +4 -4
  4. data/lib/quonfig/evaluation_details.rb +60 -0
  5. data/lib/quonfig/options.rb +37 -16
  6. data/lib/quonfig/sse_config_client.rb +1 -1
  7. data/lib/quonfig/version.rb +5 -0
  8. data/lib/quonfig.rb +2 -1
  9. data/quonfig.gemspec +30 -163
  10. metadata +29 -182
  11. data/.claude/rules/constitution.md +0 -81
  12. data/.claude/rules/git-safety.md +0 -11
  13. data/.claude/rules/issue-tracking.md +0 -13
  14. data/.claude/rules/testing-workflow.md +0 -28
  15. data/.envrc.sample +0 -3
  16. data/.github/CODEOWNERS +0 -2
  17. data/.github/pull_request_template.md +0 -8
  18. data/.github/workflows/release.yml +0 -49
  19. data/.github/workflows/ruby.yml +0 -60
  20. data/.github/workflows/test.yaml +0 -40
  21. data/.rubocop.yml +0 -13
  22. data/.tool-versions +0 -1
  23. data/CLAUDE.md +0 -29
  24. data/CODEOWNERS +0 -1
  25. data/Gemfile +0 -26
  26. data/Gemfile.lock +0 -177
  27. data/Rakefile +0 -64
  28. data/VERSION +0 -1
  29. data/dev/allocation_stats +0 -60
  30. data/dev/benchmark +0 -40
  31. data/dev/console +0 -12
  32. data/dev/script_setup.rb +0 -18
  33. data/test/fixtures/datafile.json +0 -87
  34. data/test/integration/test_context_precedence.rb +0 -112
  35. data/test/integration/test_datadir_environment.rb +0 -54
  36. data/test/integration/test_dev_overrides.rb +0 -40
  37. data/test/integration/test_enabled.rb +0 -478
  38. data/test/integration/test_enabled_with_contexts.rb +0 -64
  39. data/test/integration/test_get.rb +0 -136
  40. data/test/integration/test_get_feature_flag.rb +0 -28
  41. data/test/integration/test_get_or_raise.rb +0 -60
  42. data/test/integration/test_get_weighted_values.rb +0 -34
  43. data/test/integration/test_helpers.rb +0 -667
  44. data/test/integration/test_helpers_test.rb +0 -73
  45. data/test/integration/test_post.rb +0 -44
  46. data/test/integration/test_telemetry.rb +0 -170
  47. data/test/support/common_helpers.rb +0 -106
  48. data/test/support/mock_base_client.rb +0 -27
  49. data/test/support/mock_config_loader.rb +0 -1
  50. data/test/test_bound_client.rb +0 -109
  51. data/test/test_caching_http_connection.rb +0 -218
  52. data/test/test_client.rb +0 -255
  53. data/test/test_client_network_mode.rb +0 -136
  54. data/test/test_client_telemetry.rb +0 -175
  55. data/test/test_config_loader.rb +0 -70
  56. data/test/test_context.rb +0 -139
  57. data/test/test_context_shape.rb +0 -37
  58. data/test/test_context_shape_aggregator.rb +0 -126
  59. data/test/test_datadir.rb +0 -203
  60. data/test/test_dev_context.rb +0 -163
  61. data/test/test_duration.rb +0 -37
  62. data/test/test_encryption.rb +0 -16
  63. data/test/test_evaluation_summaries_aggregator.rb +0 -180
  64. data/test/test_evaluator.rb +0 -285
  65. data/test/test_example_contexts_aggregator.rb +0 -119
  66. data/test/test_exponential_backoff.rb +0 -44
  67. data/test/test_fixed_size_hash.rb +0 -119
  68. data/test/test_helper.rb +0 -17
  69. data/test/test_http_connection.rb +0 -79
  70. data/test/test_internal_logger.rb +0 -34
  71. data/test/test_options.rb +0 -167
  72. data/test/test_rate_limit_cache.rb +0 -44
  73. data/test/test_reason.rb +0 -79
  74. data/test/test_rename.rb +0 -65
  75. data/test/test_resolver.rb +0 -291
  76. data/test/test_semantic_logger_filter.rb +0 -144
  77. data/test/test_semver.rb +0 -108
  78. data/test/test_should_log.rb +0 -186
  79. data/test/test_sse_config_client.rb +0 -297
  80. data/test/test_stdlib_formatter.rb +0 -195
  81. data/test/test_telemetry_reporter.rb +0 -209
  82. data/test/test_typed_getters.rb +0 -131
  83. data/test/test_types.rb +0 -141
  84. data/test/test_weighted_value_resolver.rb +0 -84
@@ -1,218 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- module Quonfig
6
- class CachingHttpConnectionTest < Minitest::Test
7
- def setup
8
- @uri = 'https://api.example.com'
9
- @sdk_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, @sdk_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, @sdk_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, @sdk_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, @sdk_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, @sdk_key)
164
- instance2 = CachingHttpConnection.new(@uri, @sdk_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, @sdk_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_client.rb DELETED
@@ -1,255 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- # Quonfig::Client wires the JSON stack: ConfigStore + Evaluator + Resolver
6
- # (introduced in qfg-dk6.4-9). These tests drive Client through an injected
7
- # ConfigStore so they never touch the network or the filesystem. The legacy
8
- # protobuf ConfigClient/ConfigResolver path was removed in qfg-dk6.32.
9
- class TestClient < Minitest::Test
10
- CONFIG_KEY = 'my.flag'
11
-
12
- # ---- Test fixtures -----------------------------------------------------
13
-
14
- # Plain ConfigResponse-shaped hash (mirrors what
15
- # Quonfig::Datadir.to_config_response and IntegrationTestHelpers emit).
16
- def make_config(key:, value:, type: 'string', criteria: nil)
17
- {
18
- 'id' => '1',
19
- 'key' => key,
20
- 'type' => 'config',
21
- 'valueType' => type,
22
- 'sendToClientSdk' => false,
23
- 'default' => {
24
- 'rules' => [
25
- {
26
- 'criteria' => criteria || [{ 'operator' => 'ALWAYS_TRUE' }],
27
- 'value' => { 'type' => type, 'value' => value }
28
- }
29
- ]
30
- },
31
- 'environment' => nil
32
- }
33
- end
34
-
35
- def store_with(*configs)
36
- store = Quonfig::ConfigStore.new
37
- configs.each { |c| store.set(c['key'], c) }
38
- store
39
- end
40
-
41
- def client_with(store, **options)
42
- Quonfig::Client.new(Quonfig::Options.new(**options), store: store)
43
- end
44
-
45
- # ---- Construction ------------------------------------------------------
46
-
47
- def test_constructor_accepts_options_object
48
- client = client_with(Quonfig::ConfigStore.new)
49
- assert_kind_of Quonfig::Options, client.options
50
- end
51
-
52
- def test_constructor_wires_resolver_and_evaluator
53
- store = Quonfig::ConfigStore.new
54
- client = Quonfig::Client.new(Quonfig::Options.new, store: store)
55
-
56
- assert_kind_of Quonfig::Resolver, client.resolver
57
- assert_kind_of Quonfig::Evaluator, client.evaluator
58
- assert_same store, client.store,
59
- 'Client must use the injected ConfigStore instance'
60
- end
61
-
62
- def test_instance_hash_is_unique_per_client
63
- a = client_with(Quonfig::ConfigStore.new)
64
- b = client_with(Quonfig::ConfigStore.new)
65
- refute_equal a.instance_hash, b.instance_hash
66
- end
67
-
68
- # ---- get returns coerced JSON values, not PrefabProto ------------------
69
-
70
- def test_get_returns_string_value
71
- store = store_with(make_config(key: CONFIG_KEY, value: 'hello'))
72
- assert_equal 'hello', client_with(store).get(CONFIG_KEY)
73
- end
74
-
75
- def test_get_returns_int_value
76
- store = store_with(make_config(key: CONFIG_KEY, value: 42, type: 'int'))
77
- assert_equal 42, client_with(store).get(CONFIG_KEY)
78
- end
79
-
80
- def test_get_returns_bool_value
81
- store = store_with(make_config(key: CONFIG_KEY, value: true, type: 'bool'))
82
- assert_equal true, client_with(store).get(CONFIG_KEY)
83
- end
84
-
85
- def test_get_returned_value_is_not_a_prefab_proto
86
- store = store_with(make_config(key: CONFIG_KEY, value: 'hello'))
87
- value = client_with(store).get(CONFIG_KEY)
88
-
89
- refute value.respond_to?(:string_list),
90
- 'Client#get must return a plain Ruby value, not a PrefabProto::ConfigValue'
91
- refute value.is_a?(Hash),
92
- 'Client#get must unwrap to the coerced Ruby value, not the JSON Value hash'
93
- end
94
-
95
- # ---- Missing key handling ---------------------------------------------
96
-
97
- def test_get_returns_explicit_default_when_key_missing
98
- store = Quonfig::ConfigStore.new
99
- assert_equal 'fallback', client_with(store).get('nope', 'fallback')
100
- end
101
-
102
- def test_get_raises_missing_default_error_by_default
103
- store = Quonfig::ConfigStore.new
104
- assert_raises(Quonfig::Errors::MissingDefaultError) do
105
- client_with(store).get('nope')
106
- end
107
- end
108
-
109
- def test_get_returns_nil_when_on_no_default_is_return_nil
110
- store = Quonfig::ConfigStore.new
111
- client = client_with(store, on_no_default: Quonfig::Options::ON_NO_DEFAULT::RETURN_NIL)
112
- assert_nil client.get('nope')
113
- end
114
-
115
- # ---- enabled? --------------------------------------------------------
116
-
117
- def test_enabled_returns_true_when_value_is_true
118
- store = store_with(make_config(key: CONFIG_KEY, value: true, type: 'bool'))
119
- assert client_with(store).enabled?(CONFIG_KEY)
120
- end
121
-
122
- def test_enabled_returns_false_when_value_is_false
123
- store = store_with(make_config(key: CONFIG_KEY, value: false, type: 'bool'))
124
- refute client_with(store).enabled?(CONFIG_KEY)
125
- end
126
-
127
- def test_enabled_returns_false_for_missing_key
128
- store = Quonfig::ConfigStore.new
129
- refute client_with(store).enabled?('nope')
130
- end
131
-
132
- # ---- defined? + keys --------------------------------------------------
133
-
134
- def test_defined_returns_true_for_known_key
135
- store = store_with(make_config(key: CONFIG_KEY, value: 'x'))
136
- assert client_with(store).defined?(CONFIG_KEY)
137
- end
138
-
139
- def test_defined_returns_false_for_unknown_key
140
- store = store_with(make_config(key: CONFIG_KEY, value: 'x'))
141
- refute client_with(store).defined?('absent')
142
- end
143
-
144
- def test_keys_returns_store_keys
145
- store = store_with(
146
- make_config(key: 'a', value: '1'),
147
- make_config(key: 'b', value: '2')
148
- )
149
- assert_equal %w[a b].sort, client_with(store).keys.sort
150
- end
151
-
152
- # ---- Context: jit context is plain Hash, not PrefabProto::Context ----
153
-
154
- def test_get_accepts_jit_context_as_plain_hash
155
- cfg = make_config(
156
- key: CONFIG_KEY,
157
- value: 'matched',
158
- criteria: [{
159
- 'operator' => 'PROP_IS_ONE_OF',
160
- 'propertyName' => 'user.role',
161
- 'valueToMatch' => { 'type' => 'string_list', 'value' => ['admin'] }
162
- }]
163
- )
164
- store = store_with(cfg)
165
-
166
- result = client_with(store).get(CONFIG_KEY, 'fallback', user: { 'role' => 'admin' })
167
-
168
- assert_equal 'matched', result
169
- end
170
-
171
- def test_with_context_returns_bound_client
172
- bound = client_with(Quonfig::ConfigStore.new).with_context(user: { 'key' => '1' })
173
- assert_kind_of Quonfig::BoundClient, bound
174
- assert_equal({ user: { 'key' => '1' } }, bound.context)
175
- end
176
-
177
- def test_in_context_yields_bound_client_when_block_given
178
- yielded = nil
179
- client_with(Quonfig::ConfigStore.new).in_context(user: { 'key' => '1' }) do |bound|
180
- yielded = bound
181
- end
182
-
183
- assert_kind_of Quonfig::BoundClient, yielded
184
- assert_equal({ user: { 'key' => '1' } }, yielded.context)
185
- end
186
-
187
- def test_global_context_is_merged_into_jit_context
188
- cfg = make_config(
189
- key: CONFIG_KEY,
190
- value: 'admin-value',
191
- criteria: [{
192
- 'operator' => 'PROP_IS_ONE_OF',
193
- 'propertyName' => 'user.role',
194
- 'valueToMatch' => { 'type' => 'string_list', 'value' => ['admin'] }
195
- }]
196
- )
197
- store = store_with(cfg)
198
- client = Quonfig::Client.new(
199
- Quonfig::Options.new(global_context: { user: { 'role' => 'admin' } }),
200
- store: store
201
- )
202
-
203
- assert_equal 'admin-value', client.get(CONFIG_KEY, 'fallback')
204
- end
205
-
206
- def test_jit_context_overrides_global_context_at_the_property_level
207
- cfg = make_config(
208
- key: CONFIG_KEY,
209
- value: 'jit-value',
210
- criteria: [{
211
- 'operator' => 'PROP_IS_ONE_OF',
212
- 'propertyName' => 'user.role',
213
- 'valueToMatch' => { 'type' => 'string_list', 'value' => ['user'] }
214
- }]
215
- )
216
- store = store_with(cfg)
217
- client = Quonfig::Client.new(
218
- Quonfig::Options.new(global_context: { user: { 'role' => 'admin' } }),
219
- store: store
220
- )
221
-
222
- # jit overrides global for this single property; keys unique to global preserved
223
- assert_equal 'jit-value', client.get(CONFIG_KEY, 'fallback', user: { 'role' => 'user' })
224
- end
225
-
226
- def test_normalize_context_rejects_non_hash_jit_context
227
- store = Quonfig::ConfigStore.new
228
- assert_raises(ArgumentError) do
229
- client_with(store).get('nope', 'fallback', 'not-a-hash')
230
- end
231
- end
232
-
233
- # ---- Misc -------------------------------------------------------------
234
-
235
- def test_stop_is_a_noop
236
- client_with(Quonfig::ConfigStore.new).stop
237
- pass
238
- end
239
-
240
- def test_inspect_includes_environment
241
- client = client_with(Quonfig::ConfigStore.new, environment: 'Production')
242
- assert_match(/environment="Production"/, client.inspect)
243
- end
244
-
245
- def test_no_prefab_proto_in_lib_quonfig_source
246
- # qfg-dk6.32: scrub PrefabProto from the runtime lib path.
247
- lib_dir = File.expand_path('../lib/quonfig', __dir__)
248
- offenders = Dir.glob(File.join(lib_dir, '**/*.rb')).select do |path|
249
- File.read(path).match?(/PrefabProto/)
250
- end
251
-
252
- assert_empty offenders,
253
- "lib/quonfig still references PrefabProto:\n#{offenders.join("\n")}"
254
- end
255
- end
@@ -1,136 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
- require 'webrick'
5
- require 'json'
6
-
7
- # Verifies Client#initialize (qfg-s7h) wires HTTP fetch + ConfigStore together
8
- # so `Quonfig.get(...)` / `Quonfig.enabled?(...)` return real values — not the
9
- # defaults — when only an `sdk_key:` + `api_urls:` are supplied. This is the
10
- # regression test for the P0 documented in test-ruby/FRICTION.md where
11
- # network-mode was accepted but silently ignored in v0.0.3.
12
- class TestClientNetworkMode < Minitest::Test
13
- PORT = 18_094
14
-
15
- SAMPLE_CONFIG = {
16
- 'id' => 'c1',
17
- 'key' => 'log-levels.test-ruby',
18
- 'type' => 'log_level',
19
- 'valueType' => 'log_level',
20
- 'sendToClientSdk' => false,
21
- 'default' => {
22
- 'rules' => [
23
- {
24
- 'criteria' => [{ 'operator' => 'ALWAYS_TRUE' }],
25
- 'value' => { 'type' => 'log_level', 'value' => 'WARN' }
26
- }
27
- ]
28
- }
29
- }.freeze
30
-
31
- def setup
32
- super
33
- @server = nil
34
- @fetch_count = 0
35
- end
36
-
37
- def teardown
38
- @server&.shutdown
39
- super
40
- end
41
-
42
- def start_server
43
- log = WEBrick::Log.new(StringIO.new)
44
- @server = WEBrick::HTTPServer.new(
45
- Port: PORT, Logger: log, AccessLog: []
46
- )
47
- @server.mount_proc '/api/v2/configs' do |_req, res|
48
- @fetch_count += 1
49
- res.status = 200
50
- res['Content-Type'] = 'application/json'
51
- res['ETag'] = "v#{@fetch_count}"
52
- res.body = JSON.generate(
53
- 'configs' => [SAMPLE_CONFIG],
54
- 'meta' => { 'version' => "v#{@fetch_count}", 'environment' => 'dev' }
55
- )
56
- end
57
- Thread.new { @server.start }
58
- # Wait for server to be ready.
59
- 50.times do
60
- break if tcp_open?
61
- sleep 0.05
62
- end
63
- end
64
-
65
- def tcp_open?
66
- require 'socket'
67
- TCPSocket.new('127.0.0.1', PORT).tap(&:close)
68
- true
69
- rescue StandardError
70
- false
71
- end
72
-
73
- def test_initialize_fetches_configs_from_api_urls_and_populates_store
74
- start_server
75
-
76
- client = Quonfig::Client.new(
77
- sdk_key: 'test-key',
78
- api_urls: ["http://127.0.0.1:#{PORT}"],
79
- enable_sse: false,
80
- enable_polling: false
81
- )
82
-
83
- assert_equal 1, @fetch_count, 'expected exactly one HTTP fetch during init'
84
- assert_includes client.keys, 'log-levels.test-ruby'
85
- assert_equal 'WARN', client.get('log-levels.test-ruby', 'default')
86
- ensure
87
- client&.stop
88
- end
89
-
90
- def test_initialize_raises_on_fetch_failure_by_default
91
- # No server started -> connection refused everywhere
92
- assert_raises(RuntimeError, Quonfig::Errors::InitializationTimeoutError) do
93
- Quonfig::Client.new(
94
- sdk_key: 'test-key',
95
- api_urls: ['http://127.0.0.1:1'], # almost certainly unreachable
96
- enable_sse: false,
97
- enable_polling: false,
98
- initialization_timeout_sec: 2
99
- )
100
- end
101
- end
102
-
103
- def test_initialize_returns_empty_store_when_on_init_failure_is_return
104
- client = Quonfig::Client.new(
105
- sdk_key: 'test-key',
106
- api_urls: ['http://127.0.0.1:1'],
107
- enable_sse: false,
108
- enable_polling: false,
109
- initialization_timeout_sec: 2,
110
- on_init_failure: Quonfig::Options::ON_INITIALIZATION_FAILURE::RETURN
111
- )
112
-
113
- assert_empty client.keys
114
- assert_logged [/Initialization did not complete cleanly/]
115
- ensure
116
- client&.stop
117
- end
118
-
119
- def test_initialize_skips_network_when_store_injected
120
- # store: passed -> Client should not try any I/O. Unreachable URL must
121
- # be fine when a store is injected.
122
- store = Quonfig::ConfigStore.new
123
- client = Quonfig::Client.new(
124
- Quonfig::Options.new(
125
- sdk_key: 'test-key',
126
- api_urls: ['http://127.0.0.1:1'],
127
- enable_sse: false,
128
- enable_polling: false
129
- ),
130
- store: store
131
- )
132
- assert_same store, client.store
133
- ensure
134
- client&.stop
135
- end
136
- end