POSTing raw JSON data with Rails 3.2.11 and RSpec

In order to ensure that my application is not vulnerable to this exploit, I am trying to create a controller test in RSpec to cover it. In order to do so, I need to be able to post raw JSON, but I haven't seemed to find a way to do that. In doing some research, I've determined that there at least used to be a way to do so using the RAW_POST_DATA header, but this doesn't seem to work anymore:

it "should not be exploitable by using an integer token value" do
  request.env["CONTENT_TYPE"] = "application/json"
  request.env["RAW_POST_DATA"]  = { token: 0 }.to_json
  post :reset_password
end

When I look at the params hash, token is not set at all, and it just contains { "controller" => "user", "action" => "reset_password" }. I get the same results when trying to use XML, or even when trying to just use regular post data, in all cases, it seems to not set it period.

I know that with the recent Rails vulnerabilities, the way parameters are hashed was changed, but is there still a way to post raw data through RSpec? Can I somehow directly use Rack::Test::Methods?


As far as I have been able to tell, sending raw POST data is no longer possible within a controller spec. However, it can be done pretty easily in a request spec:

describe "Example", :type => :request do
  params = { token: 0 }
  post "/user/reset_password", params.to_json, { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
  #=> params contains { "controller" => "user", "action" => "reset_password", "token" => 0 }
end

This is the way to send raw JSON to a controller action (Rails 3+):

Let's say we have a route like this:

post "/users/:username/posts" => "posts#create"

And let's say you expect the body to be a json that you read by doing:

JSON.parse(request.body.read)

Then your test will look like this:

it "should create a post from a json body" do
  json_payload = '{"message": "My opinion is very important"}'
  post :create, json_payload, {format: 'json', username: "larry" }
end

{format: 'json'} is the magic that makes it happen. Additionally, if we look at the source for TestCase#post http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html#method-i-process you can see that it takes the first argument after the action (json_payload) and if it is a string it sets that as raw post body, and parses the rest of the args as normal.

It's also important to point out that rspec is simply a DSL on top of the Rails testing architecture. The post method above is the ActionController::TestCase#post and not some rspec invention.


What we've done in our controller tests is explicitly set the RAW_POST_DATA:

before do
  @request.env['RAW_POST_DATA'] = payload.to_json
  post :my_action
end

Rails 5 example:

RSpec.describe "Sessions responds to JSON", :type => :request do

  scenario 'with correct authentication' do
    params = {id: 1, format: :json}
    post "/users/sign_in", params: params.to_json, headers: { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' }
    expect(response.header['Content-Type']).to include 'application/json'
  end
end