diff --git a/features/headers.feature b/features/headers.feature new file mode 100644 index 00000000..d2eba9d8 --- /dev/null +++ b/features/headers.feature @@ -0,0 +1,39 @@ +Feature: Specifying request headers + + Background: + Given a file named "app.rb" with: + """ + class App + def self.call(env) + if env["HTTP_ACCEPT"] == "foo" + [200, {}, ["foo"]] + else + [406, {}, ["unknown content type"]] + end + end + end + """ + And a file named "app_spec.rb" with: + """ + require "rspec_api_documentation" + require "rspec_api_documentation/dsl" + + RspecApiDocumentation.configure do |config| + config.app = App + end + + resource "FooBars" do + get "/foobar" do + header "Accept", "foo" + + example "Getting Foo" do + do_request + response_body.should == "foo" + end + end + end + """ + When I run `rspec app_spec.rb --require ./app.rb --format RspecApiDocumentation::ApiFormatter` + + Scenario: Sending headers along with the request + Then the output should not contain "unknown content type" diff --git a/lib/rspec_api_documentation/client_base.rb b/lib/rspec_api_documentation/client_base.rb index c510348c..1e5954a6 100644 --- a/lib/rspec_api_documentation/client_base.rb +++ b/lib/rspec_api_documentation/client_base.rb @@ -23,12 +23,12 @@ def delete(*args) private - def process(method, path, params = {}) - do_request(method, path, params) - document_example(method.to_s.upcase, path, params) + def process(method, path, params = {}, headers ={}) + do_request(method, path, params, headers) + document_example(method.to_s.upcase, path) end - def document_example(method, path, params) + def document_example(method, path) return unless metadata[:document] input = last_request.env["rack.input"] @@ -63,12 +63,8 @@ def query_hash Hash[arrays] end - def headers(method, path, params) - if options && options[:headers] - options[:headers] - else - {} - end + def headers(headers) + headers || {} end end end diff --git a/lib/rspec_api_documentation/dsl/endpoint.rb b/lib/rspec_api_documentation/dsl/endpoint.rb index a3fca1a8..f2a28247 100644 --- a/lib/rspec_api_documentation/dsl/endpoint.rb +++ b/lib/rspec_api_documentation/dsl/endpoint.rb @@ -41,7 +41,7 @@ def do_request(extra_params = {}) params_or_body = respond_to?(:raw_post) ? raw_post : params end - client.send(method, path_or_query, params_or_body) + client.send(method, path_or_query, params_or_body, headers) end def query_string @@ -61,6 +61,10 @@ def params parameters end + def headers + example.metadata[:headers] + end + def method example.metadata[:method] end diff --git a/lib/rspec_api_documentation/dsl/resource.rb b/lib/rspec_api_documentation/dsl/resource.rb index 5702744e..db9b45b4 100644 --- a/lib/rspec_api_documentation/dsl/resource.rb +++ b/lib/rspec_api_documentation/dsl/resource.rb @@ -35,6 +35,10 @@ def parameter(name, description, options = {}) parameters.push(options.merge(:name => name.to_s, :description => description)) end + def header(name, value) + headers[name] = value + end + def required_parameters(*names) names.each do |name| param = parameters.find { |param| param[:name] == name.to_s } @@ -67,6 +71,14 @@ def parameters metadata[:parameters] end + def headers + metadata[:headers] ||= {} + if superclass_metadata && metadata[:headers].equal?(superclass_metadata[:headers]) + metadata[:headers] = Marshal.load(Marshal.dump(superclass_metadata[:headers])) + end + metadata[:headers] + end + def parameter_keys parameters.map { |param| param[:name] } end diff --git a/lib/rspec_api_documentation/headers.rb b/lib/rspec_api_documentation/headers.rb index 15393a81..01b3e221 100644 --- a/lib/rspec_api_documentation/headers.rb +++ b/lib/rspec_api_documentation/headers.rb @@ -14,6 +14,15 @@ def env_to_headers(env) headers end + def headers_to_env(headers) + headers.inject({}) do |hsh, (k, v)| + new_key = k.upcase.gsub("-", "_") + new_key = "HTTP_#{new_key}" unless new_key == "CONTENT_TYPE" + hsh[new_key] = v + hsh + end + end + def format_headers(headers) headers.map do |key, value| "#{key}: #{value}" diff --git a/lib/rspec_api_documentation/oauth2_mac_client.rb b/lib/rspec_api_documentation/oauth2_mac_client.rb index 1d897dae..172a972e 100644 --- a/lib/rspec_api_documentation/oauth2_mac_client.rb +++ b/lib/rspec_api_documentation/oauth2_mac_client.rb @@ -42,8 +42,8 @@ def response_content_type protected - def do_request(method, path, params) - self.last_response = access_token.send(method, "http://example.com#{path}", :body => params, :header => headers(method, path, params)) + def do_request(method, path, params, request_headers) + self.last_response = access_token.send(method, "http://example.com#{path}", :body => params, :header => headers(request_headers)) end class ProxyApp < Struct.new(:client, :app) diff --git a/lib/rspec_api_documentation/rack_test_client.rb b/lib/rspec_api_documentation/rack_test_client.rb index 9ea53ffa..b5fc7e3f 100644 --- a/lib/rspec_api_documentation/rack_test_client.rb +++ b/lib/rspec_api_documentation/rack_test_client.rb @@ -34,8 +34,12 @@ def response_content_type protected - def do_request(method, path, params) - rack_test_session.send(method, path, params, headers(method, path, params)) + def do_request(method, path, params, request_headers) + rack_test_session.send(method, path, params, headers(request_headers)) + end + + def headers(*args) + headers_to_env(super) end private diff --git a/spec/dsl_spec.rb b/spec/dsl_spec.rb index 3ea084c0..ca811826 100644 --- a/spec/dsl_spec.rb +++ b/spec/dsl_spec.rb @@ -139,30 +139,30 @@ let(:raw_post) { { :bill => params }.to_json } it "should send the raw post body" do - client.should_receive(method).with(path, raw_post) + client.should_receive(method).with(path, raw_post, nil) do_request end end context "when raw_post is not defined" do it "should send the params hash" do - client.should_receive(method).with(path, params) + client.should_receive(method).with(path, params, nil) do_request end end it "should allow extra parameters to be passed in" do - client.should_receive(method).with(path, params.merge("extra" => true)) + client.should_receive(method).with(path, params.merge("extra" => true), nil) do_request(:extra => true) end it "should overwrite parameters" do - client.should_receive(method).with(path, params.merge("size" => "large")) + client.should_receive(method).with(path, params.merge("size" => "large"), nil) do_request(:size => "large") end it "should overwrite path variables" do - client.should_receive(method).with("/orders/2", params) + client.should_receive(method).with("/orders/2", params, nil) do_request(:id => 2) end end @@ -203,32 +203,6 @@ end end - # parameter :type, "The type of drink you want." - # parameter :size, "The size of drink you want." - # parameter :note, "Any additional notes about your order." - - # required_parameters :type, :size - - # raw_post { { :bill => params }.to_json } - - # example_request "Ordering a cup of coffee" do - # param(:type) { "coffee" } - # param(:size) { "cup" } - - # should_respond_with_status eq(200) - # should_respond_with_body eq("Order created") - # end - - # example_request "An invalid order" do - # param(:type) { "caramel macchiato" } - # param(:note) { "whipped cream" } - - # should_respond_with_status eq(400) - # should_respond_with_body json_eql({:errors => {:size => ["can't be blank"]}}.to_json) - # end - #end - # - describe "nested parameters" do parameter :per_page, "Number of results on a page" @@ -309,10 +283,11 @@ get "/users/:id/orders" do example "Page should be in the query string" do - client.should_receive(method).with do |path, data| + client.should_receive(method).with do |path, data, headers| path.should =~ /^\/users\/1\/orders\?/ path.split("?")[1].split("&").sort.should == "page=2&message=Thank+you".split("&").sort data.should be_nil + headers.should be_nil end do_request end @@ -320,7 +295,7 @@ post "/users/:id/orders" do example "Page should be in the post body" do - client.should_receive(method).with("/users/1/orders", {"page" => 2, "message" => "Thank you"}) + client.should_receive(method).with("/users/1/orders", {"page" => 2, "message" => "Thank you"}, nil) do_request end end @@ -411,7 +386,7 @@ context "no extra params" do before do - client.should_receive(:post).with("/orders", {}) + client.should_receive(:post).with("/orders", {}, nil) end example_request "Creating an order" @@ -423,7 +398,7 @@ context "extra options for do_request" do before do - client.should_receive(:post).with("/orders", {"order_type" => "big"}) + client.should_receive(:post).with("/orders", {"order_type" => "big"}, nil) end example_request "should take an optional parameter hash", :order_type => "big" @@ -444,6 +419,24 @@ end end end + + context "headers" do + put "/orders" do + header "Accept", "application/json" + + it "should be sent with the request" do + example.metadata[:headers].should == { "Accept" => "application/json" } + end + + context "nested headers" do + header "Content-Type", "application/json" + + it "does not affect the outer context's assertions" do + # pass + end + end + end + end end resource "top level parameters" do diff --git a/spec/rack_test_client_spec.rb b/spec/rack_test_client_spec.rb index e68c7903..6ef5cad6 100644 --- a/spec/rack_test_client_spec.rb +++ b/spec/rack_test_client_spec.rb @@ -67,11 +67,7 @@ class StubApp < Sinatra::Base describe "#request_headers" do before do - test_client.options[:headers] = { - "HTTP_ACCEPT" => "application/json", - "CONTENT_TYPE" => "application/json" - } - test_client.get "/" + test_client.get "/", {}, { "Accept" => "application/json", "Content-Type" => "application/json" } end it "should contain all the headers" do @@ -84,42 +80,13 @@ class StubApp < Sinatra::Base end end - describe "#headers" do - before do - test_client.options[:headers] = { "HTTP_X_CUSTOM_HEADER" => "custom header value" } - test_client.get "/" - end - - it "can be overridden to add headers to the request" do - test_client.request_headers["X-Custom-Header"].should eq("custom header value") - end - end - - describe "setup default headers" do - it "should let you set default headers when creating a new RackTestClient" do - test_client = RspecApiDocumentation::RackTestClient.new(context, :headers => { "HTTP_MY_HEADER" => "hello" }) - test_client.get "/" - test_client.request_headers["My-Header"].should == "hello" - test_client.request_headers.should have(3).headers - end - - it "should be blank if not set" do - test_client = RspecApiDocumentation::RackTestClient.new(context) - test_client.get "/" - test_client.request_headers.should have(2).headers - end - end - context "after a request is made" do before do - test_client.options[:headers] = { - "CONTENT_TYPE" => "application/json;charset=utf-8", - "HTTP_X_CUSTOM_HEADER" => "custom header value" - } - test_client.post "/greet?query=test+query", post_data + test_client.post "/greet?query=test+query", post_data, headers end let(:post_data) { { :target => "nurse" }.to_json } + let(:headers) { { "Content-Type" => "application/json;charset=utf-8", "X-Custom-Header" => "custom header value" } } context "when examples should be documented", :document => true do it "should augment the metadata with information about the request" do