module Setsuzoku::DynamicSpecHelper

Public Instance Methods

register_all_plugin_tests_and_stubs(plugin_class: self.described_class, subclasses_to_register: plugin_class.subclasses, registering_instance: nil, stub_directory: nil) click to toggle source
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
register_plugin_tests_and_stubs(plugin_class, registering_instance: nil, stub_directory: nil) click to toggle source

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
stub_all_plugin_methods(registering_class = self.described_class) click to toggle source
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_plugin_methods_for_spec(registering_class = self.described_class) click to toggle source

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

define_stub_plugin_method(plugin_class, action_name, spec, always_stub = false) click to toggle source
# 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
get_url_and_body(plugin, propz, stub_directory, action_name, url_params = nil) click to toggle source
# 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
perform_register(plugin_class, register_tests = true, registering_instance: nil, subclasses_to_register: nil, stub_directory: nil) click to toggle source

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
register_stub(plugin, method_name, stubs, registering_instance: nil, stub_directory: nil) click to toggle source

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
register_tests_and_get_stubs(plugin, register_tests = true) click to toggle source

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