class ApiMiniTester::TestStep

Constants

SUPPORTED_METHODS
SUPPORTED_RANDOM_DISTRIBUTION
TYPE_TO_METHOD_MAP

Attributes

debug[R]
input[RW]
method[R]
name[RW]
output[RW]
results[R]
sleeps[R]
timing[RW]
uri[R]

Public Class Methods

new(base_uri, step, context = nil, data = nil, defaults = nil, debug = false) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 25
def initialize(base_uri, step, context = nil, data = nil, defaults = nil, debug = false)
  @debug = debug
  if debug
    @debug_output = File.open(ApiMiniTester::TestSuite::DEBUG_FILE, 'a')
  end
  step = step.deep_merge!(defaults) do |key, this_val, other_val|
    if this_val.nil?
      other_val
    elsif this_val.is_a?(Array)
      (this_val + other_val)
    else
      this_val
    end
  end if defaults

  Liquid::Template.register_filter(::TestFakerFilter)
  @context = context
  uri_template = Liquid::Template.parse([base_uri, step['uri']].join("/"), error_mode: :strict)
  @name = step['name']
  @sleeps = step['sleep']

  @uri = uri_template.render(
    {'context' => context, 'data' => data},
    { strict_variables: true })
  @method = step['method'].downcase.to_sym

  input_template = Liquid::Template.parse(step['input'].to_yaml.to_s, error_mode: :strict)
  @input = YAML.load(
    input_template.render({'context' => context, 'data' => data}, { strict_variables: true }))

  output_template = Liquid::Template.parse(step['output'].to_yaml.to_s, error_mode: :strict)
  @output = YAML.load(
    output_template.render({'context' => context, 'data' => data}, { strict_variables: true }))

  @results = { name: step['name'], desc: step['desc'], status: [], headers: [], body: [], url: [], method: [], timing: [] }
end

Public Instance Methods

add_result(section, result) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 192
def add_result(section, result)
  @results[section] << result
end
array_diff(a, b, path = nil, section = :body) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 246
def array_diff(a, b, path = nil, section = :body)
  a.each do |a_item|
    if b.nil?
      add_result section, { result: false,
        name: "Response boby value: #{[path].join(".")}",
        desc: "Assert #{[path].join(".")} is empty" }
    elsif a_item.instance_of?(Hash)
      found = false
      b.each do |b_item|
        matching = true
        a_item.each_key do |k, v|
          matching = (b_item[k] == a_item[k]) if matching
        end
        found = true if matching
      end
      add_result section, { result: found,
                            name: "Response body value: #{[path].join(".")}",
                            desc: "Assert #{[path].join(".")} #{found ? 'contains' : 'does not contains'} #{a_item}" }
    elsif a_item.instance_of?(Array)
      # TODO: Add support for array of array it isn't so needed to compate so deep structures
    else
      add_result section, { result: b.include?(a_item),
                            name: "Response boby value: #{[path].join(".")}",
                            desc: "Assert #{[path].join(".")} #{b.include?(a_item) ? 'contains' : 'does not contains'} #{a_item}" }
    end
  end
end
assert_body(response, output) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 238
def assert_body(response, output)
  if output.instance_of?(Hash)
    hash_diff(output, response)
  elsif output.instance_of?(Array)
    array_diff(output, response)
  end
end
assert_headers(response, output) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 227
def assert_headers(response, output)
  return if output.nil?

  output.each do |k, v|
    add_result :headers, { result: (v == response[k]),
                           name: "Header value: #{k} == #{v}",
                           desc: "Header #{k} expected: #{v}, got #{response[k]}",
                           exp: v, real: response[k] }
  end
end
assert_status(response, output) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 220
def assert_status(response, output)
  add_result :status, { result: (response == output),
                        name: "Response code == #{output}",
                        desc: "Expected response #{output}, got response #{response}",
                        exp: output, real: response }
end
assert_timing(runtime, limit = nil) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 212
def assert_timing(runtime, limit = nil)
  limit ||= Float::INFINITY
  add_result :timing, { result: (runtime < limit),
                        name: "Request time < #{limit}",
                        desc: "Expected request time #{limit}, real time #{runtime}",
                        exp: limit, real: runtime }
end
body() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 82
def body
  case content_type
  when 'application/x-www-form-urlencoded'
    body_to_urlencoded
  when 'multipart/form-data'
    body_to_form_data
  else
    body_by_anotations
  end
end
body_by_anotations() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 93
def body_by_anotations
  resolve_annotations(@input["body"]).to_json
end
body_to_form_data() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 103
def body_to_form_data
  body = {}
  @input["body"].each do |item|
    body[item['name']] = item['value'] if item['type'] == 'input'
    body[item['name']] = File.open(item['value'], 'r') if item['type'] == 'file'
  end
  body
end
body_to_urlencoded() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 112
def body_to_urlencoded
  body = []
  @input["body"].each do |key, value|
    body << [key, value]
  end
  URI.encode_www_form(body)
end
content_type() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 72
def content_type
  @input['content_type'] || 'application/json'
end
hash_diff(a, b, path = nil, section = :body) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 274
def hash_diff(a, b, path = nil, section = :body)
  return nil if a.nil? || b.nil?

  a.each_key do |k, v|
    current_path = [path, k].join('.')
    if b[k].nil?
      add_result section, { result: false,
                            name: "Reponse value: #{[path, k].join(".")}",
                            desc: "Missing #{current_path}" }
    elsif v.instance_of?(Hash)
      hash_diff(a[k], b[k], current_path, section)
    elsif v.instance_of?(Array)
      array_diff(a[k], b[k], current_path, section)
    else
      add_result section, { result: (a[k] == b[k]),
                            name: "Reponse body value: #{[path, k].join(".")}",
                            desc: "Assert #{[path, k].join(".")}: #{a[k]} #{a[k] == b[k] ? '==' : '!='} #{b[k]}" }
    end
  end
end
headers() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 76
def headers
  @input['header'] = {} unless @input['header']
  @input['header']['Content-Type'] ||= content_type
  @input['header']
end
log_debug(request, response, expectations) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 181
def log_debug(request, response, expectations)
  log = { uri: uri, method: method, request: request, response: response, expectations: expectations }
  @debug_output.puts log.to_json
end
print_results() click to toggle source
raw_body() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 97
def raw_body
  @input["body"].to_hash
rescue StandardError
  ""
end
resolve_annotations(struct) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 299
def resolve_annotations(struct)
  if struct.instance_of?(Hash)
    struct = resolve_annotations_hash(struct)
  end
  struct
end
resolve_annotations_array(array, type) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 327
def resolve_annotations_array(array, type)
  array.map(&type_to_method(type))
end
resolve_annotations_hash(hash) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 306
def resolve_annotations_hash(hash)
  hash.each do |k, v|
    next unless matched = /\A\.(?<key>.*)\Z/.match(k)
    type = v
    typed_key = matched[:key]
    next if hash[typed_key].nil?
    hash[typed_key] = if hash[typed_key].instance_of?(Array)
                        resolve_annotations_array(hash[typed_key], type)
                      else
                        resolve_annotations_value(hash[typed_key], type)
                      end
    hash.delete(k)
  end
end
resolve_annotations_value(value, type) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 321
def resolve_annotations_value(value, type)
  value.send(type_to_method(type))
rescue StandardError
  value
end
run_asserts(response) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 174
def run_asserts(response)
  assert_status(response.code, test_status)
  assert_headers(response.headers, test_headers)
  assert_body(response.parsed_response, test_body)
  assert_timing(timing, test_timing)
end
run_step() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 136
def run_step
  if sleeps
    step_sleep(sleeps['before']) if sleeps['before']
  end

  @timing = Time.now
  case method
  when :get
    response = HTTParty.get(uri, headers: headers)
  when :post
    response = HTTParty.post(uri, headers: headers, body: body)
  when :put
    response = HTTParty.put(uri, headers: headers, body: body)
  when :delete
    response = HTTParty.delete(uri, headers: headers)
  when :patch
    response = HTTParty.patch(uri, headers: headers, body: body)
  else
    raise "Unknown HTTP method: #{method}"
  end
  @timing = Time.now - @timing

  if sleeps
    step_sleep(sleeps['after']) if sleeps['after']
  end

  add_result :url, { result: true, desc: "Url: #{uri}" }
  add_result :method, { result: true, desc: "Method: #{method}" }

  log_debug({ headers: headers, body: raw_body },
            { headers: response.headers, body: response.parsed_response, code: response.code },
            output) if debug

  run_asserts(response)

  [ results, response ]
end
step_sleep(params) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 196
def step_sleep(params)
  params['distribution'] = 'static' unless SUPPORTED_RANDOM_DISTRIBUTION.include?(params['distribution'])

  t = begin
    case params['distribution']
    when 'static'
      params['value']
    when 'norm'
      Distribution::Normal.rng(params['mean'], params['sigma'], srand).call
    when 'uniform'
      Random.new.rand(params['max'] - params['min']) + params['min']
    end
  end
  sleep(t.to_f.abs) if t
end
test_body() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 124
def test_body
  @output['body']
end
test_headers() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 120
def test_headers
  @output['header']
end
test_status() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 128
def test_status
  @output['status']
end
test_timing() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 132
def test_timing
  @output['timing']
end
type_to_method(type) click to toggle source
# File lib/api_mini_tester/test_step.rb, line 295
def type_to_method(type)
  TYPE_TO_METHOD_MAP[type.downcase].to_sym
end
valid?() click to toggle source
# File lib/api_mini_tester/test_step.rb, line 62
def valid?
  return false if uri.nil? || uri.empty?

  return false unless URI.parse(uri) rescue false
  return false unless SUPPORTED_METHODS.include? method
  return false if @name.nil? || @name.empty?

  true
end