class ReqresRspec::Collector

Constants

EXCLUDE_PARAMS

Exclude replacement in symbolized path

EXCLUDE_REQUEST_HEADER_PATTERNS

request headers contain many unnecessary information, everything that match items from this list will be stripped

EXCLUDE_RESPONSE_HEADER_PATTERNS

response headers contain many unnecessary information, everything from this list will be stripped

PARAM_IMPORTANCES

Param importances

PARAM_TYPES

Param types NOTE: make sure sub-strings go at the end

Attributes

records[RW]

Contains spec values read from rspec example, request and response

Public Class Methods

new() click to toggle source
# File lib/reqres_rspec/collector.rb, line 65
def initialize
  self.records = []
end

Public Instance Methods

collect(spec, example, request, response) click to toggle source

collects spec data for further processing

# File lib/reqres_rspec/collector.rb, line 70
def collect(spec, example, request, response)
  # TODO: remove boilerplate code
  return if request.nil? || response.nil? || !defined?(request.env)

  description = query_parameters = backend_parameters = 'not available'
  params = []
  if request.env && (request_params = request.env['action_dispatch.request.parameters'])
    if request_params['controller'] && request_params['action']
      description = get_action_description(request_params['controller'], request_params['action'])
      params = get_action_params(request_params['controller'], request_params['action'])
      query_parameters = request_params.reject { |p| %w[controller action format].include? p }
      backend_parameters = request_params.reject { |p| !%w[controller action format].include? p }
    end
  end

  ex_gr = spec.class.example.metadata[:example_group]
  section = ex_gr[:description]
  while !ex_gr.nil? do
    section = ex_gr[:description]
    ex_gr = ex_gr[:parent_example_group]
  end

  self.records << {
    filename: prepare_filename_for(spec.class.metadata),
    group: spec.class.metadata[:reqres_section] || section, # Top level example group
    title: example_title(spec, example),
    description: description,
    params: params,
    request: {
      host: request.host,
      url: request.url,
      path: request.path.to_s.gsub('%2F', '/'),
      symbolized_path: get_symbolized_path(request),
      method: request.request_method,
      query_parameters: query_parameters,
      backend_parameters: backend_parameters,
      body: request.body.read,
      content_length: request.content_length,
      content_type: request.content_type,
      headers: read_request_headers(request),
      accept: (request.accept rescue nil)
    },
    response: {
      code: response.status,
      body: response.body,
      headers: read_response_headers(response),
      format: format(response)
    }
  }

  # cleanup query params
  begin
    body_hash = JSON.parse(self.records.last[:request][:body])
    query_hash = self.records.last[:request][:query_parameters]
    diff = Hash[*((query_hash.size > body_hash.size) ? query_hash.to_a - body_hash.to_a : body_hash.to_a - query_hash.to_a).flatten]
    self.records.last[:request][:query_parameters] = diff
  rescue
  end
end
prepare_filename_for(metadata) click to toggle source
# File lib/reqres_rspec/collector.rb, line 130
def prepare_filename_for(metadata)
  description = metadata[:description]
  example_group = if metadata.key?(:example_group)
                    metadata[:example_group]
                  else
                    metadata[:parent_example_group]
                  end

  if example_group
    [prepare_filename_for(example_group), description].join('/')
  else
    description
  end.downcase.gsub(/[\W]+/, '_').gsub('__', '_').gsub(/^_|_$/, '')
end
sort() click to toggle source

sorts records alphabetically

# File lib/reqres_rspec/collector.rb, line 146
def sort
  self.records.sort! do |x,y|
    comp = x[:request][:symbolized_path] <=> y[:request][:symbolized_path]
    comp.zero? ? (x[:title] <=> y[:title]) : comp
  end
end

Private Instance Methods

cleanup_header(key) click to toggle source
# File lib/reqres_rspec/collector.rb, line 324
def cleanup_header(key)
  key.sub(/^HTTP_/, '').underscore.split('_').map(&:capitalize).join('-')
end
example_title(spec, example) click to toggle source
# File lib/reqres_rspec/collector.rb, line 155
def example_title(spec, example)
  t = prepare_description(example.metadata, :reqres_title) ||
      spec.class.example.full_description
  t.strip
end
format(response) click to toggle source
# File lib/reqres_rspec/collector.rb, line 180
def format(response)
  case response.headers['Content-Type']
  when %r{text/html}
    :html
  when %r{application/json}
    :json
  else
    :json
  end
end
get_action_comments(controller, action) click to toggle source

returns action comments taken from controller file example TODO

# File lib/reqres_rspec/collector.rb, line 229
def get_action_comments(controller, action)
  lines = File.readlines(File.join(ReqresRspec.root, 'app', 'controllers', "#{controller}_controller.rb"))

  action_line = nil
  lines.each_with_index do |line, index|
    if line.match(/\s*def #{action}/) #  def show
      action_line = index
      break
    end
  end

  if action_line
    comment_lines = []
    was_comment = true
    while action_line > 0 && was_comment
      action_line -= 1

      if lines[action_line].match(/\s*#/)
        comment_lines << lines[action_line].strip
      else
        was_comment = false
      end
    end

    comment_lines.reverse
  else
    ['not found']
  end
rescue Errno::ENOENT
  ['not found']
end
get_action_description(controller, action) click to toggle source

returns description action comments example TODO

# File lib/reqres_rspec/collector.rb, line 263
def get_action_description(controller, action)
  comment_lines = get_action_comments(controller, action)

  description = []
  comment_lines.each_with_index do |line, index|
    if line.match(/\s*#\s*@description/) # @description blah blah
      description << line.gsub(/\A\s*#\s*@description/, '').strip
      comment_lines[(index + 1)..-1].each do |multiline|
        if !multiline.match(/\s*#\s*@param/)
          description << "\n"
          description << multiline.gsub(/\A\s*#\s*/, '').strip
        else
          break
        end
      end
    end
  end

  description.join ' '
end
get_action_params(controller, action) click to toggle source

returns params action comments example TODO

# File lib/reqres_rspec/collector.rb, line 286
def get_action_params(controller, action)
  comment_lines = get_action_comments(controller, action)

  comments_raw = []
  has_param = false
  comment_lines.each do |line|
    if line.match(/\s*#\s*@param/) # @param id required Integer blah blah
      has_param = true
      comments_raw << ''
    end
    if has_param
      line = line.gsub(/\A\s*#\s*@param/, '')
      line = line.gsub(/\A\s*#\s*/, '').strip

      comments_raw.last << "\n" unless comments_raw.last.blank?
      comments_raw.last << line
    end
  end

  comments = []
  comments_raw.each do |comment|
    match_data = comment.match(/(?<name>[a-z0-9A-Z_\[\]]+)?\s*(?<required>#{PARAM_IMPORTANCES.join('|')})?\s*(?<type>#{PARAM_TYPES.join('|')})?\s*(?<description>.*)/m)

    if match_data
      comments << {
        name: match_data[:name],
        required: match_data[:required],
        type: match_data[:type],
        description: match_data[:description]
      }
    else
      comments << { description: comment }
    end
  end

  comments
end
get_symbolized_path(request) click to toggle source

replace each first occurrence of param's value in the request path

Example:

request path = /api/users/123
id = 123
symbolized path => /api/users/:id
# File lib/reqres_rspec/collector.rb, line 210
def get_symbolized_path(request)
  request_path = (request.env['REQUEST_URI'] || request.path).dup
  request_params =
    request.env['action_dispatch.request.parameters'] ||
    request.env['rack.request.form_hash'] ||
    request.env['rack.request.query_hash']

  if request_params
    request_params
      .except(*EXCLUDE_PARAMS)
      .select { |_, value| value.is_a?(String) }
      .each { |key, value| request_path.sub!("/#{value}", "/:#{key}") if value.to_s != '' }
  end

  request_path.freeze
end
prepare_description(payload, key) click to toggle source
# File lib/reqres_rspec/collector.rb, line 161
def prepare_description(payload, key)
  payload[key] &&
    ->(x) { (x.is_a?(TrueClass) || x == '') ? payload[:description] : x }.call(payload[key])
end
read_request_headers(request) click to toggle source

read and cleanup request headers returns Hash

# File lib/reqres_rspec/collector.rb, line 193
def read_request_headers(request)
  headers = {}
  request.env.keys.each do |key|
    if EXCLUDE_REQUEST_HEADER_PATTERNS.all? { |p| !key.starts_with? p }
      headers.merge!(cleanup_header(key) => request.env[key])
    end
  end
  headers
end
read_response_headers(response) click to toggle source

read and cleanup response headers returns Hash

# File lib/reqres_rspec/collector.rb, line 168
def read_response_headers(response)
  raw_headers = response.headers
  headers = {}
  EXCLUDE_RESPONSE_HEADER_PATTERNS.each do |pattern|
    raw_headers = raw_headers.reject { |h| h if h.starts_with? pattern }
  end
  raw_headers.each do |key, val|
    headers.merge!(cleanup_header(key) => val)
  end
  headers
end