lib/endpoint/stub.rb in endpoint_stub-1.1.0 vs lib/endpoint/stub.rb in endpoint_stub-1.4.5

- old
+ new

@@ -1,5 +1,6 @@ +require 'endpoint/response' require 'endpoint_stub' require 'webmock' module Endpoint ## @@ -19,11 +20,11 @@ # If a block is supplied, it will be executed in the context # of the new Endpoint::Stub, allowing you to elegantly mock # custom responses if needed. def create_for(model, options={}, &block) model = assure_model model - return if stubs.keys.include? model + return if stubs.keys.map(&:name).include? model.name new_stub = Stub.new(model, options) EndpointStub::Config.default_responses.each do |response| new_stub.mock_response(*response) end @@ -44,10 +45,14 @@ def get_for(model) @stubs[assure_model(model)] end + def clear_all_records! + @stubs.values.each(&:clear_records!) + end + ## # Clears all endpoint stubs. def clear! @stubs = {} end @@ -57,10 +62,14 @@ # i.e. Endpoint::Stub[Post] def [](model) create_for model or get_for model end + def each(&block) + @stubs.each(&block) + end + private def assure_model(model) if model.ancestors.include? ActiveResource::Base model else @@ -69,16 +78,17 @@ end end attr_reader :defaults attr_reader :model + attr_reader :site attr_accessor :records def initialize(model, options) @defaults = options[:defaults] || {} @model = model - @site = URI "#{model.site}/#{model.name.underscore.pluralize}" + @site = URI "#{model.site}/#{model.name.split('::').last.underscore.pluralize}" @responses = {} @records = [] end @@ -91,10 +101,11 @@ raise "Endpoint::Stub#add_record expects a Hash. Got #{attrs.class.name}." end attrs[:id] = current_id attrs.merge!(@defaults) { |k,a,b| a } @records << attrs + attrs end ## # Updates the record with the given id with the given attributes. def update_record(id, attrs) @@ -115,10 +126,18 @@ @records[id] = nil true end end + ## + # Clear all records in this stub. + def clear_records! + @records = [] + end + + ## + # Get the record at the given id. Accepts strings as well as ints. def record(id) @records[id.to_i] end ## @@ -169,32 +188,60 @@ # The proc will be called with the request object, the extracted # parameters from the uri, and the stub object so that you can # interact with the stubbed records. def mock_response(type, route='', proc=nil, &block) proc = block if block_given? + route = clean_route route + @responses[type] ||= {} + @responses[type][route].deactivate! if @responses[type][route] + @responses[type][route] = Response.new(type, prepare_uri(type, route), self, &proc) + @responses[type][route].activate! + end + + ## + # Same thing as mock_response, except it will not overWRITE existing + # mocks. Instead, it allows you to call a block inside of your response + # which will act as a 'super' call, invoking previously defined + # responses. Yielding inside a top-level response will give you + # an empty hash, so no nil related issues should arrise (unless of + # course the super-response returns nil, which it shouldn't). + # + # Also note that this does not re-activate a deactivated response. + def override_response(type, route, proc=nil, &block) + proc = block if block_given? route = clean_route route - site = "#{@site.scheme}://#{@site.host}" - path = @site.path.split(/\/+/).reject(&:empty?) - if route[0] == '.' && !route.include?('/') - # This allows passing '.json', etc as the route - if path.last - path = path[0...-1] + [path.last+route] - else - site += route - end + if @responses[type] and @responses[type][route] + @responses[type][route].add_to_stack(&proc) else - path += route.split('/') + mock_response(type, route, proc) end + end - @responses[type] ||= {} - @responses[type][route] = Response.new(type, URI.parse(site+'/'+path.join('/')), self, &proc) - @responses[type][route].activate! + ## + # Overrides all currently assigned responses. Will not have any effect + # on responses mocked after this method is called. + def override_all(&block) + @responses.each do |type, responses| + responses.each do |route, response| + response.add_to_stack(&block) + end + end end ## + # Removes all overrides, reducing each response to their originals. + def drop_overrides! + @responses.each do |type, responses| + responses.each do |route, response| + response.drop_overrides! + end + end + end + + ## # Remove a mocked response with the given type and route. def unmock_response(type, route) route = clean_route route if @responses[type] && @responses[type][route] @responses[type][route].deactivate! @@ -202,106 +249,30 @@ true end end private - def clean_route(route) - route = route[1..-1] if route[0] == '/' - route = route[0...-1] if route[-1] == '/' - route - end + def prepare_uri(type, route) + site = "#{@site.scheme}://#{@site.host}:#{@site.port}" + path = @site.path.split(/\/+/).reject(&:empty?) - class Response - include WebMock::API - - # For remembering where a uri-based parameter is located. - ParamIndices = Struct.new(:slash, :dot) - # Allows more comfortable use of Symbol keys when accessing - # params (which are string keys). - class Params < Hash - def [](key) - super(key.to_s) + if route[0] == '.' && !route.include?('/') + # This allows passing '.json', etc as the route + if path.last + path = path[0...-1] + [path.last+route] + else + site += route end - def []=(key, value) - super(key.to_s, value) - end + else + path += route.split('/') end - def initialize(type, url, stub, &proc) - @param_indices = {} - - @url_regex = build_url_regex!(url) + URI.parse site+'/'+path.join('/') + end - @type = type - @proc = proc - @stub = stub - end - - # Should be called only once, internally to perform the actual WebMock stubbing. - def activate! - @stubbed_request = stub_request(@type, @url_regex).to_return do |request| - params = extract_params(request) - - results = @proc.call(request, params, @stub) - results[:body] = results[:body].to_json unless results[:body].is_a? String - results - end - end - - # This should remove the request stubbed by #activate! - def deactivate! - remove_request_stub @stubbed_request - end - - private - # Bang is there because this method populates @param_indices. - def build_url_regex!(url) - regex = "" - separate(url).each_with_index do |x, slash_index| - regex += '/' unless slash_index == 0 - # If there is a colon, it's a parameter. i.e. /resource/:id.json - if x.include? ':' and !(x[1..-1] =~ /^\d$/) # If it's just numbers, it's probably a port number - # We split by dot at this point to separate the parameter from any - # format/domain related suffix. - dot_split = x.split('.') - inner_regex = [] - - dot_split.each_with_index do |name, dot_index| - # A parameter can show up after a dot as well. i.e. /resource/:id.:format - inner_regex << if name.include? ':' - param_name = name[1..-1] - @param_indices[param_name] = ParamIndices.new(slash_index, dot_index) - # Add .+ regex to capture any data at this point in the url. - ".+" - else - # If there's no colon, it's a static part of the target url. - Regexp.escape(name) - end - end - - # "inner_regex" was built by splitting on dots, so we put the dots back. - regex += inner_regex.join('\.') - else - # No colon, so this segment is static. - regex += Regexp.escape(x) - end - end - Regexp.new regex - end - - def extract_params(request) - url = separate request.uri - params = Params.new - @param_indices.each do |param_name, index| - value = url[index.slash].split('.')[index.dot] - - params[param_name] = value - end - params - end - - def separate(url) - url.to_s[url.to_s.index('://')+3..-1].split '/' - end + def clean_route(route) + route = route[1..-1] if route[0] == '/' + route = route[0...-1] if route[-1] == '/' + route end end end \ No newline at end of file