POSTing сырые данные JSON с Rails 3.2.11 и RSpec
Чтобы убедиться, что мое приложение не уязвимо для этого использования, я пытаюсь создать тест контроллера в RSpec для его покрытия. Для этого мне нужно иметь возможность отправлять сырые JSON, но я, похоже, не нашел способ сделать это. Выполняя некоторые исследования, я решил, что, по крайней мере, это был способ сделать это, используя заголовок RAW_POST_DATA
, но это больше не работает:
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
Когда я смотрю на хеш params, токен не устанавливается вообще, и он просто содержит { "controller" => "user", "action" => "reset_password" }
. Я получаю те же самые результаты при попытке использовать XML или даже при попытке использовать обычные почтовые данные во всех случаях, кажется, что он не устанавливает период.
Я знаю, что с недавними уязвимостями Rails был изменен способ хэширования параметров, но есть ли способ отправить необработанные данные через RSpec? Могу ли я как-то напрямую использовать Rack::Test::Methods
?
Ответы
Ответ 1
Насколько я мог сказать, отправка необработанных POST-данных больше невозможна в спецификации контроллера. Однако это довольно легко сделать в спецификации запроса:
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
Ответ 2
Это способ отправить сырой JSON в действие контроллера (Rails 3 +):
Скажем, у нас есть такой маршрут:
post "/users/:username/posts" => "posts#create"
И позвольте сказать, что вы ожидаете, что тело станет json, которое вы читаете:
JSON.parse(request.body.read)
Тогда ваш тест будет выглядеть следующим образом:
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'}
- это волшебство, которое делает это возможным. Кроме того, если мы посмотрим на источник для TestCase # post http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html#method-i-process, вы увидите, что он принимает первый аргумент после действия (json_payload), и если это строка он устанавливает это как исходное тело сообщения и анализирует остальные аргументы как обычные.
Также важно отметить, что rspec - это просто DSL поверх архитектуры тестирования Rails. Вышеуказанный метод post
- это сообщение ActionController:: TestCase #, а не какое-либо изобретение rspec.
Ответ 3
Что мы сделали в наших тестах контроллера, явно задано RAW_POST_DATA:
before do
@request.env['RAW_POST_DATA'] = payload.to_json
post :my_action
end
Ответ 4
Пример Rails 5:
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
Ответ 5
Вот полный рабочий пример теста контроллера, отправляющего сырые данные json:
describe UsersController, :type => :controller do
describe "#update" do
context 'when resource is found' do
before(:each) do
@user = FactoryGirl.create(:user)
end
it 'updates the resource with valid data' do
@request.headers['Content-Type'] = 'application/vnd.api+json'
old_email = @user.email
new_email = Faker::Internet.email
jsondata =
{
"data" => {
"type" => "users",
"id" => @user.id,
"attributes" => {
"email" => new_email
}
}
}
patch :update, jsondata.to_json, jsondata.merge({:id => old_id})
expect(response.status).to eq(200)
json_response = JSON.parse(response.body)
expect(json_response['data']['id']).to eq(@user.id)
expect(json_response['data']['attributes']['email']).to eq(new_email)
end
end
end
end
Важными частями являются:
@request.headers['Content-Type'] = 'application/vnd.api+json'
и
patch :update, jsondata.to_json, jsondata.merge({:id => old_id})
Сначала убедитесь, что тип контента правильно настроен для вашего запроса, это довольно просто.
Вторая часть давала мне головные боли в течение нескольких часов, мой первоначальный подход был совсем другим, но оказалось, что есть ошибка Rails, что мешает нам отправлять исходные данные в функциональные тесты (но позволяет нам в тестах интеграции), и это уродливое обходное решение, но оно работает (на rails 4.1.8 и rspec-rails 3.0.0).