class CreateTests

Public Class Methods

from(requests_file, type: :request_hash, test: :rspec, mode: :append) click to toggle source

Generate tests from a file that contains Request Hashes. More info about Request Hashes: github.com/MarioRuiz/Request-Hash @param requests_file [String]. Path and file name. Could be absolute or relative to project root folder. @param type [Symbol]. (default :request_hash). The kind of content that requests_file contains. @param test [Symbol]. (default :rspec). What kind of tests we want to create @param mode [Symbol]. (default :append). :overwrite, :append, :dont_overwrite. How we want to create the tests.

:overwrite, it will overwrite the test, settings, helper... files in case they exist so you will loose your original source code.
:dont_overwrite, it will create only the files that don't exist previously
:append, it will append or create the tests or helpers that corrently don't exist on every file, but it won't modify any other code.
# File lib/create-tests/from.rb, line 15
def self.from(requests_file, type: :request_hash, test: :rspec, mode: :append)
  begin
    f = File.new("#{requests_file}_create_tests.log", "w")
    f.sync = true
    @logger = Logger.new f
    puts "- Logs: #{requests_file}_create_tests.log"
  rescue StandardError => e
    warn "** Not possible to create the Logger file"
    warn e
    @logger = Logger.new nil
  end
  @logger.info "requests_file: #{requests_file}, type: #{type}, test: #{test}, mode: #{mode}"
  requests_file_orig = requests_file

  requests_file = if requests_file["./"].nil?
                    requests_file
                  else
                    Dir.pwd.to_s + "/" + requests_file.gsub("./", "")
                  end
  unless File.exist?(requests_file)
    message = "** The file #{requests_file} doesn't exist"
    @logger.fatal message
    raise message
  end

  unless [:request_hash].include?(type)
    message = "** Wrong type parameter: #{type}"
    @logger.fatal message
    raise message
  end

  unless [:rspec].include?(test)
    message = "** Wrong test parameter: #{test}"
    @logger.fatal message
    raise message
  end

  unless [:overwrite, :dont_overwrite, :append].include?(mode)
    message = "** Wrong mode parameter: #{mode}"
    @logger.fatal message
    raise message
  end

  if mode == :overwrite
    message = "** Pay attention, if any of the files exist, will be overwritten"
    @logger.warn message
    warn message
  elsif mode == :append
    if Dir["./spec/*/**_spec.rb"].size > 0
      message = "** Pay attention, if any of the test files exist or the help file exist only will be added the tests, methods that are missing."
      @logger.warn message
      warn message
    end
  end

  @params = Array.new

  Dir.mkdir "./spec" unless test != :rspec or Dir.exist?("./spec")

  add_settings = true
  settings_file = "./settings/general.rb"
  helper_file = './spec/helper.rb'
  Dir.mkdir "./settings" unless Dir.exist?("./settings")
  if File.exist?(settings_file) and mode!=:overwrite
    message = "** The file #{settings_file} already exists so no content will be added to it.\n"
    message += "   Remove the settings file to be able to be generated by create_tests or set mode: :overwrite"
    @logger.warn message
    warn message
    add_settings = false
  end
  add_helper = true
  helper_txt = ""
  if File.exist?(helper_file)
    if mode == :dont_overwrite
      message = "** The file #{helper_file} already exists so no content will be added to it.\n"
      message += "   Remove the helper file to be able to be generated by create_tests or set mode: :overwrite or :append"
      @logger.warn message
      warn message
      add_helper = false
    elsif mode == :append
      helper_txt = File.read(helper_file)
    end
  end

  begin
    eval("require '#{requests_file}'")
  rescue Exception => stack
    message = "\n\n** Error evaluating the ruby file containing the requests: \n" + stack.to_s
    @logger.fatal message
    raise message
  end

  if Kernel.const_defined?(:Swagger)
    first_module = Swagger
  elsif Kernel.const_defined?(:OpenApi)
    first_module = OpenApi
  elsif Kernel.const_defined?(:Requests)
    first_module = Requests
  else
    message = "** The requests need to be inside a module named Swagger, OpenApi or Requests. For example:\n"
    message += "   module Swagger\n  module UberApi\n    module Products\n      def self.list_products\n"
    @logger.fatal message
    raise message
  end

  modules = get_modules(first_module)
  modules.uniq!

  if add_settings
    mods_to_include = []
    modules.each do |m|
      mods_to_include << m.scan(/^(.+)::/).join
    end
    mods_to_include.uniq!
    File.open(settings_file, "w") { |file| file.write(create_settings(requests_file_orig, mods_to_include)) }
    message = "- Settings: #{settings_file}"
    @logger.info message
    puts message
    `rufo #{settings_file}`
  end

  modules.each do |mod_txt|
    mod_name = mod_txt.scan(/::(\w+)$/).join
    folder = "./spec/#{mod_name}"
    unless Dir.exist?(folder)
      Dir.mkdir folder
      @logger.info "Created folder: #{folder}"
    end
    mod_obj = eval("#{mod_txt}")
    mod_methods_txt = eval ("#{mod_txt}.methods(false)")
    mod_methods_txt.each do |method_txt|
      test_file = "#{folder}/#{method_txt}_spec.rb"
      if File.exist?(test_file) and mode==:dont_overwrite
        message = "** The file #{test_file} already exists so no content will be added to it.\n"
        message += "   Remove the test file to be able to be generated by create_tests or set mode: :overwrite, or mode: :append"
        @logger.warn message
        warn message
      else
        if File.exist?(test_file) and mode == :append
          test_txt = File.read(test_file)
        else
          test_txt = ''
        end
        modified, txt = create_test(mod_txt, method_txt, mod_obj.method(method_txt),test_txt)
        File.open(test_file, "w") { |file| file.write(txt) }
        `rufo #{test_file}`
        if test_txt == ""
          message = "- Test created: #{test_file}"
        elsif modified
          message = "- Test updated: #{test_file}"
        else 
          message = "- Test without changes: #{test_file}"
        end
        @logger.info message
        unless message.include?("without changes")
          puts message
        end
      end
    end
  end

  if add_helper
    @params.uniq!
    File.open(helper_file, "w") { |file| file.write(create_helper(@params, helper_txt)) }
    message = "- Helper: #{helper_file}"
    @logger.info message
    puts message
    `rufo #{helper_file}`
  end
end

Private Class Methods

create_helper(params, helper_txt) click to toggle source

Create the helper file

# File lib/create-tests/create_helper.rb, line 7
        def create_helper(params, helper_txt)
  if helper_txt == ""
    output = "# for the case we want to use it standalone, not inside the project
                require_relative '../settings/general' unless defined?(ROOT_DIR)
    
                # On the methods you can pass the active http connection or none, then it will be created a new one.
                # Examples from tests:
                #   Helper.the_method_i_call(@http)
                #   Helper.the_method_i_call()
                module Helper\n"
  else
    output = helper_txt
    output.gsub!(/\s*end\s*\Z/,"\n")
  end
  
  params.each do |p|
    unless output.include?("def self.#{p.gsub("@","")}(")
      @logger.info "= Helper: added method #{p.gsub("@","")}" unless helper_txt == ""
      output += "def self.#{p.gsub("@","")}(http = NiceHttp.new())\n"
      output += 'http.logger.info "Helper.#{__method__}"'
      output += "\n\n"
      output += "return ''"
      output += "end\n"
    end
  end
  
  output += "\nend"
  
end
create_settings(requests_file_orig, modules_to_include) click to toggle source

Create the settings file

# File lib/create-tests/create_settings.rb, line 7
        def create_settings(requests_file_orig, modules_to_include)
  output = "# required libraries
    require 'nice_http'
    require 'nice_hash'
    require 'string_pattern'
    require 'pathname'
  
    # Root directory for the project
    ROOT_DIR = Pathname.new(__FILE__).join('..').join('..')
  
    # Global settings
    # in case supplied HOST=XXXXX in command line or added to ENV variables
    # fex: HOST=myhosttotest
    ENV['HOST'] ||= 'defaulthost'
    NiceHttp.host = ENV['HOST']
    NiceHttp.log = :file_run
    # Add here the headers for authentication for example
    NiceHttp.headers = {
        Auhentication: 'Token'
    }
  
    # Requests
    require_relative '.#{requests_file_orig}'\n
    require_relative '../spec/helper.rb'\n"
  
  modules_to_include.each do |m|
    output += "include #{m}\n"
  end
  output
end
create_test(module_txt, method_txt, method_obj, test_txt) click to toggle source

Return true or false and the test source code

# File lib/create-tests/create_test.rb, line 7
        def create_test(module_txt, method_txt, method_obj, test_txt)
  modified = false      
  mod_name = module_txt.scan(/::(\w+)$/).join
  req_txt = "#{method_txt}("
  params = []
  keywords_required = []
  method_obj.parameters.each do |p|
    if p[0] == :req #required
      params << "@#{p[1]}"
    elsif p[0] == :key and Object.const_defined?(p[1].to_s.upcase)
      keywords_required << p[1]
    end
  end
  req_txt += params.join(", ")
  req_txt += ")"
  request = eval("require 'nice_hash';#{module_txt}.#{req_txt}")

  req_txt = "#{mod_name}.#{req_txt}"
  params_declaration_txt = ""
  ## todo: add param in case of :append
  params.each do |p|
    params_declaration_txt << "#{p} = Helper.#{p.gsub('@','')}(@http)\n"
    @params << p
  end

  if test_txt ==""
    modified = true
    output = "
    require_relative '../../settings/general'

    RSpec.describe #{mod_name}, '##{method_txt}' do
    before(:all) do
      @http = NiceHttp.new()
      #{params_declaration_txt}@request = #{req_txt}
      @http.logger.info(\"\\n\#{'+'*50} Before All ends \#{'+'*50}\")
    end
    before(:each) do |example|
        @http.logger.info(\"\\n\\n\#{'='*100}\\nTest: \#{example.description}\\n\#{'-'*100}\")
    end\n"
  else
    output = test_txt
    output.gsub!(/\s*end\s*\Z/,"\n")
  end


  tests = Hash.new()

  # first response on responses is the one expected to be returned when success
  if request.key?(:responses) and request[:responses].size > 0
    code = request[:responses].keys[0]
    title="it 'has correct structure in successful response' "
    tests[title] = "do
            resp = @http.#{request[:method]}(@request)
            expect(resp.code).to eq #{code}\n"

    if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
      if request.key?(:data_pattern)
        tests[title] +="expect(NiceHash.compare_structure(@request.responses._#{code}.data, resp.data.json, true, @request.data_pattern)).to be true\n"
      else
        tests[title] +="expect(NiceHash.compare_structure(@request.responses._#{code}.data, resp.data.json, true)).to be true\n"
      end
    end
    tests[title] += "end\n"
  end

  title = "it 'doesn\\'t retrieve data if not authenticated'"
  tests[title] = "do
        http = NiceHttp.new()
        http.headers = {}
        resp = http.#{request[:method]}(@request)
        expect(resp.code).to be_between('400', '499')\n"
  if request.key?(:responses) and (request[:responses].keys.select{|c| c.to_s.to_i>=400&&c.to_s.to_i<=499}).size>0
  tests[title] += "expect(NiceHash.compare_structure(@request.responses[resp.code.to_sym].data, resp.data.json)).to eq true
    expect(resp.message).to eq @request.responses[resp.code.to_sym].message\n"
  end
  tests[title] += "end\n"
        
  if params.size > 0 or keywords_required.size >0
    empty_param = ""
    params.each do |p|
      r = req_txt.gsub(/#{p}([),])/, '""\1')
      empty_param += "
                request = #{r}
                resp = @http.#{request[:method]}(request)
                expect(resp.code).to be_between('400', '499')\n"
                if request.key?(:responses) and (request[:responses].keys.select{|c| c.to_s.to_i>=400&&c.to_s.to_i<=499}).size>0
                  empty_param += "expect(resp.message).to match /\#{request.responses[resp.code.to_sym].message}/i\n"
                end
    end
    if keywords_required.size > 0 
      r = req_txt.scan(/(.+)\(/).join
      empty_param += "
                [:#{keywords_required.join(', :')}].each do |kw|
                  request = #{r}(kw => '')
                  resp = @http.#{request[:method]}(request)
                  expect(resp.code).to be_between('400', '499')\n"
      if request.key?(:responses) and (request[:responses].keys.select{|c| c.to_s.to_i>=400&&c.to_s.to_i<=499}).size>0
        empty_param += "expect(resp.message).to match /\#{request.responses[resp.code.to_sym].message}/i\n"
      end
      empty_param += "end\n"
    end
    empty_param += "end\n"
    tests["it 'returns error if required parameter empty' "] = "do\n#{empty_param}"
  end

  if request.key?(:data) and request.key?(:data_required)
    empty_param_data = ""
    empty_param_data += "
            @request[:data_required].each do |p|
              request = @request.deep_copy
              request.values_for[p] = ''
              resp = @http.#{request[:method]}(request)
              expect(resp.code).not_to be_between('200', '299')
                if request.responses.key?(resp.code.to_sym)
                  expect(resp.message).to match /\#{request.responses[resp.code.to_sym].message}/i
                end
            end
        "
    empty_param_data += "end\n"
    tests["it 'returns error if required parameter on data empty' do\n"] = empty_param_data

    missing_param_data = ""
    missing_param_data += "
            @request[:data_required].each do |p|
              request = @request.deep_copy
              NiceHash.delete_nested(request[:data], p)
              resp = @http.#{request[:method]}(request)
              expect(resp.code).not_to be_between('200', '299')
                if request.responses.key?(resp.code.to_sym)
                  expect(resp.message).to match /\#{request.responses[resp.code.to_sym].message}/i
                end
            end
        "
    missing_param_data += "end\n"
    tests["it 'returns error if required parameter on data missing' do\n"] = missing_param_data
  end

  tests.each do |k,v|
    unless output.include?(k.gsub(" '",' "').gsub("' ",'" ').gsub("\n",'').gsub(/\s+do$/,'').gsub(/^\s*it/,'')) or 
      output.include?(k.gsub(' "'," '").gsub('" ',"' ").gsub("\n",'').gsub(/\s+do$/,'').gsub(/^\s*it/,''))
      modified = true
      message = " = test added #{k} for #{method_txt}"
      @logger.info message
      unless test_txt == ''
        puts message
        output +="# Appended #{Time.now.stamp}\n"
      end
      output += "#{k}#{v}"
    end
  end
  output += "\nend"
  return modified, output
end
get_modules(mod) click to toggle source

Returns array with the modules that include the http methods fex: ['Swagger::UberApi::V1_0_0::Products', 'Swagger::UberApi::V1_0_0::Cities']

# File lib/create-tests/get_modules.rb, line 7
        def get_modules(mod)
  modules = []
  mod = eval(mod) if mod.kind_of?(String)
  mod.constants.each do |m|
    o = eval ("#{mod}::#{m}.constants")
    if o.size == 0
      modules << "#{mod}::#{m}"
    else
      modules = get_modules("#{mod}::#{m}")
    end
  end
  modules
end