This repository has been archived by the owner on Nov 20, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #123 from aws/stub-responses
Added support for stubbing client responses.
- Loading branch information
Showing
8 changed files
with
557 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
module Aws | ||
|
||
# This module provides the ability to specify the data and/or errors to | ||
# return when a client is using stubbed responses. Pass | ||
# `:stub_responses => true` to a client constructor to enable this | ||
# behavior. | ||
module ClientStubs | ||
|
||
def initialize(*args) | ||
@stubs = {} | ||
super | ||
end | ||
|
||
# Configures what data / errors should be returned from the named operation | ||
# when response stubbing is enabled. | ||
# | ||
# ## Basic usage | ||
# | ||
# By default, fake responses are generated. You can override the default | ||
# fake data with specific response data by passing a hash. | ||
# | ||
# # enable response stubbing in the client constructor | ||
# client = Aws::S3::Client.new(stub_responses: true) | ||
# | ||
# # specify the response data for #list_buckets | ||
# client.stub_responses(:list_buckets, buckets:[{name:'aws-sdk'}]) | ||
# | ||
# # no api calls made, stub returned | ||
# client.list_buckets.map(&:name) | ||
# #=> ['aws-sdk'] | ||
# | ||
# ## Stubbing Errors | ||
# | ||
# When stubbing is enabled, the SDK will default to generate | ||
# fake responses with placeholder values. You can override the data | ||
# returned. You can also specify errors it should raise. | ||
# | ||
# client.stub_responses(:get_object, 'NotFound') | ||
# client.get_object(bucket:'aws-sdk', key:'foo') | ||
# #=> raises Aws::S3::Errors::NotFound | ||
# | ||
# client.stub_responses(:get_object, Timeout::Error) | ||
# client.get_object(bucket:'aws-sdk', key:'foo') | ||
# #=> raises new Timeout::Error | ||
# | ||
# client.stub_responses(:get_object, RuntimeError.new('custom message')) | ||
# client.get_object(bucket:'aws-sdk', key:'foo') | ||
# #=> raises the given runtime error object | ||
# | ||
# ## Stubbing Multiple Responses | ||
# | ||
# Calling an operation multiple times will return similar responses. | ||
# You can configure multiple stubs and they will be returned in sequence. | ||
# | ||
# | ||
# client.stub_responses(:head_object, [ | ||
# 'NotFound', | ||
# { content_length: 150 }, | ||
# ]) | ||
# | ||
# client.head_object(bucket:'aws-sdk', key:'foo') | ||
# #=> raises Aws::S3::Errors::NotFound | ||
# | ||
# resp = client.head_object(bucket:'aws-sdk', key:'foo') | ||
# resp.content_length #=> 150 | ||
# | ||
# @param [Symbol] operation_name | ||
# @param [Mixed] stubs One or more responses to return from the named | ||
# operation. | ||
# @return [void] | ||
# @raise [RuntimeError] Raises a runtime error when {#stub} is called | ||
# on a client that has not enabled response stubbing via | ||
# `:stub_responses => true`. | ||
def stub_responses(operation_name, *stubs) | ||
if config.stub_responses | ||
apply_stubs(operation_name, stubs.flatten) | ||
else | ||
msg = 'stubbing is not enabled; enable stubbing in the constructor ' | ||
msg << 'with `:stub_responses => true`' | ||
raise msg | ||
end | ||
end | ||
|
||
# @api private | ||
def next_stub(operation_name) | ||
stubs = @stubs[operation_name.to_sym] || [] | ||
case stubs.length | ||
when 0 then new_stub(operation_name) | ||
when 1 then stubs.first | ||
else stubs.shift | ||
end | ||
end | ||
|
||
private | ||
|
||
# @param [Symbol] operation_name | ||
# @param [Hash, nil] data | ||
# @return [Structure] | ||
def new_stub(operation_name, data = nil) | ||
Stub.new(operation(operation_name).output).format(data || {}) | ||
end | ||
|
||
def apply_stubs(operation_name, stubs) | ||
@stubs[operation_name.to_sym] = stubs.map do |stub| | ||
case stub | ||
when Exception then stub | ||
when String then service_error_class(stub) | ||
when Hash then new_stub(operation_name, stub) | ||
else stub | ||
end | ||
end | ||
end | ||
|
||
def service_error_class(name) | ||
svc_module = Aws.const_get(self.class.name.split('::')[1]) | ||
svc_module.const_get(:Errors).const_get(name) | ||
end | ||
|
||
class Stub | ||
|
||
# @param [Seahorse::Models::Shapes::Structure] output_shape This should | ||
# be the output shape for an operation. | ||
def initialize(output_shape) | ||
@shape = output_shape | ||
end | ||
|
||
# @param [Hash] data An optional hash of data to format into the stubbed | ||
# object. | ||
def format(data = {}) | ||
if @shape.nil? | ||
empty_stub(data) | ||
else | ||
validate_data(data) | ||
stub(@shape, data) | ||
end | ||
end | ||
|
||
private | ||
|
||
def stub(shape, value) | ||
case shape | ||
when Seahorse::Model::Shapes::Structure then stub_structure(shape, value) | ||
when Seahorse::Model::Shapes::List then stub_list(shape, value || []) | ||
when Seahorse::Model::Shapes::Map then stub_map(shape, value || {}) | ||
else stub_scalar(shape, value) | ||
end | ||
end | ||
|
||
def stub_structure(shape, hash) | ||
if hash | ||
structure_obj(shape, hash) | ||
else | ||
nil | ||
end | ||
end | ||
|
||
def structure_obj(shape, hash) | ||
stubs = Structure.new(shape.member_names) | ||
shape.members.each do |member_name, member_shape| | ||
value = structure_value(shape, member_name, member_shape, hash) | ||
stubs[member_name] = stub(member_shape, value) | ||
end | ||
stubs | ||
end | ||
|
||
def structure_value(shape, member_name, member_shape, hash) | ||
if hash.key?(member_name) | ||
hash[member_name] | ||
elsif | ||
Seahorse::Model::Shapes::Structure === member_shape && | ||
shape.required.include?(member_name) | ||
then | ||
{} | ||
else | ||
nil | ||
end | ||
end | ||
|
||
def stub_list(shape, array) | ||
stubs = [] | ||
array.each do |value| | ||
stubs << stub(shape.member, value) | ||
end | ||
stubs | ||
end | ||
|
||
def stub_map(shape, value) | ||
stubs = {} | ||
value.each do |key, value| | ||
stubs[key] = stub(shape.value, value) | ||
end | ||
stubs | ||
end | ||
|
||
def stub_scalar(shape, value) | ||
if value.nil? | ||
case shape | ||
when Seahorse::Model::Shapes::String then shape.name | ||
when Seahorse::Model::Shapes::Integer then 0 | ||
when Seahorse::Model::Shapes::Float then 0.0 | ||
when Seahorse::Model::Shapes::Boolean then false | ||
when Seahorse::Model::Shapes::Timestamp then Time.now | ||
else nil | ||
end | ||
else | ||
value | ||
end | ||
end | ||
|
||
def empty_stub(data) | ||
if data.empty? | ||
Structure.new(data) | ||
else | ||
msg = 'unable to generate a stubbed response from the given data; ' | ||
msg << 'this operation does not return data' | ||
raise ArgumentError, msg | ||
end | ||
end | ||
|
||
def validate_data(data) | ||
args = [@shape, { validate_required:false }] | ||
Seahorse::Client::ParamValidator.new(*args).validate!(data) | ||
end | ||
|
||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
module Aws | ||
module Plugins | ||
|
||
# @seahorse.client.option [Boolean] :stub_responses (false) | ||
# Causes the client to return stubbed responses. By default | ||
# fake responses are generated and returned. You can specify | ||
# the response data to return or errors to raise by calling | ||
# {ClientStubs#stub_responses}. See {ClientStubs} for more information. | ||
# | ||
# ** Please note ** When response stubbing is enabled, no HTTP | ||
# requests are made, and retries are disabled. | ||
# | ||
class StubResponses < Seahorse::Client::Plugin | ||
|
||
option(:stub_responses, false) | ||
|
||
def add_handlers(handlers, config) | ||
handlers.add(Handler, step: :send) if config.stub_responses | ||
end | ||
|
||
def after_initialize(client) | ||
# disable retries when stubbing responses | ||
client.config.retry_limit = 0 if client.config.stub_responses | ||
end | ||
|
||
class Handler < Seahorse::Client::Handler | ||
|
||
def call(context) | ||
response = Seahorse::Client::Response.new(context: context) | ||
apply_stub(response, context.client.next_stub(context.operation_name)) | ||
response | ||
end | ||
|
||
private | ||
|
||
def apply_stub(resp, stub) | ||
if Exception === stub | ||
resp.error = stub | ||
elsif Class === stub && stub.ancestors.include?(Errors::ServiceError) | ||
resp.error = stub.new(resp.context, 'stubbed error') | ||
elsif Class === stub && stub.ancestors.include?(Exception) | ||
resp.error = stub.new | ||
else | ||
resp.data = stub | ||
end | ||
end | ||
|
||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.