sdk-reforge 1.9.0 → 1.9.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c692e3a481a6340630232b56282b7e2dfa1e55102a03e910bb2522cc49a7660
4
- data.tar.gz: e4485f45667650616b375e9b1d4aa76b03f3377744fb223d2807e4a30e99a7f4
3
+ metadata.gz: 46d6f6afa160400b4319eb454fd5271462d1dc8264570c549327652a4debb1c3
4
+ data.tar.gz: 4a88ea9de1cdfef5dc0e8eff6f8d7b210808ffed6236efb8ec150d138d6923cd
5
5
  SHA512:
6
- metadata.gz: a378c5553acaf78760bdad194c3843f2d565a3cb8e66e4db3f462b5555748394674df9fa5a599eea144d533f6069dd3bc7a052bce3e604ede4193703e79fa719
7
- data.tar.gz: 39b53e20a22e1c793bbb9bbe4dbde40c90dcea9ac0298be835fea3a22c222dec28e1cb12741fe8564952201de30db3e5700204430104df475c99c514cca3d611
6
+ metadata.gz: 569a9c836b3c0d85f06387235f3909343233ee2d5c8ab9c5d888d5f7780a16cac6c70b78fafa89e9cf3653890c16c1eb86a5461afedce5d96edcf904c1d8a42a
7
+ data.tar.gz: 1dd174148278acbc0625ee85081c39ca3d975d710ee837813c4ab3587f117796a341c27d63fd548e16a423d040421efb9096e62809d9922818d9a5ed6de508b2
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: Push gem
3
+
4
+ "on":
5
+ workflow_run:
6
+ workflows: ["Ruby"]
7
+ branches: [main]
8
+ types:
9
+ - completed
10
+
11
+ jobs:
12
+ push:
13
+ runs-on: ubuntu-latest
14
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
15
+
16
+ permissions:
17
+ contents: write
18
+ id-token: write
19
+
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ persist-credentials: false
24
+ submodules: recursive
25
+
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@v1
28
+ with:
29
+ bundler-cache: true
30
+ ruby-version: ruby
31
+
32
+ - name: Check if version already exists
33
+ id: version-check
34
+ run: |
35
+ VERSION=$(cat VERSION)
36
+ if gem list -r sdk-reforge | grep -q "sdk-reforge ($VERSION)"; then
37
+ echo "version-exists=true" >> $GITHUB_OUTPUT
38
+ echo "Version $VERSION already exists on RubyGems, skipping publish"
39
+ else
40
+ echo "version-exists=false" >> $GITHUB_OUTPUT
41
+ echo "Version $VERSION not found, proceeding with publish"
42
+ fi
43
+
44
+ - name: Release gem
45
+ if: steps.version-check.outputs.version-exists == 'false'
46
+ uses: rubygems/release-gem@v1
data/CHANGELOG.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 1.9.2 - 2025-10-02
5
+
6
+ - Fix bug in row index calculation for the evaluation summary data
7
+
8
+ ## 1.9.1 - 2025-10-01
9
+
10
+ - Fix entrypoint
11
+
12
+
4
13
  ## 1.9.0 - 2025-08-23
5
14
 
6
15
  - Moved to reforge gem name `sdk-reforge`
data/Rakefile CHANGED
@@ -56,8 +56,9 @@ end
56
56
 
57
57
  # Add release task for CI
58
58
  task :release do
59
- sh 'gem build sdk-reforge.gemspec'
59
+ sh 'mkdir -p pkg'
60
60
  version = File.read('VERSION').strip
61
- gem_file = "sdk-reforge-#{version}.gem"
61
+ gem_file = "pkg/sdk-reforge-#{version}.gem"
62
+ sh "gem build sdk-reforge.gemspec --output #{gem_file}"
62
63
  sh "gem push #{gem_file}"
63
64
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.9.0
1
+ 1.9.2
data/dev/script_setup.rb CHANGED
@@ -12,7 +12,7 @@ spec.require_paths.each do |path|
12
12
  end
13
13
 
14
14
  spec.require_paths.each do |path|
15
- require "./lib/reforge-sdk"
15
+ require "./lib/sdk-reforge"
16
16
  end
17
17
 
18
18
  SemanticLogger.add_appender(io: $stdout)
@@ -140,6 +140,11 @@ module Reforge
140
140
  def load_url(conn, source)
141
141
  resp = conn.get('')
142
142
  if resp.status == 200
143
+ if resp.body.nil? || resp.body.empty?
144
+ LOG.warn "Checkpoint #{source} [#{conn.uri}] failed to load. Response body is empty"
145
+ return false
146
+ end
147
+
143
148
  configs = PrefabProto::Configs.decode(resp.body)
144
149
  load_configs(configs, source)
145
150
  cache_configs(configs)
@@ -218,7 +223,13 @@ module Reforge
218
223
  return false unless @options.use_local_cache
219
224
  File.open(cache_path) do |f|
220
225
  f.flock(File::LOCK_SH)
221
- configs = PrefabProto::Configs.decode_json(f.read)
226
+ content = f.read
227
+ if content.nil? || content.empty?
228
+ LOG.warn "Failed to read cached configs at #{cache_path}. File is empty"
229
+ return false
230
+ end
231
+
232
+ configs = PrefabProto::Configs.decode_json(content)
222
233
  load_configs(configs, :cache)
223
234
 
224
235
  hours_old = ((Time.now - File.mtime(f)) / 60 / 60).round(2)
@@ -235,7 +246,13 @@ module Reforge
235
246
  def load_json_file(file)
236
247
  File.open(file) do |f|
237
248
  f.flock(File::LOCK_SH)
238
- configs = PrefabProto::Configs.decode_json(f.read)
249
+ content = f.read
250
+ if content.nil? || content.empty?
251
+ LOG.warn "Failed to read datafile at #{file}. File is empty"
252
+ return false
253
+ end
254
+
255
+ configs = PrefabProto::Configs.decode_json(content)
239
256
  load_configs(configs, :datafile)
240
257
  end
241
258
  end
@@ -227,11 +227,23 @@ module Reforge
227
227
  row.values.each_with_index do |conditional_value, value_index|
228
228
  next unless all_criteria_match?(conditional_value, properties)
229
229
 
230
+ # we compute the row index here as if the @config.rows were sorted with the default environment (id=0) last. the client would only ever see one or two rows
231
+ # 2 rows if there's a default env rule and a targed env rule
232
+ # 1 row if there's only a default env rule or only a target env rule
233
+ config_row_index = if @config.rows.length == 1
234
+ 0
235
+ elsif row.project_env_id != 0
236
+ 0
237
+ else
238
+ 1
239
+ end
240
+
241
+
230
242
  return Reforge::Evaluation.new(
231
243
  config: @config,
232
244
  value: conditional_value.value,
233
245
  value_index: value_index,
234
- config_row_index: index,
246
+ config_row_index: config_row_index,
235
247
  context: properties,
236
248
  resolver: @resolver
237
249
  )
@@ -117,4 +117,4 @@ module Reforge
117
117
  raise Reforge::Errors::UninitializedError.new(key)
118
118
  end
119
119
  end
120
- end
120
+ end
@@ -73,7 +73,20 @@ module Reforge
73
73
  reconnect_time: @options.sse_default_reconnect_time,
74
74
  logger: Reforge::InternalLogger.new(SSE::Client)) do |client|
75
75
  client.on_event do |event|
76
- configs = PrefabProto::Configs.decode(Base64.decode64(event.data))
76
+ if event.data.nil? || event.data.empty?
77
+ @logger.error "SSE Streaming Error: Received empty data for url #{url}"
78
+ client.close
79
+ return
80
+ end
81
+
82
+ decoded_data = Base64.decode64(event.data)
83
+ if decoded_data.nil? || decoded_data.empty?
84
+ @logger.error "SSE Streaming Error: Decoded data is empty for url #{url}"
85
+ client.close
86
+ return
87
+ end
88
+
89
+ configs = PrefabProto::Configs.decode(decoded_data)
77
90
  load_configs.call(configs, event, :sse)
78
91
  end
79
92
 
@@ -49,7 +49,7 @@ require 'reforge/client'
49
49
  require 'reforge/config_client_presenter'
50
50
  require 'reforge/config_client'
51
51
  require 'reforge/feature_flag_client'
52
- require 'reforge/prefab'
52
+ require 'reforge/reforge'
53
53
  require 'reforge/murmer3'
54
54
  require 'reforge/javascript_stub'
55
55
  require 'reforge/semver'
@@ -0,0 +1,149 @@
1
+ # Generated by juwelier
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+ # stub: sdk-reforge 1.9.2 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "sdk-reforge".freeze
9
+ s.version = "1.9.2"
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib".freeze]
13
+ s.authors = ["Jeff Dwyer".freeze]
14
+ s.date = "2025-10-02"
15
+ s.description = "Feature Flags, Live Config as a service".freeze
16
+ s.email = "jeff.dwyer@reforge.com.cloud".freeze
17
+ s.extra_rdoc_files = [
18
+ "CHANGELOG.md",
19
+ "LICENSE.txt",
20
+ "README.md"
21
+ ]
22
+ s.files = [
23
+ ".envrc.sample",
24
+ ".github/CODEOWNERS",
25
+ ".github/pull_request_template.md",
26
+ ".github/workflows/push_gem.yml",
27
+ ".github/workflows/ruby.yml",
28
+ ".gitmodules",
29
+ ".rubocop.yml",
30
+ ".tool-versions",
31
+ "CHANGELOG.md",
32
+ "CODEOWNERS",
33
+ "Gemfile",
34
+ "Gemfile.lock",
35
+ "LICENSE.txt",
36
+ "README.md",
37
+ "Rakefile",
38
+ "VERSION",
39
+ "compile_protos.sh",
40
+ "dev/allocation_stats",
41
+ "dev/benchmark",
42
+ "dev/console",
43
+ "dev/script_setup.rb",
44
+ "lib/prefab_pb.rb",
45
+ "lib/reforge/caching_http_connection.rb",
46
+ "lib/reforge/client.rb",
47
+ "lib/reforge/config_client.rb",
48
+ "lib/reforge/config_client_presenter.rb",
49
+ "lib/reforge/config_loader.rb",
50
+ "lib/reforge/config_resolver.rb",
51
+ "lib/reforge/config_value_unwrapper.rb",
52
+ "lib/reforge/config_value_wrapper.rb",
53
+ "lib/reforge/context.rb",
54
+ "lib/reforge/context_shape.rb",
55
+ "lib/reforge/context_shape_aggregator.rb",
56
+ "lib/reforge/criteria_evaluator.rb",
57
+ "lib/reforge/duration.rb",
58
+ "lib/reforge/encryption.rb",
59
+ "lib/reforge/error.rb",
60
+ "lib/reforge/errors/env_var_parse_error.rb",
61
+ "lib/reforge/errors/initialization_timeout_error.rb",
62
+ "lib/reforge/errors/invalid_sdk_key_error.rb",
63
+ "lib/reforge/errors/missing_default_error.rb",
64
+ "lib/reforge/errors/missing_env_var_error.rb",
65
+ "lib/reforge/errors/uninitialized_error.rb",
66
+ "lib/reforge/evaluation.rb",
67
+ "lib/reforge/evaluation_summary_aggregator.rb",
68
+ "lib/reforge/example_contexts_aggregator.rb",
69
+ "lib/reforge/exponential_backoff.rb",
70
+ "lib/reforge/feature_flag_client.rb",
71
+ "lib/reforge/fixed_size_hash.rb",
72
+ "lib/reforge/http_connection.rb",
73
+ "lib/reforge/internal_logger.rb",
74
+ "lib/reforge/javascript_stub.rb",
75
+ "lib/reforge/local_config_parser.rb",
76
+ "lib/reforge/murmer3.rb",
77
+ "lib/reforge/options.rb",
78
+ "lib/reforge/periodic_sync.rb",
79
+ "lib/reforge/rate_limit_cache.rb",
80
+ "lib/reforge/reforge.rb",
81
+ "lib/reforge/resolved_config_presenter.rb",
82
+ "lib/reforge/semver.rb",
83
+ "lib/reforge/sse_config_client.rb",
84
+ "lib/reforge/time_helpers.rb",
85
+ "lib/reforge/weighted_value_resolver.rb",
86
+ "lib/reforge/yaml_config_parser.rb",
87
+ "lib/sdk-reforge.rb",
88
+ "sdk-reforge.gemspec",
89
+ "test/fixtures/datafile.json",
90
+ "test/integration_test.rb",
91
+ "test/integration_test_helpers.rb",
92
+ "test/support/common_helpers.rb",
93
+ "test/support/mock_base_client.rb",
94
+ "test/support/mock_config_client.rb",
95
+ "test/support/mock_config_loader.rb",
96
+ "test/test_caching_http_connection.rb",
97
+ "test/test_client.rb",
98
+ "test/test_config_client.rb",
99
+ "test/test_config_loader.rb",
100
+ "test/test_config_resolver.rb",
101
+ "test/test_config_value_unwrapper.rb",
102
+ "test/test_config_value_wrapper.rb",
103
+ "test/test_context.rb",
104
+ "test/test_context_shape.rb",
105
+ "test/test_context_shape_aggregator.rb",
106
+ "test/test_criteria_evaluator.rb",
107
+ "test/test_duration.rb",
108
+ "test/test_encryption.rb",
109
+ "test/test_evaluation_summary_aggregator.rb",
110
+ "test/test_example_contexts_aggregator.rb",
111
+ "test/test_exponential_backoff.rb",
112
+ "test/test_feature_flag_client.rb",
113
+ "test/test_fixed_size_hash.rb",
114
+ "test/test_helper.rb",
115
+ "test/test_integration.rb",
116
+ "test/test_internal_logger.rb",
117
+ "test/test_javascript_stub.rb",
118
+ "test/test_local_config_parser.rb",
119
+ "test/test_logger_initialization.rb",
120
+ "test/test_options.rb",
121
+ "test/test_prefab.rb",
122
+ "test/test_rate_limit_cache.rb",
123
+ "test/test_semver.rb",
124
+ "test/test_sse_config_client.rb",
125
+ "test/test_weighted_value_resolver.rb"
126
+ ]
127
+ s.homepage = "http://github.com/ReforgeHQ/sdk-ruby".freeze
128
+ s.licenses = ["MIT".freeze]
129
+ s.rubygems_version = "3.4.19".freeze
130
+ s.summary = "Reforge Launch Ruby Infrastructure".freeze
131
+
132
+ s.specification_version = 4
133
+
134
+ s.add_runtime_dependency(%q<concurrent-ruby>.freeze, ["~> 1.0", ">= 1.0.5"])
135
+ s.add_runtime_dependency(%q<faraday>.freeze, [">= 0"])
136
+ s.add_runtime_dependency(%q<googleapis-common-protos-types>.freeze, [">= 0"])
137
+ s.add_runtime_dependency(%q<google-protobuf>.freeze, [">= 0"])
138
+ s.add_runtime_dependency(%q<ld-eventsource>.freeze, [">= 0"])
139
+ s.add_runtime_dependency(%q<uuid>.freeze, [">= 0"])
140
+ s.add_runtime_dependency(%q<activesupport>.freeze, [">= 4"])
141
+ s.add_runtime_dependency(%q<semantic_logger>.freeze, ["!= 4.16.0"])
142
+ s.add_development_dependency(%q<allocation_stats>.freeze, [">= 0"])
143
+ s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
144
+ s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
145
+ s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.4.9"])
146
+ s.add_development_dependency(%q<rdoc>.freeze, [">= 0"])
147
+ s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
148
+ end
149
+
@@ -137,7 +137,21 @@ module CommonHelpers
137
137
  def assert_summary(client, data)
138
138
  raise 'Evaluation summary aggregator not enabled' unless client.evaluation_summary_aggregator
139
139
 
140
- assert_equal data, client.evaluation_summary_aggregator.data
140
+ actual = client.evaluation_summary_aggregator.data
141
+
142
+ # Compare keys first (order independent)
143
+ assert_equal data.keys.sort_by(&:to_s), actual.keys.sort_by(&:to_s), "Summary keys mismatch"
144
+
145
+ # Then compare each nested hash (order independent)
146
+ data.each do |key, expected_counters|
147
+ actual_counters = actual[key]
148
+
149
+ # Convert to sets for order-independent comparison
150
+ expected_set = expected_counters.to_a.to_set
151
+ actual_set = actual_counters.to_a.to_set
152
+
153
+ assert_equal expected_set, actual_set, "Counter mismatch for #{key}"
154
+ end
141
155
  end
142
156
 
143
157
  def assert_example_contexts(client, data)
data/test/test_client.rb CHANGED
@@ -106,7 +106,7 @@ class TestClient < Minitest::Test
106
106
  [KEY, :CONFIG] => {
107
107
  {
108
108
  config_id: config.id,
109
- config_row_index: 1,
109
+ config_row_index: 0,
110
110
  selected_value: DESIRED_VALUE_CONFIG,
111
111
  conditional_value_index: 0,
112
112
  weighted_value_index: nil,
@@ -131,7 +131,7 @@ class TestClient < Minitest::Test
131
131
  [KEY, :CONFIG] => {
132
132
  {
133
133
  config_id: config.id,
134
- config_row_index: 1,
134
+ config_row_index: 0,
135
135
  selected_value: DESIRED_VALUE_CONFIG,
136
136
  conditional_value_index: 0,
137
137
  weighted_value_index: nil,
@@ -181,7 +181,7 @@ class TestClient < Minitest::Test
181
181
  [KEY, :CONFIG] => {
182
182
  {
183
183
  config_id: config.id,
184
- config_row_index: 1,
184
+ config_row_index: 0,
185
185
  selected_value: PrefabProto::ConfigValue.new(string: 'abc'),
186
186
  conditional_value_index: 0,
187
187
  weighted_value_index: 0,
@@ -190,7 +190,7 @@ class TestClient < Minitest::Test
190
190
 
191
191
  {
192
192
  config_id: config.id,
193
- config_row_index: 1,
193
+ config_row_index: 0,
194
194
  selected_value: PrefabProto::ConfigValue.new(string: 'def'),
195
195
  conditional_value_index: 0,
196
196
  weighted_value_index: 1,
@@ -199,7 +199,7 @@ class TestClient < Minitest::Test
199
199
 
200
200
  {
201
201
  config_id: config.id,
202
- config_row_index: 1,
202
+ config_row_index: 0,
203
203
  selected_value: PrefabProto::ConfigValue.new(string: 'ghi'),
204
204
  conditional_value_index: 0,
205
205
  weighted_value_index: 2,
@@ -278,9 +278,9 @@ class TestClient < Minitest::Test
278
278
  weighted_value_index: nil, selected_index: nil } => 1
279
279
  },
280
280
  [KEY, :NOT_SET_CONFIG_TYPE] => {
281
- { config_id: 0, config_row_index: 0, conditional_value_index: 0, selected_value: DEFAULT_VALUE_CONFIG,
281
+ { config_id: 0, config_row_index: 1, conditional_value_index: 0, selected_value: DEFAULT_VALUE_CONFIG,
282
282
  weighted_value_index: nil, selected_index: nil } => 2,
283
- { config_id: 0, config_row_index: 1, conditional_value_index: 0, selected_value: DESIRED_VALUE_CONFIG,
283
+ { config_id: 0, config_row_index: 0, conditional_value_index: 0, selected_value: DESIRED_VALUE_CONFIG,
284
284
  weighted_value_index: nil, selected_index: nil } => 1
285
285
  }
286
286
  }
@@ -81,4 +81,71 @@ class TestConfigClient < Minitest::Test
81
81
  end
82
82
  end
83
83
 
84
+ def test_load_url_with_empty_body
85
+ options = Reforge::Options.new(
86
+ prefab_datasources: Reforge::Options::DATASOURCES::LOCAL_ONLY,
87
+ x_use_local_cache: true,
88
+ sdk_key: "123-ENV-KEY-SDK",)
89
+
90
+ config_client = Reforge::ConfigClient.new(MockBaseClient.new(options), 10)
91
+
92
+ # Mock connection with empty response body
93
+ mock_conn = Minitest::Mock.new
94
+ mock_resp = Minitest::Mock.new
95
+ mock_resp.expect(:status, 200)
96
+ mock_resp.expect(:body, '')
97
+ mock_resp.expect(:body, '')
98
+ mock_conn.expect(:get, mock_resp, [''])
99
+ mock_conn.expect(:uri, 'http://test.example.com')
100
+
101
+ result = config_client.send(:load_url, mock_conn, :test_source)
102
+
103
+ assert_equal false, result, 'Expected load_url to return false for empty body'
104
+ mock_conn.verify
105
+ mock_resp.verify
106
+
107
+ assert_logged [/Response body is empty/]
108
+ end
109
+
110
+ def test_load_cache_with_empty_file
111
+ options = Reforge::Options.new(
112
+ prefab_datasources: Reforge::Options::DATASOURCES::LOCAL_ONLY,
113
+ x_use_local_cache: true,
114
+ sdk_key: "123-ENV-KEY-SDK",)
115
+
116
+ config_client = Reforge::ConfigClient.new(MockBaseClient.new(options), 10)
117
+ cache_path = config_client.send(:cache_path)
118
+
119
+ # Create an empty cache file
120
+ FileUtils.mkdir_p(File.dirname(cache_path))
121
+ File.write(cache_path, '')
122
+
123
+ result = config_client.send(:load_cache)
124
+
125
+ assert_equal false, result, 'Expected load_cache to return false for empty file'
126
+ assert_logged [/File is empty/]
127
+ ensure
128
+ File.delete(cache_path) if File.exist?(cache_path)
129
+ end
130
+
131
+ def test_load_json_file_with_empty_file
132
+ options = Reforge::Options.new(
133
+ prefab_datasources: Reforge::Options::DATASOURCES::LOCAL_ONLY,
134
+ x_use_local_cache: true,
135
+ sdk_key: "123-ENV-KEY-SDK",)
136
+
137
+ config_client = Reforge::ConfigClient.new(MockBaseClient.new(options), 10)
138
+
139
+ # Create a temporary empty datafile
140
+ temp_file = File.join(Dir.tmpdir, 'test_empty_datafile.json')
141
+ File.write(temp_file, '')
142
+
143
+ result = config_client.send(:load_json_file, temp_file)
144
+
145
+ assert_equal false, result, 'Expected load_json_file to return false for empty file'
146
+ assert_logged [/File is empty/]
147
+ ensure
148
+ File.delete(temp_file) if File.exist?(temp_file)
149
+ end
150
+
84
151
  end
data/test/test_helper.rb CHANGED
@@ -5,7 +5,7 @@ require 'minitest/focus'
5
5
  require 'minitest/reporters'
6
6
  Minitest::Reporters.use! unless ENV['RM_INFO']
7
7
 
8
- require 'reforge-sdk'
8
+ require 'sdk-reforge'
9
9
 
10
10
  Dir.glob(File.join(File.dirname(__FILE__), 'support', '**', '*.rb')).each do |file|
11
11
  require file
@@ -208,4 +208,43 @@ class TestSSEConfigClient < Minitest::Test
208
208
  output << "data: CmYIu8fh4YaO0x4QZBo0bG9nLWxldmVsLmNsb3VkLnByZWZhYi5zZXJ2ZXIubG9nZ2luZy5FdmVudFByb2Nlc3NvciIfCAESG2phbWVzLmtlYmluZ2VyQHByZWZhYi5jbG91ZDgGSAkSDQhkELvH4eGGjtMeGGU=\n\n"
209
209
  end
210
210
  end
211
+
212
+ def test_empty_data_validation
213
+ # Unit test to verify that empty data is properly detected and handled
214
+ log_output = StringIO.new
215
+ logger = Logger.new(log_output)
216
+
217
+ # Test that empty event.data is detected
218
+ mock_event = OpenStruct.new(data: '')
219
+ mock_client = Minitest::Mock.new
220
+ mock_client.expect(:close, nil)
221
+
222
+ # Simulate the on_event handler logic
223
+ if mock_event.data.nil? || mock_event.data.empty?
224
+ logger.error "SSE Streaming Error: Received empty data for url http://test"
225
+ mock_client.close
226
+ end
227
+
228
+ log_lines = log_output.string.split("\n")
229
+ assert log_lines.any? { |line| line.include?('SSE Streaming Error') && line.include?('empty data') },
230
+ 'Expected to have logged an error about empty data'
231
+ mock_client.verify
232
+
233
+ # Test that nil event.data is detected
234
+ log_output = StringIO.new
235
+ logger = Logger.new(log_output)
236
+ mock_event = OpenStruct.new(data: nil)
237
+ mock_client = Minitest::Mock.new
238
+ mock_client.expect(:close, nil)
239
+
240
+ if mock_event.data.nil? || mock_event.data.empty?
241
+ logger.error "SSE Streaming Error: Received empty data for url http://test"
242
+ mock_client.close
243
+ end
244
+
245
+ log_lines = log_output.string.split("\n")
246
+ assert log_lines.any? { |line| line.include?('SSE Streaming Error') && line.include?('empty data') },
247
+ 'Expected to have logged an error about empty data for nil'
248
+ mock_client.verify
249
+ end
211
250
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sdk-reforge
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-09-23 00:00:00.000000000 Z
10
+ date: 2025-10-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: concurrent-ruby
@@ -223,6 +223,7 @@ files:
223
223
  - ".envrc.sample"
224
224
  - ".github/CODEOWNERS"
225
225
  - ".github/pull_request_template.md"
226
+ - ".github/workflows/push_gem.yml"
226
227
  - ".github/workflows/ruby.yml"
227
228
  - ".gitmodules"
228
229
  - ".rubocop.yml"
@@ -241,7 +242,6 @@ files:
241
242
  - dev/console
242
243
  - dev/script_setup.rb
243
244
  - lib/prefab_pb.rb
244
- - lib/reforge-sdk.rb
245
245
  - lib/reforge/caching_http_connection.rb
246
246
  - lib/reforge/client.rb
247
247
  - lib/reforge/config_client.rb
@@ -276,14 +276,16 @@ files:
276
276
  - lib/reforge/murmer3.rb
277
277
  - lib/reforge/options.rb
278
278
  - lib/reforge/periodic_sync.rb
279
- - lib/reforge/prefab.rb
280
279
  - lib/reforge/rate_limit_cache.rb
280
+ - lib/reforge/reforge.rb
281
281
  - lib/reforge/resolved_config_presenter.rb
282
282
  - lib/reforge/semver.rb
283
283
  - lib/reforge/sse_config_client.rb
284
284
  - lib/reforge/time_helpers.rb
285
285
  - lib/reforge/weighted_value_resolver.rb
286
286
  - lib/reforge/yaml_config_parser.rb
287
+ - lib/sdk-reforge.rb
288
+ - sdk-reforge.gemspec
287
289
  - test/fixtures/datafile.json
288
290
  - test/integration_test.rb
289
291
  - test/integration_test_helpers.rb