require 'binary_data_checker' require 'hashie/mash' module Mirage class ServerResponseNotFound < Exception end class MalformedResponse < Exception end class MockResponse class << self def find_by_id(id) all.find{|response| response.response_id == id} || raise(ServerResponseNotFound) end def delete(id) responses.values.each do |set| set.values.each{|responses| responses.delete_if{|response|response.response_id == id}} end end def delete_all responses.clear @next_id = 0 end #TODO - this is flakey, make a proper copy def backup snapshot.clear and snapshot.replace(responses.deep_clone) end def revert delete_all and responses.replace(snapshot.deep_clone) end def all responses.values.collect do|response_set| response_set.values end.flatten end def find_default(options) http_method = options[:http_method].upcase! default_responses = subdomains(options[:endpoint]).collect do |domain| if(responses_for_domain = responses[domain]) responses_for_domain[http_method].find_all{|response| response.default?} if responses_for_domain[http_method] end end.flatten.compact default_responses.find{|response| match?(options,response)} || raise(ServerResponseNotFound) end def subdomains(name) domains=[] name.split("/").each do |part| domains << (domains.last ? "#{domains.last}/#{part}" : part) end domains.reverse end def find(options) options[:response_set] = responses[options[:endpoint]] find_in_response_set(options) || raise(ServerResponseNotFound) end def add(new_response) response_set = responses_for_endpoint(new_response) method_specific_responses = response_set[new_response.request_spec['http_method'].upcase]||=[] duplicate_response_location = method_specific_responses.index{|response| response.request_spec == new_response.request_spec} old_response = method_specific_responses.delete_at(duplicate_response_location) if duplicate_response_location if old_response new_response.response_id = old_response.response_id else new_response.response_id = next_id end method_specific_responses< a.score}.first end def match?(options,stored_response) parameters = options[:params] headers = Hash[options[:headers].collect{|key, value| [key.downcase, value]}] request_spec = stored_response.request_spec match = true {request_spec['parameters'] => parameters, request_spec['headers'] => headers}.each do |spec, actual| spec.each do |key, value| value = interpret_value(value) if value.is_a? Regexp match = false unless value.match(actual[key]) else match = false unless value == actual[key] end end end request_spec['body_content'].each do |value| value = interpret_value(value) if value.is_a? Regexp match = false unless options[:body] =~ value else match = false unless options[:body].include?(value) end end match end def interpret_value(value) value.start_with?("%r{") && value.end_with?("}") ? eval(value) : value end def responses_for_endpoint(response) responses[response.name]||={} end def responses @responses ||={} end def snapshot @snapshot ||={} end def next_id @next_id||= 0 @next_id+=1 end end attr_reader :name, :request_spec, :response_spec attr_accessor :response_id, :requests_url def initialize name, spec={} request_defaults = JSON.parse({:parameters => {}, :body_content => [], :http_method => 'get', :headers => {}}.to_json) response_defaults = JSON.parse({:default => false, :body => Base64.encode64(''), :delay => 0, :content_type => "text/plain", :status => 200}.to_json) @name = name @spec = spec @request_spec = Hashie::Mash.new request_defaults.merge(spec['request']||{}) @response_spec = Hashie::Mash.new response_defaults.merge(spec['response']||{}) @request_spec['headers'] = Hash[@request_spec['headers'].collect{|key, value| [key.downcase, value.to_s]}] @request_spec['parameters'] = Hash[@request_spec['parameters'].collect{|key, value| [key, value.to_s]}] @request_spec['body_content'] = @request_spec['body_content'].collect{|value|value.to_s} @binary = BinaryDataChecker.contains_binary_data? @response_spec['body'] MockResponse.add self end def headers @response_spec['headers'] end def default? @response_spec["default"] end def score [@request_spec['headers'].values, @request_spec['parameters'].values, @request_spec['body_content']].inject(0) do |score, matchers| matchers.inject(score){|matcher_score, value| interpret_value(value).is_a?(Regexp) ? matcher_score+=1 : matcher_score+=2} end end def value(request_body='', request_parameters={}, query_string='') body = Base64.decode64(response_spec['body']) return body if @binary value = body.dup value.scan(/\$\{([^\}]*)\}/).flatten.each do |pattern| if (parameter_match = request_parameters[pattern]) value = value.gsub("${#{pattern}}", parameter_match) end [request_body, query_string].each do |string| if (string_match = find_match(string, pattern)) value = value.gsub("${#{pattern}}", string_match) end end end value end def == response response.is_a?(MockResponse) && @name == response.send(:eval, "@name") && @request_spec == response.send(:eval, "@request_spec") && @response_spec == response.send(:eval, "@response_spec") end def raw {:id =>response_id, :endpoint => @name, :requests_url => requests_url, :response => @response_spec, :request => @request_spec}.to_json end def binary? @binary end private def find_match(string, regex) string.scan(/#{regex}/).flatten.first end def interpret_value(value) value.start_with?("%r{") && value.end_with?("}") ? eval(value) : value end end end