diff --git a/features/oauth2_mac_client.feature b/features/oauth2_mac_client.feature index a80da515..e528a109 100644 --- a/features/oauth2_mac_client.feature +++ b/features/oauth2_mac_client.feature @@ -31,6 +31,38 @@ Feature: Use OAuth2 MAC client as a test client response.finish end + run app + end + + map "/multiple" do + app = lambda do |env| + if env["HTTP_AUTHORIZATION"].blank? + return [401, {"Content-Type" => "text/plain"}, [""]] + end + + request = Rack::Request.new(env) + response = Rack::Response.new + response["Content-Type"] = "text/plain" + response.write("hello #{request.params["targets"].join(", ")}") + response.finish + end + + run app + end + + map "/multiple_nested" do + app = lambda do |env| + if env["HTTP_AUTHORIZATION"].blank? + return [401, {"Content-Type" => "text/plain"}, [""]] + end + + request = Rack::Request.new(env) + response = Rack::Response.new + response["Content-Type"] = "text/plain" + response.write("hello #{request.params["targets"].sort.map {|company, products| company.to_s + ' with ' + products.join(' and ')}.join(", ")}") + response.finish + end + run app end end @@ -50,6 +82,35 @@ Feature: Use OAuth2 MAC client as a test client response_body.should eq('hello rspec_api_documentation') end end + + get "/multiple" do + parameter :targets, "The people you want to greet" + + let(:targets) { ["eric", "sam"] } + + example "Greeting your favorite people" do + do_request + + response_headers["Content-Type"].should eq("text/plain") + status.should eq(200) + response_body.should eq("hello eric, sam") + end + end + + get "/multiple_nested" do + parameter :targets, "The companies you want to greet" + + let(:targets) { { "apple" => ['mac', 'ios'], "google" => ['search', 'mail']} } + + example "Greeting your favorite companies" do + do_request + + response_headers["Content-Type"].should eq("text/plain") + status.should eq(200) + response_body.should eq("hello apple with mac and ios, google with search and mail") + end + end + end """ When I run `rspec app_spec.rb --format RspecApiDocumentation::ApiFormatter` @@ -61,6 +122,10 @@ Feature: Use OAuth2 MAC client as a test client Greetings GET / * Greeting your favorite gem + GET /multiple + * Greeting your favorite people + GET /multiple_nested + * Greeting your favorite companies """ - And the output should contain "1 example, 0 failures" + And the output should contain "3 examples, 0 failures" And the exit status should be 0 diff --git a/gemfiles/minimum_dependencies b/gemfiles/minimum_dependencies index 82c901e7..4574b05c 100644 --- a/gemfiles/minimum_dependencies +++ b/gemfiles/minimum_dependencies @@ -6,7 +6,7 @@ gem "rspec", "2.6.0" gem "activesupport", "3.0.0" gem "i18n", "0.4.0" gem "mustache", "0.99.4" -gem "webmock", "1.8.0" +gem "webmock", "1.8.8" gem "json", "1.4.6" gem "rack-test", "0.5.5" gem "rack-oauth2", "0.14.4" diff --git a/lib/rspec_api_documentation/dsl/endpoint.rb b/lib/rspec_api_documentation/dsl/endpoint.rb index 76ba1402..a0a26c0b 100644 --- a/lib/rspec_api_documentation/dsl/endpoint.rb +++ b/lib/rspec_api_documentation/dsl/endpoint.rb @@ -1,8 +1,11 @@ require 'rspec/core/formatters/base_formatter' +require 'rack/utils' +require 'rack/test/utils' module RspecApiDocumentation::DSL module Endpoint extend ActiveSupport::Concern + include Rack::Test::Utils delegate :response_headers, :status, :response_status, :response_body, :to => :client @@ -45,11 +48,7 @@ def do_request(extra_params = {}) end def query_string - query = params.to_a.map do |param| - param.map! { |a| CGI.escape(a.to_s) } - param.join("=") - end - query.join("&") + build_nested_query(params || {}) end def params diff --git a/lib/rspec_api_documentation/oauth2_mac_client.rb b/lib/rspec_api_documentation/oauth2_mac_client.rb index 712afb4d..c799f0c6 100644 --- a/lib/rspec_api_documentation/oauth2_mac_client.rb +++ b/lib/rspec_api_documentation/oauth2_mac_client.rb @@ -48,9 +48,15 @@ def do_request(method, path, params, request_headers) class ProxyApp < Struct.new(:client, :app) def call(env) + env["QUERY_STRING"] = query_string_hack(env) client.last_request = Struct.new(:env, :content_type).new(env, env["CONTENT_TYPE"]) app.call(env.merge("SCRIPT_NAME" => "")) end + + private + def query_string_hack(env) + env["QUERY_STRING"].gsub('%5B', '[').gsub('%5D', ']').gsub(/\[\d+/) { |s| "#{$1}[" } + end end def access_token diff --git a/spec/dsl_spec.rb b/spec/dsl_spec.rb index c7a1420c..fa69e314 100644 --- a/spec/dsl_spec.rb +++ b/spec/dsl_spec.rb @@ -246,7 +246,6 @@ trigger_callback do uri = URI.parse(callback_url) Net::HTTP.start(uri.host, uri.port) do |http| - # debugger http.request Net::HTTP::Post.new(uri.path) end end @@ -380,6 +379,41 @@ end end + context "proper query_string serialization" do + get "/orders" do + context "with an array parameter" do + parameter :id_eq, "List of IDs" + + let(:id_eq) { [1, 2] } + + example "parsed properly" do + client.should_receive(:get).with do |path, data, headers| + Rack::Utils.parse_nested_query(path.gsub('/orders?', '')).should eq({"id_eq"=>['1', '2']}) + end + do_request + end + end + + context "with a deep nested parameter, including Hashes and Arrays" do + parameter :within_id, "Fancy search condition" + + let(:within_id) { {"first" => 1, "last" => 10, "exclude" => [3,5,7]} } + scope_parameters :search, :all + + example "parsed properly" do + client.should_receive(:get).with do |path, data, headers| + Rack::Utils.parse_nested_query(path.gsub('/orders?', '')).should eq({ + "search" => { "within_id" => {"first" => '1', "last" => '10', "exclude" => ['3','5','7']}} + }) + end + do_request + end + end + end + end + + + context "auto request" do post "/orders" do parameter :order_type, "Type of order"