debugbundle 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,202 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require 'stringio'
5
-
6
- RSpec.describe 'stdlib logger integration' do
7
- def instrument_logger(logger, &capture)
8
- client = Object.new
9
- client.define_singleton_method(:capture_log, &capture)
10
- DebugBundle::Logging.install_stdlib_logger(logger, client: client)
11
- end
12
-
13
- it 'respects the current logger level without evaluating suppressed blocks' do
14
- output = StringIO.new
15
- logger = Logger.new(output, level: Logger::ERROR)
16
- captured = []
17
- instrument_logger(logger) { |message, **_options| captured << message }
18
- evaluations = 0
19
- result = logger.warn do
20
- evaluations += 1
21
- 'suppressed'
22
- end
23
- expect(result).to be(true)
24
- expect(captured).to be_empty
25
- expect(evaluations).to eq(0)
26
- logger.level = Logger::WARN
27
- logger.warn do
28
- evaluations += 1
29
- 'accepted'
30
- end
31
- expect(evaluations).to eq(1)
32
- expect(captured).to eq(['accepted'])
33
- expect(output.string).to include('accepted')
34
- expect(output.string).not_to include('suppressed')
35
- end
36
-
37
- it 'preserves logger message and progname semantics and evaluates accepted blocks once' do
38
- output = StringIO.new
39
- logger = Logger.new(output, progname: 'default-name')
40
- captured = []
41
- instrument_logger(logger) { |message, **_options| captured << message }
42
- logger.add(Logger::WARN)
43
- logger.add(Logger::WARN, nil, 'message-as-progname')
44
- logger.add(nil, 'unknown-severity')
45
- evaluations = 0
46
- logger.warn('explicit-name') do
47
- evaluations += 1
48
- "block-#{evaluations}"
49
- end
50
- expect(captured).to eq(%w[default-name message-as-progname unknown-severity block-1])
51
- expect(evaluations).to eq(1)
52
- expect(output.string).to include('explicit-name: block-1')
53
- end
54
-
55
- it 'does not evaluate blocks or capture when the logger has no output device' do
56
- logger = Logger.new(nil)
57
- instrument_logger(logger) { raise 'should not capture' }
58
- expect(logger.warn { raise 'should not evaluate' }).to be(true)
59
- end
60
-
61
- it 'captures the log alias and preserves false messages and Rails silence' do
62
- require 'active_support'
63
- require 'active_support/logger'
64
- output = StringIO.new
65
- logger = ActiveSupport::Logger.new(output)
66
- captured = []
67
- instrument_logger(logger) { |message, **_options| captured << message }
68
- logger.silence(Logger::ERROR) { logger.warn { raise 'suppressed block' } }
69
- logger.log(Logger::WARN, false)
70
- expect(captured).to eq(['false'])
71
- expect(output.string).to include('false')
72
- end
73
-
74
- it 'isolates capture failures and recursion while preserving application logging failures' do
75
- output = StringIO.new
76
- logger = Logger.new(output)
77
- calls = 0
78
- instrument_logger(logger) do |_message, **_options|
79
- calls += 1
80
- logger.warn('nested')
81
- raise 'capture failed'
82
- end
83
- expect { logger.warn('outer') }.not_to raise_error
84
- expect(calls).to eq(1)
85
- expect(output.string).to include('outer', 'nested')
86
- expect { logger.warn { raise 'application failure' } }.to raise_error('application failure')
87
- expect { logger.warn('after failure') }.not_to raise_error
88
- expect(calls).to eq(2)
89
- end
90
-
91
- it 'captures log events without changing logger output' do
92
- transport_events = []
93
- transport = Class.new do
94
- define_method(:initialize) do |transport_events|
95
- @transport_events = transport_events
96
- end
97
-
98
- define_method(:call) do |request|
99
- @transport_events << request
100
- DebugBundle::Transport::Result.new(status_code: 202)
101
- end
102
- end.new(transport_events)
103
-
104
- output = StringIO.new
105
- logger = Logger.new(output)
106
- logger.progname = 'checkout'
107
-
108
- client = DebugBundle::Client.new(project_token: 'dbundle_proj_test', transport: transport)
109
- client.capture_logger(logger)
110
- client.capture_logger(logger)
111
-
112
- logger.warn('payment retry failed')
113
- client.flush
114
-
115
- event = transport_events.fetch(0).fetch(:events).fetch(0)
116
-
117
- expect(output.string).to include('payment retry failed')
118
- expect(event.fetch('event_type')).to eq('log_event')
119
- expect(event.fetch('payload')).to include('message' => 'payment retry failed', 'level' => 'warning')
120
- expect(event.fetch('payload').fetch('attributes')).to include('logger_name' => 'checkout')
121
- end
122
-
123
- it 'registers a semantic logger appender when SemanticLogger is available' do
124
- transport_events = []
125
- transport = Class.new do
126
- define_method(:initialize) do |transport_events|
127
- @transport_events = transport_events
128
- end
129
-
130
- define_method(:call) do |request|
131
- @transport_events << request
132
- DebugBundle::Transport::Result.new(status_code: 202)
133
- end
134
- end.new(transport_events)
135
-
136
- semantic_logger = Module.new do
137
- class << self
138
- attr_reader :appenders
139
-
140
- def add_appender(appender:)
141
- @appenders ||= []
142
- @appenders << appender
143
- end
144
- end
145
- end
146
-
147
- stub_const('SemanticLogger', semantic_logger)
148
-
149
- log_entry = Struct.new(:message, :level, :name, :payload, :tags).new(
150
- 'semantic failure',
151
- :error,
152
- 'semantic-checkout',
153
- { order_id: 123 },
154
- %w[payments critical]
155
- )
156
-
157
- client = DebugBundle::Client.new(project_token: 'dbundle_proj_test', transport: transport)
158
- appender = client.capture_semantic_logger
159
- appender.log(log_entry)
160
- client.flush
161
-
162
- event = transport_events.fetch(0).fetch(:events).fetch(0)
163
-
164
- expect(SemanticLogger.appenders).to include(appender)
165
- expect(event.fetch('payload')).to include('message' => 'semantic failure', 'level' => 'error')
166
- expect(event.fetch('payload').fetch('attributes')).to include(
167
- 'logger_name' => 'semantic-checkout',
168
- 'payload' => { 'order_id' => 123 },
169
- 'tags' => %w[payments critical]
170
- )
171
- end
172
-
173
- it 'guards semantic logger capture against recursive SDK logging' do
174
- capture_count = 0
175
- appender = nil
176
- log_entry = Struct.new(:message, :level, :name, :payload, :tags).new(
177
- 'semantic recursion check',
178
- :error,
179
- 'semantic-checkout',
180
- {},
181
- []
182
- )
183
- recursive_client = Class.new do
184
- define_method(:initialize) do |on_capture|
185
- @on_capture = on_capture
186
- end
187
-
188
- define_method(:capture_log) do |_message, level:, context:|
189
- @on_capture.call(level, context)
190
- end
191
- end.new(lambda do |_level, _context|
192
- capture_count += 1
193
- appender.log(log_entry)
194
- end)
195
-
196
- appender = DebugBundle::Logging::SemanticLoggerAppender.new(client: recursive_client)
197
-
198
- appender.log(log_entry)
199
-
200
- expect(capture_count).to eq(1)
201
- end
202
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require 'rack'
5
- require 'rack/mock'
6
-
7
- RSpec.describe 'Rack integration' do
8
- let(:transport_events) { [] }
9
- let(:transport) do
10
- Class.new do
11
- define_method(:initialize) do |transport_events|
12
- @transport_events = transport_events
13
- end
14
-
15
- define_method(:call) do |request|
16
- @transport_events << request
17
- DebugBundle::Transport::Result.new(status_code: 202)
18
- end
19
- end.new(transport_events)
20
- end
21
-
22
- it 'captures request context in a real Rack builder stack' do
23
- client = DebugBundle::Client.new(project_token: 'dbundle_proj_test', service: 'rack-checkout', transport: transport)
24
- app = Rack::Builder.new do
25
- use DebugBundle::Rack::Middleware, client: client
26
- run ->(_env) { [503, { 'Content-Type' => 'text/plain' }, ['ok']] }
27
- end.to_app
28
-
29
- response = Rack::MockRequest.new(app).get(
30
- '/checkout?cart_id=123',
31
- 'HTTP_X_REQUEST_ID' => 'req-rack',
32
- 'HTTP_X_DEBUGBUNDLE_TRACE_ID' => 'trace-rack'
33
- )
34
-
35
- client.flush
36
- event = transport_events.fetch(0).fetch(:events).fetch(0)
37
-
38
- expect(response.status).to eq(503)
39
- expect(response.body).to eq('ok')
40
- expect(event.fetch('event_type')).to eq('request_event')
41
- expect(event.fetch('correlation')).to include('request_id' => 'req-rack', 'trace_id' => 'trace-rack')
42
- expect(event.fetch('payload')).to include('path' => '/checkout', 'method' => 'GET', 'response_status' => 503)
43
- end
44
- end
@@ -1,210 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require 'base64'
5
- require 'json'
6
- require 'openssl'
7
-
8
- RSpec.describe DebugBundle::Rack::Middleware do
9
- let(:transport_events) { [] }
10
- let(:client) do
11
- transport = Class.new do
12
- define_method(:initialize) do |transport_events|
13
- @transport_events = transport_events
14
- end
15
-
16
- define_method(:call) do |request|
17
- @transport_events << request
18
- DebugBundle::Transport::Result.new(status_code: 202)
19
- end
20
- end.new(transport_events)
21
-
22
- config_fetcher = lambda do |_etag|
23
- {
24
- status_code: 200,
25
- etag: 'rack-etag',
26
- body: {
27
- capture_policy: {
28
- preset: 'balanced',
29
- capture_logs: 'warning',
30
- capture_request_events: 'all',
31
- capture_breadcrumbs: 'exception_only',
32
- capture_probe_events: 'buffer_only',
33
- immediate_client_error_statuses: []
34
- }
35
- }
36
- }
37
- end
38
-
39
- DebugBundle::Client.new(
40
- project_token: 'dbundle_proj_test',
41
- service: 'checkout-api',
42
- transport: transport,
43
- config_fetcher: config_fetcher
44
- )
45
- end
46
-
47
- it 'captures request metadata and preserves the response' do
48
- app = lambda do |_env|
49
- [201, { 'Content-Type' => 'application/json', 'Set-Cookie' => 'secret' }, ['ok']]
50
- end
51
-
52
- middleware = described_class.new(app, client: client)
53
- status, headers, body = middleware.call(
54
- 'REQUEST_METHOD' => 'POST',
55
- 'PATH_INFO' => '/checkout',
56
- 'QUERY_STRING' => 'cart_id=123',
57
- 'HTTP_X_REQUEST_ID' => 'req-1',
58
- 'HTTP_X_DEBUGBUNDLE_TRACE_ID' => 'trace-1',
59
- 'HTTP_AUTHORIZATION' => 'Bearer secret',
60
- 'HTTP_USER_AGENT' => 'RSpec'
61
- )
62
-
63
- client.flush
64
- event = transport_events.fetch(0).fetch(:events).fetch(0)
65
-
66
- expect(status).to eq(201)
67
- expect(headers).to include('Content-Type' => 'application/json')
68
- expect(body).to eq(['ok'])
69
- expect(event.fetch('event_type')).to eq('request_event')
70
- expect(event.fetch('correlation')).to include('request_id' => 'req-1', 'trace_id' => 'trace-1')
71
- expect(event.fetch('payload').fetch('headers')).to include('user-agent' => 'RSpec')
72
- expect(event.fetch('payload').fetch('headers')).not_to have_key('authorization')
73
- expect(event.fetch('payload').fetch('response_headers')).to include('content-type' => 'application/json')
74
- expect(event.fetch('payload').fetch('response_headers')).not_to have_key('set-cookie')
75
- end
76
-
77
- it 'captures exceptions and re-raises them' do
78
- app = lambda do |_env|
79
- raise 'checkout failure'
80
- end
81
-
82
- middleware = described_class.new(app, client: client)
83
-
84
- expect do
85
- middleware.call(
86
- 'REQUEST_METHOD' => 'GET',
87
- 'PATH_INFO' => '/checkout',
88
- 'QUERY_STRING' => '',
89
- 'HTTP_X_REQUEST_ID' => 'req-2'
90
- )
91
- end.to raise_error(RuntimeError, 'checkout failure')
92
-
93
- client.flush
94
- event = transport_events.fetch(0).fetch(:events).fetch(0)
95
-
96
- expect(event.fetch('event_type')).to eq('backend_exception')
97
- expect(event.fetch('payload')).to include('handled' => false)
98
- expect(event.fetch('correlation')).to include('request_id' => 'req-2')
99
- end
100
-
101
- it 'activates trigger-token probe shipping for a single request' do
102
- trigger_key = 'trigger-secret'
103
- expires_at = (Time.now.utc + 60).iso8601
104
- payload_json = JSON.generate(
105
- activation_id: 'probe-1',
106
- label_pattern: 'checkout.*',
107
- service: '*',
108
- environment: '*',
109
- trigger_expires_at: expires_at
110
- )
111
- payload_segment = Base64.urlsafe_encode64(payload_json, padding: false)
112
- signature = OpenSSL::HMAC.digest('sha256', trigger_key, payload_segment)
113
- token = "dbundle_probe_#{payload_segment}.#{Base64.urlsafe_encode64(signature, padding: false)}"
114
-
115
- transport = Class.new do
116
- define_method(:initialize) do |transport_events|
117
- @transport_events = transport_events
118
- end
119
-
120
- define_method(:call) do |request|
121
- @transport_events << request
122
- DebugBundle::Transport::Result.new(status_code: 202)
123
- end
124
- end.new(transport_events)
125
-
126
- config_fetcher = lambda do |_etag|
127
- {
128
- status_code: 200,
129
- etag: 'rack-trigger-etag',
130
- body: {
131
- probes_enabled: true,
132
- remote_probes_enabled: true,
133
- trigger_token_key: trigger_key,
134
- capture_policy: {
135
- preset: 'balanced',
136
- capture_logs: 'warning',
137
- capture_request_events: 'all',
138
- capture_breadcrumbs: 'exception_only',
139
- capture_probe_events: 'buffer_only',
140
- immediate_client_error_statuses: []
141
- }
142
- }
143
- }
144
- end
145
-
146
- trigger_client = DebugBundle::Client.new(
147
- project_token: 'dbundle_proj_test',
148
- service: 'checkout-api',
149
- transport: transport,
150
- config_fetcher: config_fetcher
151
- )
152
-
153
- app = lambda do |_env|
154
- trigger_client.probe('checkout.tax', { region: 'us-east-1' })
155
- [200, { 'Content-Type' => 'application/json' }, ['ok']]
156
- end
157
-
158
- middleware = described_class.new(app, client: trigger_client)
159
- middleware.call(
160
- 'REQUEST_METHOD' => 'GET',
161
- 'PATH_INFO' => '/checkout',
162
- 'QUERY_STRING' => '',
163
- 'HTTP_X_DEBUGBUNDLE_PROBE_TRIGGER' => token,
164
- 'HTTP_X_REQUEST_ID' => 'req-trigger'
165
- )
166
-
167
- trigger_client.flush
168
- events = transport_events.fetch(0).fetch(:events)
169
- probe_event = events.find { |entry| entry.fetch('event_type') == 'probe_event' }
170
-
171
- expect(probe_event).not_to be_nil
172
- expect(probe_event.fetch('payload')).to include(
173
- 'label' => 'checkout.tax',
174
- 'activation_id' => 'probe-1',
175
- 'probe_label_pattern' => 'checkout.*'
176
- )
177
- end
178
-
179
- it 'captures Rails route, controller, and action metadata when present' do
180
- app = lambda do |_env|
181
- [422, { 'Content-Type' => 'application/json' }, ['invalid']]
182
- end
183
-
184
- middleware = described_class.new(app, client: client)
185
- middleware.call(
186
- 'REQUEST_METHOD' => 'POST',
187
- 'PATH_INFO' => '/patients/123/checkouts',
188
- 'QUERY_STRING' => '',
189
- 'action_dispatch.request_id' => 'req-rails',
190
- 'action_dispatch.route_uri_pattern' => '/patients/:patient_id/checkouts',
191
- 'action_dispatch.request.parameters' => {
192
- 'controller' => 'checkouts',
193
- 'action' => 'create'
194
- }
195
- )
196
-
197
- client.flush
198
- event = transport_events.fetch(0).fetch(:events).fetch(0)
199
- payload = event.fetch('payload')
200
- context = event.fetch('context')
201
-
202
- expect(payload).to include(
203
- 'route_template' => '/patients/:patient_id/checkouts'
204
- )
205
- expect(context).to include(
206
- 'controller' => 'checkouts',
207
- 'action' => 'create'
208
- )
209
- end
210
- end
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require 'json'
5
- require 'logger'
6
- require 'rack/test'
7
- require 'rails'
8
- require 'debugbundle/rails'
9
-
10
- RSpec.describe 'Rails Railtie integration' do
11
- include Rack::Test::Methods
12
-
13
- def app
14
- @app ||= build_rails_app
15
- end
16
-
17
- after do
18
- @app = nil
19
- DebugBundle.client = nil
20
- Rails.instance_variable_set(:@application, nil)
21
- end
22
-
23
- it 'mounts the configured relay route and forwards browser events' do
24
- post '/relay/browser',
25
- JSON.generate('batch' => [browser_event]),
26
- {
27
- 'CONTENT_TYPE' => 'application/json',
28
- 'HTTP_HOST' => 'app.example.com',
29
- 'HTTP_ORIGIN' => 'https://app.example.com'
30
- }
31
-
32
- expect(last_response.status).to eq(202)
33
- expect(last_response.headers.fetch('Content-Type')).to include('application/json')
34
- expect(JSON.parse(last_response.body)).to include('accepted' => 1, 'rejected' => 0)
35
- expect(@transport_events.fetch(0).fetch(:events).fetch(0).fetch('service')).to include(
36
- 'name' => 'rails-checkout',
37
- 'environment' => 'production'
38
- )
39
-
40
- post '/debugbundle/browser',
41
- JSON.generate('batch' => [browser_event]),
42
- {
43
- 'CONTENT_TYPE' => 'application/json',
44
- 'HTTP_HOST' => 'app.example.com',
45
- 'HTTP_ORIGIN' => 'https://app.example.com'
46
- }
47
-
48
- expect(last_response.status).to eq(404)
49
- end
50
-
51
- def build_rails_app
52
- @transport_events = []
53
- forward_transport = Class.new do
54
- define_method(:initialize) do |transport_events|
55
- @transport_events = transport_events
56
- end
57
-
58
- define_method(:call) do |request|
59
- @transport_events << request
60
- DebugBundle::Transport::Result.new(status_code: 202)
61
- end
62
- end.new(@transport_events)
63
-
64
- app_class = Class.new(Rails::Application) do
65
- config.root = File.expand_path('..', __dir__)
66
- config.eager_load = false
67
- config.secret_key_base = 'test-secret'
68
- config.logger = Logger.new(nil)
69
- end
70
-
71
- app_class.config.hosts << 'app.example.com' if app_class.config.respond_to?(:hosts)
72
- app_class.config.debugbundle.project_token = 'dbundle_proj_test'
73
- app_class.config.debugbundle.service = 'rails-checkout'
74
- app_class.config.debugbundle.environment = 'production'
75
- app_class.config.debugbundle.relay_durable_write = false
76
- app_class.config.debugbundle.relay_allowed_origins = ['https://app.example.com']
77
- app_class.config.debugbundle.relay_forward_transport = forward_transport
78
- app_class.config.debugbundle.relay_path = '/relay/browser'
79
- app_class.initialize!
80
- app_class
81
- end
82
-
83
- def browser_event
84
- {
85
- 'schema_version' => '2026-03-01',
86
- 'event_id' => 'evt-1',
87
- 'event_type' => 'frontend_exception',
88
- 'sdk_name' => '@debugbundle/sdk-browser',
89
- 'sdk_version' => '0.1.0',
90
- 'occurred_at' => Time.now.utc.iso8601,
91
- 'service' => { 'name' => 'browser-app', 'environment' => 'production' },
92
- 'correlation' => { 'trace_id' => 'trace-1' },
93
- 'payload' => { 'message' => 'boom' }
94
- }
95
- end
96
- end
@@ -1,121 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require 'json'
5
- require 'stringio'
6
-
7
- RSpec.describe DebugBundle::Rails::RelayEndpoint do
8
- let(:relay_options_class) do
9
- Struct.new(
10
- :project_mode,
11
- :project_token,
12
- :service,
13
- :environment,
14
- :relay_durable_write,
15
- :relay_allowed_origins,
16
- :relay_forward_transport,
17
- :relay_rate_limit_per_minute,
18
- :relay_handler,
19
- keyword_init: true
20
- )
21
- end
22
- let(:app_config_class) { Struct.new(:debugbundle, keyword_init: true) }
23
- let(:fake_app_class) { Struct.new(:config, keyword_init: true) }
24
-
25
- it 'builds a relay handler from Rails config and forwards browser events' do
26
- transport_events = []
27
- forward_transport = Class.new do
28
- define_method(:initialize) do |transport_events|
29
- @transport_events = transport_events
30
- end
31
-
32
- define_method(:call) do |request|
33
- @transport_events << request
34
- DebugBundle::Transport::Result.new(status_code: 202)
35
- end
36
- end.new(transport_events)
37
-
38
- app = fake_app_class.new(
39
- config: app_config_class.new(
40
- debugbundle: relay_options_class.new(
41
- project_mode: :connected,
42
- project_token: 'dbundle_proj_test',
43
- service: 'rails-checkout',
44
- environment: 'production',
45
- relay_durable_write: false,
46
- relay_allowed_origins: ['https://app.example.com'],
47
- relay_forward_transport: forward_transport,
48
- relay_rate_limit_per_minute: 10
49
- )
50
- )
51
- )
52
-
53
- endpoint = described_class.new(app: app)
54
- status, headers, body = endpoint.call(
55
- 'REQUEST_METHOD' => 'POST',
56
- 'CONTENT_TYPE' => 'application/json',
57
- 'HOST' => 'app.example.com',
58
- 'HTTP_ORIGIN' => 'https://app.example.com',
59
- 'rack.input' => StringIO.new(
60
- JSON.generate(
61
- 'batch' => [
62
- {
63
- 'schema_version' => '2026-03-01',
64
- 'event_id' => 'evt-1',
65
- 'event_type' => 'frontend_exception',
66
- 'sdk_name' => '@debugbundle/sdk-browser',
67
- 'sdk_version' => '0.1.0',
68
- 'occurred_at' => Time.now.utc.iso8601,
69
- 'service' => { 'name' => 'browser-app', 'environment' => 'production' },
70
- 'correlation' => { 'trace_id' => 'trace-1' },
71
- 'payload' => { 'message' => 'boom' }
72
- }
73
- ]
74
- )
75
- )
76
- )
77
-
78
- expect(status).to eq(202)
79
- expect(headers).to include('Content-Type' => 'application/json')
80
- expect(JSON.parse(body.join)).to include('accepted' => 1, 'rejected' => 0)
81
- expect(transport_events.fetch(0).fetch(:events).fetch(0).fetch('service')).to include(
82
- 'name' => 'rails-checkout',
83
- 'environment' => 'production'
84
- )
85
- end
86
-
87
- it 'uses an explicit relay handler override when provided' do
88
- custom_handler = Class.new do
89
- attr_reader :requests
90
-
91
- def initialize
92
- @requests = []
93
- end
94
-
95
- def handle(request)
96
- @requests << request
97
- DebugBundle::Relay::Response.new(status: 202, body: { 'accepted' => 0, 'rejected' => 0, 'errors' => [] })
98
- end
99
- end.new
100
-
101
- app = fake_app_class.new(
102
- config: app_config_class.new(
103
- debugbundle: relay_options_class.new(
104
- relay_handler: custom_handler
105
- )
106
- )
107
- )
108
-
109
- endpoint = described_class.new(app: app)
110
- status, = endpoint.call(
111
- 'REQUEST_METHOD' => 'POST',
112
- 'CONTENT_TYPE' => 'application/json',
113
- 'HOST' => 'example.com',
114
- 'HTTP_ORIGIN' => 'https://example.com',
115
- 'rack.input' => StringIO.new('{"batch":[]}')
116
- )
117
-
118
- expect(status).to eq(202)
119
- expect(custom_handler.requests.fetch(0)).to include(method: 'POST')
120
- end
121
- end