Skip to content

Commit 641f4f1

Browse files
authored
Stub config prototype (#3204)
1 parent 7958b5a commit 641f4f1

File tree

8 files changed

+57
-63
lines changed

8 files changed

+57
-63
lines changed

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ group :build do
4242
end
4343

4444
group :development do
45+
gem 'byebug', platforms: :ruby
4546
gem 'rubocop', '1.28.0'
4647
end
4748

build_tools/aws-sdk-code-generator/spec/interfaces/client/bearer_authorization_spec.rb

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
it 'sets the Authorization header' do
2626
client.stub_responses(:bearer_auth, -> (context) {
2727
expect(context.http_request.headers['Authorization']).to eq('Bearer token')
28+
{}
2829
})
2930
client.bearer_auth
3031
end
@@ -34,6 +35,7 @@
3435
it 'sets the Authorization header' do
3536
client.stub_responses(:sigv_4_auth, -> (context) {
3637
expect(context.http_request.headers['Authorization']).to include('AWS4-HMAC-SHA256')
38+
{}
3739
})
3840
client.sigv_4_auth
3941
end
@@ -43,6 +45,7 @@
4345
it 'does not set the Authorization header' do
4446
client.stub_responses(:no_auth, -> (context) {
4547
expect(context.http_request.headers.key?('Authorization')).to be_falsey
48+
{}
4649
})
4750
client.no_auth
4851
end

gems/aws-sdk-core/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Unreleased Changes
22
------------------
33

4+
* Issue - Convert stubs at request time.
5+
46
3.220.0 (2025-03-04)
57
------------------
68

gems/aws-sdk-core/lib/aws-sdk-core/client_stubs.rb

+22-48
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,11 @@ module ClientStubs
1515

1616
# @api private
1717
def setup_stubbing
18-
@stubs = {}
19-
@stub_mutex = Mutex.new
2018
if Hash === @config.stub_responses
2119
@config.stub_responses.each do |operation_name, stubs|
2220
apply_stubs(operation_name, Array === stubs ? stubs : [stubs])
2321
end
2422
end
25-
26-
# When a client is stubbed allow the user to access the requests made
27-
requests = @api_requests = []
28-
requests_mutex = @requests_mutex = Mutex.new
29-
self.handle do |context|
30-
requests_mutex.synchronize do
31-
requests << {
32-
operation_name: context.operation_name,
33-
params: context.params,
34-
context: context
35-
}
36-
end
37-
@handler.call(context)
38-
end
3923
end
4024

4125
# Configures what data / errors should be returned from the named operation
@@ -175,7 +159,7 @@ def setup_stubbing
175159
# on a client that has not enabled response stubbing via
176160
# `:stub_responses => true`.
177161
def stub_responses(operation_name, *stubs)
178-
if config.stub_responses
162+
if @config.stub_responses
179163
apply_stubs(operation_name, stubs.flatten)
180164
else
181165
msg = 'stubbing is not enabled; enable stubbing in the constructor '\
@@ -194,12 +178,12 @@ def stub_responses(operation_name, *stubs)
194178
# @raise [NotImplementedError] Raises `NotImplementedError` when the client
195179
# is not stubbed.
196180
def api_requests(options = {})
197-
if config.stub_responses
198-
@requests_mutex.synchronize do
181+
if @config.stub_responses
182+
@config.api_requests_mutex.synchronize do
199183
if options[:exclude_presign]
200-
@api_requests.reject {|req| req[:context][:presigned_url] }
184+
@config.api_requests.reject {|req| req[:context][:presigned_url] }
201185
else
202-
@api_requests
186+
@config.api_requests
203187
end
204188
end
205189
else
@@ -228,54 +212,44 @@ def api_requests(options = {})
228212
# @return [Structure] Returns a stubbed response data structure. The
229213
# actual class returned will depend on the given `operation_name`.
230214
def stub_data(operation_name, data = {})
231-
Stubbing::StubData.new(config.api.operation(operation_name)).stub(data)
215+
Stubbing::StubData.new(@config.api.operation(operation_name)).stub(data)
232216
end
233217

234218
# @api private
235219
def next_stub(context)
236220
operation_name = context.operation_name.to_sym
237-
stub = @stub_mutex.synchronize do
238-
stubs = @stubs[operation_name] || []
221+
stub = @config.stubs_mutex.synchronize do
222+
stubs = @config.stubs[operation_name] || []
239223
case stubs.length
240-
when 0 then default_stub(operation_name)
224+
when 0 then stub_data(operation_name)
241225
when 1 then stubs.first
242226
else stubs.shift
243227
end
244228
end
245-
Proc === stub ? convert_stub(operation_name, stub.call(context)) : stub
229+
stub = convert_stub(operation_name, stub, context)
230+
stub[:mutex] = Mutex.new
231+
stub
246232
end
247233

248234
private
249235

250-
def default_stub(operation_name)
251-
stub = stub_data(operation_name)
252-
http_response_stub(operation_name, stub)
236+
def apply_stubs(operation_name, stubs)
237+
@config.stubs_mutex.synchronize do
238+
@config.stubs[operation_name.to_sym] = stubs
239+
end
253240
end
254241

255242
# This method converts the given stub data and converts it to a
256243
# HTTP response (when possible). This enables the response stubbing
257244
# plugin to provide a HTTP response that triggers all normal events
258245
# during response handling.
259-
def apply_stubs(operation_name, stubs)
260-
@stub_mutex.synchronize do
261-
@stubs[operation_name.to_sym] = stubs.map do |stub|
262-
convert_stub(operation_name, stub)
263-
end
264-
end
265-
end
266-
267-
def convert_stub(operation_name, stub)
268-
stub = case stub
269-
when Proc then stub
246+
def convert_stub(operation_name, stub, context)
247+
case stub
248+
when Proc then convert_stub(operation_name, stub.call(context), context)
270249
when Exception, Class then { error: stub }
271250
when String then service_error_stub(stub)
272-
when Hash then http_response_stub(operation_name, stub)
273-
else { data: stub }
274-
end
275-
if Hash === stub
276-
stub[:mutex] = Mutex.new
251+
else http_response_stub(operation_name, stub)
277252
end
278-
stub
279253
end
280254

281255
def service_error_stub(error_code)
@@ -299,14 +273,14 @@ def hash_to_http_resp(data)
299273
end
300274

301275
def data_to_http_resp(operation_name, data)
302-
api = config.api
276+
api = @config.api
303277
operation = api.operation(operation_name)
304278
ParamValidator.new(operation.output, input: false).validate!(data)
305279
protocol_helper.stub_data(api, operation, data)
306280
end
307281

308282
def protocol_helper
309-
case config.api.metadata['protocol']
283+
case @config.api.metadata['protocol']
310284
when 'json' then Stubbing::Protocols::Json
311285
when 'rest-json' then Stubbing::Protocols::RestJson
312286
when 'rest-xml' then Stubbing::Protocols::RestXml

gems/aws-sdk-core/lib/aws-sdk-core/plugins/stub_responses.rb

+24-8
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,16 @@ class StubResponses < Seahorse::Client::Plugin
2929
end
3030
end
3131

32+
option(:stubs) { {} }
33+
option(:stubs_mutex) { Mutex.new }
34+
option(:api_requests) { [] }
35+
option(:api_requests_mutex) { Mutex.new }
36+
3237
def add_handlers(handlers, config)
33-
handlers.add(Handler, step: :send) if config.stub_responses
38+
return unless config.stub_responses
39+
40+
handlers.add(ApiRequestsHandler)
41+
handlers.add(StubbingHandler, step: :send)
3442
end
3543

3644
def after_initialize(client)
@@ -46,8 +54,20 @@ def after_initialize(client)
4654
end
4755
end
4856

49-
class Handler < Seahorse::Client::Handler
57+
class ApiRequestsHandler < Seahorse::Client::Handler
58+
def call(context)
59+
context.config.api_requests_mutex.synchronize do
60+
context.config.api_requests << {
61+
operation_name: context.operation_name,
62+
params: context.params,
63+
context: context
64+
}
65+
end
66+
@handler.call(context)
67+
end
68+
end
5069

70+
class StubbingHandler < Seahorse::Client::Handler
5171
def call(context)
5272
span_wrapper(context) do
5373
stub_responses(context)
@@ -57,14 +77,10 @@ def call(context)
5777
private
5878

5979
def stub_responses(context)
60-
stub = context.client.next_stub(context)
6180
resp = Seahorse::Client::Response.new(context: context)
6281
async_mode = context.client.is_a? Seahorse::Client::AsyncBase
63-
if Hash === stub && stub[:mutex]
64-
stub[:mutex].synchronize { apply_stub(stub, resp, async_mode) }
65-
else
66-
apply_stub(stub, resp, async_mode)
67-
end
82+
stub = context.client.next_stub(context)
83+
stub[:mutex].synchronize { apply_stub(stub, resp, async_mode) }
6884

6985
if async_mode
7086
Seahorse::Client::AsyncResponse.new(

gems/aws-sdk-core/spec/aws/plugins/checksum_algorithm_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ module Plugins
255255
test_case['expectTrailers'].each do |key, value|
256256
expect(read_body).to include("#{key}:#{value}")
257257
end
258-
context
258+
{}
259259
end)
260260

261261
client.http_checksum_streaming_operation(**options)

gems/aws-sdk-core/spec/aws/plugins/request_compression_spec.rb

+2
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ def expect_uncompressed_body(resp, body)
171171
large_body.rewind
172172
uncompressed = Zlib::GzipReader.new(body)
173173
expect(uncompressed.read).to eq(large_body.read)
174+
{}
174175
end)
175176
client.operation_streaming(body: large_body)
176177
end
@@ -185,6 +186,7 @@ def expect_uncompressed_body(resp, body)
185186
body.rewind
186187
uncompressed = Zlib::GzipReader.new(body)
187188
expect(uncompressed.read).to eq(small_body)
189+
{}
188190
end)
189191
client.operation_streaming(body: small_body)
190192
end

gems/aws-sdk-dynamodb/spec/client_spec.rb

+2-6
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,10 @@ module DynamoDB
161161

162162
it 'observes the :simple_attributes configuration option' do
163163
client = Client.new(stub_responses: true, simple_attributes: false)
164+
client.stub_responses(:get_item, item: { 'id' => 'value' })
164165
expect {
165-
client.stub_responses(:get_item, item: { 'id' => 'value' })
166+
client.get_item(table_name: 'table', key: { 'id' => { s: 'value' }})
166167
}.to raise_error(ArgumentError)
167-
168-
client.stub_responses(:get_item, item: { 'id' => { s: 'value' }})
169-
resp = client.get_item(table_name: 'table', key: { 'id' => { s: 'value' }})
170-
expect(resp.item.keys).to eq(['id'])
171-
expect(resp.item['id'].s).to eq('value')
172168
end
173169

174170
end

0 commit comments

Comments
 (0)