module Setsuzoku::DynamicSpecHelper
Public Instance Methods
EXTERNAL PLUGIN SPEC HELPERS ###
These specs are designed to test and ensure that a plugin's implementations uphold their contract. Additionally they ensure that a plugin's blackbox stubbing also upholds their contract.
This will register plugin specs for a base plugin class that defines its interfaces.
It will register specs to ensure the base plugin's black box stubs are correct.
It will also register specs for all implementations of the plugin to ensure the implementations are valid. (This is similar to register_plugin_tests_and_stubs
)
# File lib/setsuzoku/rspec/dynamic_spec_helper.rb, line 22 def register_all_plugin_tests_and_stubs(plugin_class: self.described_class, subclasses_to_register: plugin_class.subclasses, registering_instance: nil, stub_directory: nil) context "*Dynamic Specs* for #{plugin_class.name}: Stub Definitions" do let(:plugin){ plugin_class.new } plugin_class.spec_definitions.each do |action_name, spec| define_stub_plugin_method(plugin_class, action_name, spec) it(spec[:name], action_name: action_name, &spec[:spec]) end end perform_register(plugin_class, registering_instance: registering_instance, subclasses_to_register: subclasses_to_register, stub_directory: stub_directory) end
This will register plugin specs for a single plugin class. The specs will test the plugin to ensure its implementations are valid.
# File lib/setsuzoku/rspec/dynamic_spec_helper.rb, line 36 def register_plugin_tests_and_stubs(plugin_class, registering_instance: nil, stub_directory: nil) perform_register(plugin_class, registering_instance: registering_instance, stub_directory: stub_directory) end
APPLICATION SPEC HELPERS ###
These stubs completely black box the plugin's functionality and returns a stubbed response without invoking the plugin's method.
Stub all plugin interface methods at a context level for an application class that registers a plugin. This also defines a before each to execute the stubs.
@param registering_class [Array/Class] the application's class/es that register a plugin and need methods stubbed.
@return void
# File lib/setsuzoku/rspec/dynamic_spec_helper.rb, line 54 def stub_all_plugin_methods(registering_class = self.described_class) registering_class = [registering_class] unless registering_class.is_a?(Array) registering_class.each do |klass| plugin_class = klass.plugin_context[:plugin_class] plugin_class.spec_definitions.each do |action_name, spec| define_stub_plugin_method(plugin_class, action_name, spec, true) end end end
Stub all plugin interface methods for an application class that registers a plugin for a single spec.
@param registering_class [Array/Class] the application's class/es that register a plugin and need methods stubbed.
@return void
# File lib/setsuzoku/rspec/dynamic_spec_helper.rb, line 69 def stub_plugin_methods_for_spec(registering_class = self.described_class) plugin_class = registering_class.plugin_context[:plugin_class] plugin_class.spec_definitions.each do |action_name, spec| plugin_class.any_instance.stub(action_name).and_return(spec[:stub][:response]) # a plugin spec might have some dynamic data, e.g. certain methods are called with plugin specific args. if spec[:stub][:dynamic_methods] spec[:stub][:dynamic_methods].each do |meth, resp| #how can we ignore the call count instead of just putting a high limit? plugin_class.any_instance.stub(meth).and_return(resp) end end end end
Private Instance Methods
# File lib/setsuzoku/rspec/dynamic_spec_helper.rb, line 210 def define_stub_plugin_method(plugin_class, action_name, spec, always_stub = false) stub_plugin_method_name = :"stub_plugin_method_#{action_name}" define_method stub_plugin_method_name do #TODO: let this use the appropriate rspec instance stub method #TODO: Does it make sense to have the stub with the method? or should it be in a json file we parse? plugin_class.any_instance.stub(action_name).and_return(spec[:stub][:response]) # a plugin spec might have some dynamic data, e.g. certain methods are called with plugin specific args. if spec[:stub][:dynamic_methods] spec[:stub][:dynamic_methods].each do |meth, resp| #how can we ignore the call count instead of just putting a high limit? plugin_class.any_instance.stub(meth).and_return(resp) end end end if always_stub before :each do send(stub_plugin_method_name) end else before :each, action_name: action_name do send(stub_plugin_method_name) end end end
# File lib/setsuzoku/rspec/dynamic_spec_helper.rb, line 235 def get_url_and_body(plugin, propz, stub_directory, action_name, url_params = nil) body = nil url = propz[:url] format = propz[:request_format].to_s.include?('x-www-form') || propz[:request_format] == :file ? :json : propz[:request_format] begin body = File.read("#{Dir.pwd}/spec/support/setsuzoku/#{stub_directory}/#{action_name}_request.#{format}") body.squish! case format when :xml body.gsub!('> <', '><') when :json body.gsub!(' "', '"') body.gsub!('" ', '"') body.gsub!(': ', ':') body.gsub!('{ ', '{') body.gsub!(' }', '}') end # We will convert these to url params when making requests. need to replicate that. req_params = case format when :json temp_body = JSON.parse(body) if url_params url_params.each{ |k,v| temp_body.delete(k.to_s) } end temp_body else # will we need to parse xml here? req_params = {} body end if url.match?(/.*\{\{.*\}\}/) url, body = plugin.api_strategy.replace_dynamic_vars(full_url: url, req_params: req_params) end if propz[:request_format] == :file body = body.to_json if body.is_a?(Hash) elsif format == :json #faraday sorts hash keys for some reason body = body.to_json if body.is_a?(Hash) body = JSON.parse(body).sort.to_h end [url, body] rescue [url, nil] end end
END APPLICATION SPEC HELPERS ###
# File lib/setsuzoku/rspec/dynamic_spec_helper.rb, line 87 def perform_register(plugin_class, register_tests = true, registering_instance: nil, subclasses_to_register: nil, stub_directory: nil) #iterate over all api methods defined by the plugin and create a context and tests for each method # in register_tests_and_get_stubs, and return the stub defs up to this method. plugin_classes = subclasses_to_register || [plugin_class] plugin_classes.each do |plugin_class| p = plugin_class.new(registering_instance: registering_instance) register_tests_and_get_stubs(p, register_tests).each do |method_name, stubs| register_stub(p, method_name, stubs, registering_instance: registering_instance, stub_directory: stub_directory) end end end
This method will then iterate over stub defs and actually define the stubs here in the stub_defs array. It will then lastly collect all the stubs in stub_defs array and do call to define the stub.
# File lib/setsuzoku/rspec/dynamic_spec_helper.rb, line 149 def register_stub(plugin, method_name, stubs, registering_instance: nil, stub_directory: nil) stub_defs = [] stubs.each do |provider, stub| stub.each do |action_name, props| props.each do |propz| plugin.api_strategy.current_action = action_name default_headers = plugin.api_strategy.request_options(propz[:request_format])[:headers] || { 'Content-Type' => "application/#{propz[:request_format]}" } auth_headers = plugin.auth_strategy.auth_headers || {} if plugin.auth_strategy.is_a?(Setsuzoku::Service::WebService::AuthStrategies::BasicAuthStrategy) basic_auth = auth_headers[:authorization][:basic_auth] auth_header = { 'Authorization' => "Basic #{Base64.encode64("#{basic_auth[:username]}:#{basic_auth[:password]}").gsub("\n", '')}" } elsif plugin.auth_strategy.is_a?(Setsuzoku::Service::WebService::AuthStrategies::OAuthStrategy) auth_header = { 'Authorization' => "Bearer #{auth_headers[:authorization][:token]}" } end default_headers.merge!(auth_header) if auth_header headers = propz[:headers] ? [propz[:headers]] : [default_headers] stub_directory ||= "#{plugin.class.plugin_namespace}/#{provider}" headers.each do |header| header = Faraday.new.headers .merge({'Accept'=>'*/*','Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3'}) .merge(header.deep_stringify_keys) if propz[:stub_data] # we have specified specific stub data for this action propz[:stub_data].each do |stub_datum| url, body = get_url_and_body(plugin, propz, stub_directory, action_name, stub_datum[:url_params]) stub_defs << proc do stub_request(propz[:method], url) .with( headers: header, body: body, query: stub_datum[:url_params] ) .to_return(status: 200, headers: {}, body: File.read("#{Dir.pwd}/spec/support/setsuzoku/#{plugin.class.plugin_namespace}/#{provider}/#{action_name}.#{propz[:response_format]}")) end end else # this is a generic action so the stub uses the default structure url, body = get_url_and_body(plugin, propz, stub_directory, action_name) stub_defs << proc do stub_request(propz[:method], url) .with( headers: header, body: body ) .to_return(status: 200, headers: {}, body: File.read("#{Dir.pwd}/spec/support/setsuzoku/#{plugin.class.plugin_namespace}/#{provider}/#{action_name}.#{propz[:response_format]}")) end end end end end end #iterate over all the stub_requests collected above and call them to stub them. define_method method_name do |spec_instance = self| stub_defs.each do |stub_def| instance_exec(&stub_def) end end end
This will iterate over every registered plugin defined in the including helper and generate formatted hashes of stub configs.
register_stubs uses these config objects to define stubs dynamically
# File lib/setsuzoku/rspec/dynamic_spec_helper.rb, line 103 def register_tests_and_get_stubs(plugin, register_tests = true) definitions = {} specs = [] action_name_namespace = :"#{plugin.name.gsub(' ', '').underscore}" stub_name = :"stub_#{action_name_namespace}" definitions[stub_name] = { action_name_namespace => {} } plugin.api_actions.each do |api_action| action_name, options = api_action plugin.api_strategy.current_action = action_name request_props = plugin.api_strategy.get_request_properties(action_name: action_name, for_stub: true) (definitions[stub_name][action_name_namespace][action_name] ||= []) << { method: request_props[:request_method], request_format: request_props[:request_format], response_format: request_props[:response_format], url: request_props[:formatted_full_url], stub_data: request_props[:stub_data] } # include the generic interface specs for this plugin's action specs << { spec: plugin.class.spec_definitions[action_name], action_name: action_name } end if register_tests context "*Dynamic Specs* for #{plugin.class.name}: Interface Implementations" do #we will instantiate the plugin, any registered_instance dependent methods/data must be stubbed by the plugin because of this! let!(:plugin) { plugin } before :each do send(stub_name, self) end specs.each do |spec_def| if spec_def[:spec] && spec_def[:spec][:spec] it(spec_def[:spec][:name], &spec_def[:spec][:spec]) else it "Undefined spec for #{spec_def[:action_name]}" do raise NotImplemented, "Spec for action: #{spec_def[:action_name]} is not defined. Expected #{plugin.class} or its parent to define it." end end end end end definitions end