quonfig 0.0.6 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/VERSION +1 -1
  4. data/lib/quonfig/bound_client.rb +26 -0
  5. data/lib/quonfig/client.rb +212 -3
  6. data/lib/quonfig/context.rb +10 -1
  7. data/lib/quonfig/datadir.rb +2 -4
  8. data/lib/quonfig/dev_context.rb +41 -0
  9. data/lib/quonfig/errors/decryption_error.rb +20 -0
  10. data/lib/quonfig/errors/env_var_parse_error.rb +8 -1
  11. data/lib/quonfig/errors/invalid_environment_error.rb +19 -0
  12. data/lib/quonfig/errors/missing_environment_error.rb +18 -0
  13. data/lib/quonfig/evaluator.rb +84 -3
  14. data/lib/quonfig/http_connection.rb +1 -1
  15. data/lib/quonfig/options.rb +4 -1
  16. data/lib/quonfig/resolver.rb +215 -2
  17. data/lib/quonfig/stdlib_formatter.rb +95 -0
  18. data/lib/quonfig/telemetry/context_shape.rb +33 -0
  19. data/lib/quonfig/telemetry/context_shape_aggregator.rb +82 -0
  20. data/lib/quonfig/telemetry/evaluation_summaries_aggregator.rb +119 -0
  21. data/lib/quonfig/telemetry/example_contexts_aggregator.rb +101 -0
  22. data/lib/quonfig/telemetry/telemetry_reporter.rb +212 -0
  23. data/lib/quonfig.rb +10 -0
  24. data/quonfig.gemspec +23 -4
  25. data/test/integration/test_context_precedence.rb +35 -117
  26. data/test/integration/test_datadir_environment.rb +15 -37
  27. data/test/integration/test_dev_overrides.rb +40 -0
  28. data/test/integration/test_enabled.rb +157 -463
  29. data/test/integration/test_enabled_with_contexts.rb +19 -49
  30. data/test/integration/test_get.rb +43 -131
  31. data/test/integration/test_get_feature_flag.rb +7 -13
  32. data/test/integration/test_get_or_raise.rb +19 -45
  33. data/test/integration/test_get_weighted_values.rb +9 -4
  34. data/test/integration/test_helpers.rb +532 -4
  35. data/test/integration/test_post.rb +15 -5
  36. data/test/integration/test_telemetry.rb +77 -21
  37. data/test/test_client_telemetry.rb +175 -0
  38. data/test/test_context.rb +4 -1
  39. data/test/test_context_shape.rb +37 -0
  40. data/test/test_context_shape_aggregator.rb +126 -0
  41. data/test/test_datadir.rb +6 -2
  42. data/test/test_dev_context.rb +163 -0
  43. data/test/test_evaluation_summaries_aggregator.rb +180 -0
  44. data/test/test_example_contexts_aggregator.rb +119 -0
  45. data/test/test_http_connection.rb +1 -1
  46. data/test/test_resolver.rb +149 -2
  47. data/test/test_should_log.rb +186 -0
  48. data/test/test_stdlib_formatter.rb +195 -0
  49. data/test/test_telemetry_reporter.rb +209 -0
  50. metadata +22 -3
  51. data/scripts/generate_integration_tests.rb +0 -362
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quonfig
4
+ module Telemetry
5
+ # Owns the background thread that periodically drains the context
6
+ # aggregators and POSTs a JSON telemetry batch to
7
+ # +<telemetry_destination>/api/v1/telemetry/+.
8
+ #
9
+ # Wire shape matches api-telemetry's TelemetryEventsSchema:
10
+ #
11
+ # {
12
+ # "instanceHash": "...",
13
+ # "events": [
14
+ # { "summaries": { "start": ..., "end": ..., "summaries": [...] } },
15
+ # { "contextShapes": { "shapes": [...] } },
16
+ # { "exampleContexts": { "examples": [...] } }
17
+ # ]
18
+ # }
19
+ #
20
+ # Auth is HTTP Basic with username "1" and the SDK key as password
21
+ # (matching sdk-node and sdk-go). The +X-Quonfig-SDK-Version+ header
22
+ # carries the +ruby-<VERSION>+ identifier.
23
+ class TelemetryReporter
24
+ LOG = Quonfig::InternalLogger.new(self)
25
+
26
+ DEFAULT_INITIAL_DELAY_SECONDS = 8
27
+ DEFAULT_MAX_DELAY_SECONDS = 600
28
+
29
+ def initialize(options:, instance_hash:,
30
+ context_shape_aggregator: nil,
31
+ example_contexts_aggregator: nil,
32
+ evaluation_summaries_aggregator: nil,
33
+ sync_interval: nil,
34
+ http_connection: nil)
35
+ @options = options
36
+ @instance_hash = instance_hash
37
+ @sdk_key = options.sdk_key
38
+ @telemetry_destination = options.telemetry_destination
39
+ @context_shape_aggregator = context_shape_aggregator
40
+ @example_contexts_aggregator = example_contexts_aggregator
41
+ @evaluation_summaries_aggregator = evaluation_summaries_aggregator
42
+ @http_connection = http_connection
43
+ @sync_interval = calculate_sync_interval(sync_interval)
44
+ @stopped = Concurrent::AtomicBoolean.new(false)
45
+ @thread = nil
46
+ @at_exit_registered = false
47
+ end
48
+
49
+ def enabled?
50
+ return false if @sdk_key.nil? || @sdk_key.to_s.empty?
51
+ return false if @telemetry_destination.nil? || @telemetry_destination.to_s.empty?
52
+
53
+ !@context_shape_aggregator.nil? ||
54
+ !@example_contexts_aggregator.nil? ||
55
+ !@evaluation_summaries_aggregator.nil?
56
+ end
57
+
58
+ # Record a context across the context-driven aggregators. Evaluation
59
+ # summaries are recorded separately via
60
+ # +record_evaluation(...)+ since they require the evaluation result.
61
+ def record(context)
62
+ return if context.nil?
63
+
64
+ @context_shape_aggregator&.push(context)
65
+ @example_contexts_aggregator&.record(context)
66
+ end
67
+
68
+ def record_evaluation(**kwargs)
69
+ @evaluation_summaries_aggregator&.record(**kwargs)
70
+ end
71
+
72
+ def start
73
+ return if @thread&.alive?
74
+ return unless enabled?
75
+
76
+ @stopped.make_false
77
+ register_at_exit_handler
78
+ @thread = Thread.new do
79
+ Thread.current.name = 'quonfig-telemetry-reporter'
80
+ LOG.debug "Telemetry reporter started instance_hash=#{@instance_hash} destination=#{@telemetry_destination}"
81
+
82
+ until @stopped.true?
83
+ begin
84
+ sleep_duration = @sync_interval.call
85
+ slept = 0.0
86
+ step = 0.5
87
+ while slept < sleep_duration && !@stopped.true?
88
+ sleep([step, sleep_duration - slept].min)
89
+ slept += step
90
+ end
91
+ break if @stopped.true?
92
+
93
+ sync
94
+ rescue StandardError => e
95
+ LOG.warn "[quonfig] Telemetry reporter error: #{e.class}: #{e.message}"
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ def stop
102
+ @stopped.make_true
103
+ thread = @thread
104
+ @thread = nil
105
+ thread&.wakeup if thread&.alive?
106
+ # Final drain attempt on stop so tests / short-lived processes
107
+ # don't silently drop pending telemetry.
108
+ begin
109
+ sync
110
+ rescue StandardError => e
111
+ LOG.debug "[quonfig] Final telemetry sync failed: #{e.class}: #{e.message}"
112
+ end
113
+ end
114
+
115
+ # Drain all aggregators and POST the batch. Public so tests can
116
+ # trigger a sync without waiting for the background loop.
117
+ def sync
118
+ events = []
119
+ if (summaries_event = @evaluation_summaries_aggregator&.drain_event)
120
+ events << summaries_event
121
+ end
122
+ if (shape_event = @context_shape_aggregator&.drain_event)
123
+ events << shape_event
124
+ end
125
+ if (example_event = @example_contexts_aggregator&.drain_event)
126
+ events << example_event
127
+ end
128
+
129
+ return if events.empty?
130
+
131
+ payload = {
132
+ 'instanceHash' => @instance_hash,
133
+ 'events' => events
134
+ }
135
+
136
+ post(payload)
137
+ end
138
+
139
+ # Visible for tests.
140
+ def at_exit_registered?
141
+ @at_exit_registered
142
+ end
143
+
144
+ private
145
+
146
+ # Rails / Passenger / Puma workers often terminate via SIGTERM without
147
+ # a chance to call Client#stop. Register a Kernel.at_exit hook on
148
+ # first start so the in-flight batch still gets flushed.
149
+ def register_at_exit_handler
150
+ return if @at_exit_registered
151
+
152
+ Kernel.at_exit { final_drain_on_exit }
153
+ @at_exit_registered = true
154
+ end
155
+
156
+ # Wait this long for the background reporter thread to exit before
157
+ # giving up. Bounded so a thread blocked on a dead telemetry endpoint
158
+ # can't hang process exit.
159
+ AT_EXIT_THREAD_JOIN_TIMEOUT_SECONDS = 1.0
160
+
161
+ # Idempotent final drain. Safe to call after #stop has already
162
+ # drained: aggregators return nil when empty and #sync becomes a
163
+ # no-op. Bounded so a stuck reporter thread or dead telemetry
164
+ # endpoint can't hang process exit.
165
+ def final_drain_on_exit
166
+ @stopped.make_true
167
+ thread = @thread
168
+ @thread = nil
169
+ if thread&.alive?
170
+ thread.wakeup
171
+ thread.join(AT_EXIT_THREAD_JOIN_TIMEOUT_SECONDS)
172
+ end
173
+ sync
174
+ rescue StandardError => e
175
+ LOG.debug "[quonfig] at_exit telemetry drain failed: #{e.class}: #{e.message}"
176
+ end
177
+
178
+ def post(payload)
179
+ conn = http_connection
180
+ return if conn.nil?
181
+
182
+ response = conn.post('/api/v1/telemetry/', payload)
183
+ status = response.respond_to?(:status) ? response.status : nil
184
+ if status && status >= 400
185
+ LOG.warn "[quonfig] Telemetry POST failed: #{status}"
186
+ else
187
+ LOG.debug "[quonfig] Telemetry POST ok: events=#{payload['events'].size}"
188
+ end
189
+ response
190
+ end
191
+
192
+ def http_connection
193
+ @http_connection ||= begin
194
+ return nil if @sdk_key.nil? || @telemetry_destination.nil?
195
+
196
+ Quonfig::HttpConnection.new(@telemetry_destination, @sdk_key)
197
+ end
198
+ end
199
+
200
+ def calculate_sync_interval(sync_interval)
201
+ return proc { sync_interval } if sync_interval.is_a?(Numeric)
202
+ return sync_interval if sync_interval.respond_to?(:call)
203
+
204
+ Quonfig::ExponentialBackoff.new(
205
+ initial_delay: DEFAULT_INITIAL_DELAY_SECONDS,
206
+ max_delay: DEFAULT_MAX_DELAY_SECONDS,
207
+ multiplier: 1.5
208
+ )
209
+ end
210
+ end
211
+ end
212
+ end
data/lib/quonfig.rb CHANGED
@@ -25,6 +25,7 @@ require 'quonfig/error'
25
25
  require 'quonfig/duration'
26
26
  require 'quonfig/reason'
27
27
  require 'quonfig/evaluation'
28
+ require 'quonfig/evaluation_details'
28
29
  require 'quonfig/encryption'
29
30
  require 'quonfig/exponential_backoff'
30
31
  require 'quonfig/periodic_sync'
@@ -35,7 +36,11 @@ require 'quonfig/errors/env_var_parse_error'
35
36
  require 'quonfig/errors/missing_env_var_error'
36
37
  require 'quonfig/errors/type_mismatch_error'
37
38
  require 'quonfig/errors/uninitialized_error'
39
+ require 'quonfig/errors/decryption_error'
40
+ require 'quonfig/errors/missing_environment_error'
41
+ require 'quonfig/errors/invalid_environment_error'
38
42
  require 'quonfig/options'
43
+ require 'quonfig/dev_context'
39
44
  require 'quonfig/rate_limit_cache'
40
45
  require 'quonfig/weighted_value_resolver'
41
46
  require 'quonfig/config_store'
@@ -48,6 +53,11 @@ require 'quonfig/sse_config_client'
48
53
  require 'quonfig/http_connection'
49
54
  require 'quonfig/caching_http_connection'
50
55
  require 'quonfig/context'
56
+ require 'quonfig/telemetry/context_shape'
57
+ require 'quonfig/telemetry/context_shape_aggregator'
58
+ require 'quonfig/telemetry/example_contexts_aggregator'
59
+ require 'quonfig/telemetry/evaluation_summaries_aggregator'
60
+ require 'quonfig/telemetry/telemetry_reporter'
51
61
  require 'quonfig/client'
52
62
  require 'quonfig/bound_client'
53
63
  require 'quonfig/semantic_logger_filter'
data/quonfig.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: quonfig 0.0.2 ruby lib
5
+ # stub: quonfig 0.0.9 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "quonfig".freeze
9
- s.version = File.read(File.expand_path("VERSION", __dir__)).strip
9
+ s.version = "0.0.9".freeze
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Jeff Dwyer".freeze]
14
- s.date = "2026-04-22"
14
+ s.date = "2026-04-27"
15
15
  s.description = "Quonfig \u2014 feature flags and live config, stored as files in git.".freeze
16
16
  s.email = "jeff@quonfig.com".freeze
17
17
  s.extra_rdoc_files = [
@@ -54,14 +54,18 @@ Gem::Specification.new do |s|
54
54
  "lib/quonfig/config_store.rb",
55
55
  "lib/quonfig/context.rb",
56
56
  "lib/quonfig/datadir.rb",
57
+ "lib/quonfig/dev_context.rb",
57
58
  "lib/quonfig/duration.rb",
58
59
  "lib/quonfig/encryption.rb",
59
60
  "lib/quonfig/error.rb",
61
+ "lib/quonfig/errors/decryption_error.rb",
60
62
  "lib/quonfig/errors/env_var_parse_error.rb",
61
63
  "lib/quonfig/errors/initialization_timeout_error.rb",
64
+ "lib/quonfig/errors/invalid_environment_error.rb",
62
65
  "lib/quonfig/errors/invalid_sdk_key_error.rb",
63
66
  "lib/quonfig/errors/missing_default_error.rb",
64
67
  "lib/quonfig/errors/missing_env_var_error.rb",
68
+ "lib/quonfig/errors/missing_environment_error.rb",
65
69
  "lib/quonfig/errors/type_mismatch_error.rb",
66
70
  "lib/quonfig/errors/uninitialized_error.rb",
67
71
  "lib/quonfig/evaluation.rb",
@@ -80,14 +84,20 @@ Gem::Specification.new do |s|
80
84
  "lib/quonfig/semantic_logger_filter.rb",
81
85
  "lib/quonfig/semver.rb",
82
86
  "lib/quonfig/sse_config_client.rb",
87
+ "lib/quonfig/stdlib_formatter.rb",
88
+ "lib/quonfig/telemetry/context_shape.rb",
89
+ "lib/quonfig/telemetry/context_shape_aggregator.rb",
90
+ "lib/quonfig/telemetry/evaluation_summaries_aggregator.rb",
91
+ "lib/quonfig/telemetry/example_contexts_aggregator.rb",
92
+ "lib/quonfig/telemetry/telemetry_reporter.rb",
83
93
  "lib/quonfig/time_helpers.rb",
84
94
  "lib/quonfig/types.rb",
85
95
  "lib/quonfig/weighted_value_resolver.rb",
86
96
  "quonfig.gemspec",
87
- "scripts/generate_integration_tests.rb",
88
97
  "test/fixtures/datafile.json",
89
98
  "test/integration/test_context_precedence.rb",
90
99
  "test/integration/test_datadir_environment.rb",
100
+ "test/integration/test_dev_overrides.rb",
91
101
  "test/integration/test_enabled.rb",
92
102
  "test/integration/test_enabled_with_contexts.rb",
93
103
  "test/integration/test_get.rb",
@@ -105,12 +115,18 @@ Gem::Specification.new do |s|
105
115
  "test/test_caching_http_connection.rb",
106
116
  "test/test_client.rb",
107
117
  "test/test_client_network_mode.rb",
118
+ "test/test_client_telemetry.rb",
108
119
  "test/test_config_loader.rb",
109
120
  "test/test_context.rb",
121
+ "test/test_context_shape.rb",
122
+ "test/test_context_shape_aggregator.rb",
110
123
  "test/test_datadir.rb",
124
+ "test/test_dev_context.rb",
111
125
  "test/test_duration.rb",
112
126
  "test/test_encryption.rb",
127
+ "test/test_evaluation_summaries_aggregator.rb",
113
128
  "test/test_evaluator.rb",
129
+ "test/test_example_contexts_aggregator.rb",
114
130
  "test/test_exponential_backoff.rb",
115
131
  "test/test_fixed_size_hash.rb",
116
132
  "test/test_helper.rb",
@@ -123,7 +139,10 @@ Gem::Specification.new do |s|
123
139
  "test/test_resolver.rb",
124
140
  "test/test_semantic_logger_filter.rb",
125
141
  "test/test_semver.rb",
142
+ "test/test_should_log.rb",
126
143
  "test/test_sse_config_client.rb",
144
+ "test/test_stdlib_formatter.rb",
145
+ "test/test_telemetry_reporter.rb",
127
146
  "test/test_typed_getters.rb",
128
147
  "test/test_types.rb",
129
148
  "test/test_weighted_value_resolver.rb"
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  #
3
3
  # AUTO-GENERATED from integration-test-data/tests/eval/context_precedence.yaml.
4
- # Regenerate with `bundle exec ruby scripts/generate_integration_tests.rb`.
4
+ # Regenerate with:
5
+ # cd integration-test-data/generators && npm run generate -- --target=ruby
6
+ # Source: integration-test-data/generators/src/targets/ruby.ts
5
7
  # Do NOT edit by hand — changes will be overwritten.
6
8
 
7
9
  require 'test_helper'
@@ -14,181 +16,97 @@ class TestContextPrecedence < Minitest::Test
14
16
 
15
17
  # returns the correct `flag` value using the global context (1)
16
18
  def test_returns_the_correct_flag_value_using_the_global_context_1
17
- begin
18
- resolver = IntegrationTestHelpers.build_resolver(@store)
19
- IntegrationTestHelpers.assert_resolved(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "verified"}}, true)
20
- rescue Exception => e
21
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
22
- end
19
+ resolver = IntegrationTestHelpers.build_resolver(@store)
20
+ IntegrationTestHelpers.assert_enabled(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "verified"}}, true)
23
21
  end
24
22
 
25
23
  # returns the correct `flag` value using the global context (2)
26
24
  def test_returns_the_correct_flag_value_using_the_global_context_2
27
- begin
28
- resolver = IntegrationTestHelpers.build_resolver(@store)
29
- IntegrationTestHelpers.assert_resolved(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "?"}}, false)
30
- rescue Exception => e
31
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
32
- end
25
+ resolver = IntegrationTestHelpers.build_resolver(@store)
26
+ IntegrationTestHelpers.assert_enabled(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "?"}}, false)
33
27
  end
34
28
 
35
29
  # returns the correct `flag` value when local context clobbers global context (1)
36
30
  def test_returns_the_correct_flag_value_when_local_context_clobbers_global_context_1
37
- begin
38
- resolver = IntegrationTestHelpers.build_resolver(@store)
39
- IntegrationTestHelpers.assert_resolved(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "verified"}}, true)
40
- rescue Exception => e
41
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
42
- end
31
+ resolver = IntegrationTestHelpers.build_resolver(@store)
32
+ IntegrationTestHelpers.assert_enabled(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "verified"}}, true)
43
33
  end
44
34
 
45
35
  # returns the correct `flag` value when local context clobbers global context (2)
46
36
  def test_returns_the_correct_flag_value_when_local_context_clobbers_global_context_2
47
- begin
48
- resolver = IntegrationTestHelpers.build_resolver(@store)
49
- IntegrationTestHelpers.assert_resolved(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "?"}}, false)
50
- rescue Exception => e
51
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
52
- end
37
+ resolver = IntegrationTestHelpers.build_resolver(@store)
38
+ IntegrationTestHelpers.assert_enabled(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "?"}}, false)
53
39
  end
54
40
 
55
41
  # returns the correct `flag` value when block context clobbers global context (1)
56
42
  def test_returns_the_correct_flag_value_when_block_context_clobbers_global_context_1
57
- begin
58
- resolver = IntegrationTestHelpers.build_resolver(@store)
59
- IntegrationTestHelpers.assert_resolved(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "?"}}, false)
60
- rescue Exception => e
61
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
62
- end
43
+ resolver = IntegrationTestHelpers.build_resolver(@store)
44
+ IntegrationTestHelpers.assert_enabled(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "?"}}, false)
63
45
  end
64
46
 
65
47
  # returns the correct `flag` value when block context clobbers global context (2)
66
48
  def test_returns_the_correct_flag_value_when_block_context_clobbers_global_context_2
67
- begin
68
- resolver = IntegrationTestHelpers.build_resolver(@store)
69
- IntegrationTestHelpers.assert_resolved(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "verified"}}, true)
70
- rescue Exception => e
71
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
72
- end
49
+ resolver = IntegrationTestHelpers.build_resolver(@store)
50
+ IntegrationTestHelpers.assert_enabled(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "verified"}}, true)
73
51
  end
74
52
 
75
53
  # returns the correct `flag` value when local context clobbers block context (1)
76
54
  def test_returns_the_correct_flag_value_when_local_context_clobbers_block_context_1
77
- begin
78
- resolver = IntegrationTestHelpers.build_resolver(@store)
79
- IntegrationTestHelpers.assert_resolved(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "?"}}, false)
80
- rescue Exception => e
81
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
82
- end
55
+ resolver = IntegrationTestHelpers.build_resolver(@store)
56
+ IntegrationTestHelpers.assert_enabled(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "?"}}, false)
83
57
  end
84
58
 
85
59
  # returns the correct `flag` value when local context clobbers block context (2)
86
60
  def test_returns_the_correct_flag_value_when_local_context_clobbers_block_context_2
87
- begin
88
- resolver = IntegrationTestHelpers.build_resolver(@store)
89
- IntegrationTestHelpers.assert_resolved(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "verified"}}, true)
90
- rescue Exception => e
91
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
92
- end
61
+ resolver = IntegrationTestHelpers.build_resolver(@store)
62
+ IntegrationTestHelpers.assert_enabled(resolver, "mixed.case.property.name", {"user" => {"isHuman" => "verified"}}, true)
93
63
  end
94
64
 
95
65
  # returns the correct `get` value using the global context (1)
96
66
  def test_returns_the_correct_get_value_using_the_global_context_1
97
- begin
98
- resolver = IntegrationTestHelpers.build_resolver(@store)
99
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@prefab.cloud"}}, "override")
100
- rescue Exception => e
101
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
102
- end
67
+ resolver = IntegrationTestHelpers.build_resolver(@store)
68
+ IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@prefab.cloud"}}, "override")
103
69
  end
104
70
 
105
71
  # returns the correct `get` value using the global context (2)
106
72
  def test_returns_the_correct_get_value_using_the_global_context_2
107
- begin
108
- resolver = IntegrationTestHelpers.build_resolver(@store)
109
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@example.com"}}, "default")
110
- rescue Exception => e
111
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
112
- end
113
- end
114
-
115
- # returns the correct `get` value using the global context and api context (1)
116
- def test_returns_the_correct_get_value_using_the_global_context_and_api_context_1
117
- begin
118
- resolver = IntegrationTestHelpers.build_resolver(@store)
119
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config.with.api.conditional", {"user" => {"email" => "test@prefab.cloud"}}, "override")
120
- rescue Exception => e
121
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
122
- end
123
- end
124
-
125
- # returns the correct `get` value using the global context and api context (2)
126
- def test_returns_the_correct_get_value_using_the_global_context_and_api_context_2
127
- begin
128
- resolver = IntegrationTestHelpers.build_resolver(@store)
129
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config.with.api.conditional", {"user" => {"email" => "test@example.com"}}, "api-override")
130
- rescue Exception => e
131
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
132
- end
73
+ resolver = IntegrationTestHelpers.build_resolver(@store)
74
+ IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@example.com"}}, "default")
133
75
  end
134
76
 
135
77
  # returns the correct `get` value when local context clobbers global context (1)
136
78
  def test_returns_the_correct_get_value_when_local_context_clobbers_global_context_1
137
- begin
138
- resolver = IntegrationTestHelpers.build_resolver(@store)
139
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@prefab.cloud"}}, "override")
140
- rescue Exception => e
141
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
142
- end
79
+ resolver = IntegrationTestHelpers.build_resolver(@store)
80
+ IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@prefab.cloud"}}, "override")
143
81
  end
144
82
 
145
83
  # returns the correct `get` value when local context clobbers global context (2)
146
84
  def test_returns_the_correct_get_value_when_local_context_clobbers_global_context_2
147
- begin
148
- resolver = IntegrationTestHelpers.build_resolver(@store)
149
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@example.com"}}, "default")
150
- rescue Exception => e
151
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
152
- end
85
+ resolver = IntegrationTestHelpers.build_resolver(@store)
86
+ IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@example.com"}}, "default")
153
87
  end
154
88
 
155
89
  # returns the correct `get` value when block context clobbers global context (1)
156
90
  def test_returns_the_correct_get_value_when_block_context_clobbers_global_context_1
157
- begin
158
- resolver = IntegrationTestHelpers.build_resolver(@store)
159
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@example.com"}}, "default")
160
- rescue Exception => e
161
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
162
- end
91
+ resolver = IntegrationTestHelpers.build_resolver(@store)
92
+ IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@example.com"}}, "default")
163
93
  end
164
94
 
165
95
  # returns the correct `get` value when block context clobbers global context (2)
166
96
  def test_returns_the_correct_get_value_when_block_context_clobbers_global_context_2
167
- begin
168
- resolver = IntegrationTestHelpers.build_resolver(@store)
169
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@prefab.cloud"}}, "override")
170
- rescue Exception => e
171
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
172
- end
97
+ resolver = IntegrationTestHelpers.build_resolver(@store)
98
+ IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@prefab.cloud"}}, "override")
173
99
  end
174
100
 
175
101
  # returns the correct `get` value when local context clobbers block context (1)
176
102
  def test_returns_the_correct_get_value_when_local_context_clobbers_block_context_1
177
- begin
178
- resolver = IntegrationTestHelpers.build_resolver(@store)
179
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@example.com"}}, "default")
180
- rescue Exception => e
181
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
182
- end
103
+ resolver = IntegrationTestHelpers.build_resolver(@store)
104
+ IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@example.com"}}, "default")
183
105
  end
184
106
 
185
107
  # returns the correct `get` value when local context clobbers block context (2)
186
108
  def test_returns_the_correct_get_value_when_local_context_clobbers_block_context_2
187
- begin
188
- resolver = IntegrationTestHelpers.build_resolver(@store)
189
- IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@prefab.cloud"}}, "override")
190
- rescue Exception => e
191
- skip("resolver not yet ported for this case: #{e.class}: #{e.message}")
192
- end
109
+ resolver = IntegrationTestHelpers.build_resolver(@store)
110
+ IntegrationTestHelpers.assert_resolved(resolver, "basic.rule.config", {"user" => {"email" => "test@prefab.cloud"}}, "override")
193
111
  end
194
112
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  #
3
3
  # AUTO-GENERATED from integration-test-data/tests/eval/datadir_environment.yaml.
4
- # Regenerate with `bundle exec ruby scripts/generate_integration_tests.rb`.
4
+ # Regenerate with:
5
+ # cd integration-test-data/generators && npm run generate -- --target=ruby
6
+ # Source: integration-test-data/generators/src/targets/ruby.ts
5
7
  # Do NOT edit by hand — changes will be overwritten.
6
8
 
7
9
  require 'test_helper'
@@ -14,63 +16,39 @@ class TestDatadirEnvironment < Minitest::Test
14
16
 
15
17
  # datadir with environment option gets environment-specific value
16
18
  def test_datadir_with_environment_option_gets_environment_specific_value
17
- begin
18
- client = Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir, environment: "Production")
19
- assert_equal "test4", client.get("james.test.key")
20
- rescue Exception => e
21
- skip("datadir Client.new not yet wired: #{e.class}: #{e.message}")
22
- end
19
+ client = Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir, environment: "Production")
20
+ assert_equal "test4", client.get("james.test.key")
23
21
  end
24
22
 
25
23
  # datadir with QUONFIG_ENVIRONMENT env var gets environment-specific value
26
24
  def test_datadir_with_quonfig_environment_env_var_gets_environment_specific_value
27
- begin
28
- IntegrationTestHelpers.with_env({"QUONFIG_ENVIRONMENT" => "Production"}) do
29
- client = Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir)
30
- assert_equal "test4", client.get("james.test.key")
31
- end
32
- rescue Exception => e
33
- skip("datadir Client.new not yet wired: #{e.class}: #{e.message}")
25
+ IntegrationTestHelpers.with_env({"QUONFIG_ENVIRONMENT" => "Production"}) do
26
+ client = Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir)
27
+ assert_equal "test4", client.get("james.test.key")
34
28
  end
35
29
  end
36
30
 
37
31
  # environment option supersedes QUONFIG_ENVIRONMENT env var
38
32
  def test_environment_option_supersedes_quonfig_environment_env_var
39
- begin
40
- IntegrationTestHelpers.with_env({"QUONFIG_ENVIRONMENT" => "nonexistent"}) do
41
- client = Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir, environment: "Production")
42
- assert_equal "test4", client.get("james.test.key")
43
- end
44
- rescue Exception => e
45
- skip("datadir Client.new not yet wired: #{e.class}: #{e.message}")
33
+ IntegrationTestHelpers.with_env({"QUONFIG_ENVIRONMENT" => "nonexistent"}) do
34
+ client = Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir, environment: "Production")
35
+ assert_equal "test4", client.get("james.test.key")
46
36
  end
47
37
  end
48
38
 
49
39
  # config without environment override returns default value
50
40
  def test_config_without_environment_override_returns_default_value
51
- begin
52
- client = Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir, environment: "Production")
53
- assert_equal "hello from no env row", client.get("config.with.only.default.env.row")
54
- rescue Exception => e
55
- skip("datadir Client.new not yet wired: #{e.class}: #{e.message}")
56
- end
41
+ client = Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir, environment: "Production")
42
+ assert_equal "hello from no env row", client.get("config.with.only.default.env.row")
57
43
  end
58
44
 
59
45
  # datadir without environment fails to init
60
46
  def test_datadir_without_environment_fails_to_init
61
- begin
62
- skip("init raise-case (missing_environment) — no Quonfig::Errors mapping yet")
63
- rescue Exception => e
64
- skip("datadir Client.new not yet wired: #{e.class}: #{e.message}")
65
- end
47
+ assert_raises(Quonfig::Errors::MissingEnvironmentError) { Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir) }
66
48
  end
67
49
 
68
50
  # datadir with invalid environment fails to init
69
51
  def test_datadir_with_invalid_environment_fails_to_init
70
- begin
71
- skip("init raise-case (invalid_environment) — no Quonfig::Errors mapping yet")
72
- rescue Exception => e
73
- skip("datadir Client.new not yet wired: #{e.class}: #{e.message}")
74
- end
52
+ assert_raises(Quonfig::Errors::InvalidEnvironmentError) { Quonfig::Client.new(datadir: IntegrationTestHelpers.data_dir, environment: "nonexistent") }
75
53
  end
76
54
  end