module HammerCLIForeman module Testing module APIExpectations def self.api_calls @api_calls ||= [] end class APICallMatcher < Mocha::ParameterMatchers::Base attr_accessor :expected_params, :expected_resource, :expected_action, :block def initialize(resource=nil, action=nil, &block) @expected_resource = resource @expected_action = action @block = block @expected_params = {} end def matches?(actual_parameters) action, params, _headers, _options = actual_parameters.shift(4) action_name = action.name.to_s resource_name = action.resource.to_s result = true result &&= (resource_name == @expected_resource.to_s) unless @expected_resource.nil? result &&= (action_name == @expected_action.to_s) unless @expected_action.nil? result &&= @block.call(params) if @block result &&= assert_params(params) result end def mocha_inspect res = @expected_resource.nil? ? 'any_resource' : ":#{@expected_resource}" act = @expected_action.nil? ? 'any_action' : ":#{@expected_action}" blk = @block ? '&block' : '*any_argument' "#{res}, #{act}, #{blk}" end protected def assert_params(params) stringify_keys(params) == deep_merge_hash(stringify_keys(params), stringify_keys(@expected_params)) end def deep_merge_hash(h, other_h) h = h.clone h.merge!(other_h) do |key, old_val, new_val| if old_val.is_a?(Hash) && new_val.is_a?(Hash) deep_merge_hash(old_val, new_val) else new_val end end end def stringify_keys(hash) hash.inject({}) do |stringified, (key, value)| if value.is_a?(Hash) value = stringify_keys(value) end stringified.update(key.to_s => value) end end end module ExpectationExtensions def method_signature signature = "#{@note}\n #{super}" if @api_call_matcher && !@api_call_matcher.expected_params.empty? signature += "\n expected params to include: " + params_signature(@api_call_matcher.expected_params) end if @api_call_matcher && !@api_call_matcher.block.nil? signature += "\n expected params to match block at: " + block_signature(@api_call_matcher.block) end signature end def params_signature(hash) JSON.pretty_generate(hash).split("\n").join("\n ") end def block_signature(block) block.source_location.join(':') end def set_note(note) @note = note end def with_params(expected_params = {}, &block) api_call_matcher.expected_params = expected_params api_call_matcher.block = block if block_given? self.with(api_call_matcher) self end def with_action(resource, action) api_call_matcher.expected_resource = resource api_call_matcher.expected_action = action self.with(api_call_matcher) self end def api_call_matcher @api_call_matcher ||= APICallMatcher.new end end class APIExpectationsDecorator < SimpleDelegator def initialize(api_instance = ApipieBindings::API.any_instance) @api_instance = api_instance super end def expects_call(resource=nil, action=nil, note=nil, &block) ex = @api_instance.expects(:call_action) ex.extend(ExpectationExtensions) ex.with_action(resource, action).with_params(&block) ex.set_note(note) ex end def expects_no_call @api_instance.expects(:call_action).never end def expects_search(resource=nil, search_options={}, note=nil) note ||= "Find #{resource}" if search_options.is_a?(Hash) search_query = search_options.map{|k, v| "#{k} = \"#{v}\"" }.join(" or ") else search_query = search_options end expects_call(resource, :index, note).with_params(:search => search_query) end end class TestAuthenticator < ApipieBindings::Authenticators::BasicAuth def user(ask=nil) @user end def password(ask=nil) @password end end class FakeApiConnection < HammerCLI::Apipie::ApiConnection attr_reader :authenticator def initialize(params, options = {}) @authenticator = params[:authenticator] super end end def api_connection(options={}, version = FOREMAN_VERSION) FakeApiConnection.new({ :uri => 'https://test.org', :apidoc_cache_dir => "test/data/#{version}", :apidoc_cache_name => 'foreman_api', :authenticator => TestAuthenticator.new('admin', 'changeme'), :dry_run => true }.merge(options)) end def api_calls HammerCLIForeman::Testing::APIExpectations.api_calls end def api_expects(resource=nil, action=nil, note=nil, &block) api_calls << [resource, action] APIExpectationsDecorator.new.expects_call(resource, action, note, &block) end def api_expects_no_call APIExpectationsDecorator.new.expects_no_call end def api_expects_search(resource=nil, search_options={}, note=nil) APIExpectationsDecorator.new.expects_search(resource, search_options, note) end def index_response(items, options={}) cnt = if items.is_a?(Hash) items.keys.count else items.length end { "total" => options.fetch(:total, cnt), "subtotal" => options.fetch(:subtotal, cnt), "page" => options.fetch(:page, 1), "per_page" => options.fetch(:per_page, cnt), "search" => "", "sort" => { "by" => nil, "order" => nil }, "results" => items } end end end end