使用 Rails 3.2.11 和 RSpec 发布原始 JSON 数据
Posted
技术标签:
【中文标题】使用 Rails 3.2.11 和 RSpec 发布原始 JSON 数据【英文标题】:POSTing raw JSON data with Rails 3.2.11 and RSpec 【发布时间】:2013-01-24 10:06:38 【问题描述】:为了确保我的应用程序不会受到this exploit 的攻击,我试图在 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
当我查看参数哈希时,根本没有设置令牌,它只包含 "controller" => "user", "action" => "reset_password"
。在尝试使用 XML 时,甚至在尝试仅使用常规发布数据时,我得到了相同的结果,在所有情况下,它似乎都没有设置它。
我知道最近 Rails 的漏洞导致参数的散列方式发生了变化,但是还有办法通过 RSpec 发布原始数据吗?我可以以某种方式直接使用Rack::Test::Methods
吗?
【问题讨论】:
从 Rails 4.2.6 开始,在 RSpec 控制器规范中设置request.env["RAW_POST_DATA"]
对我有用。
【参考方案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
【讨论】:
这是我发现的最简洁的方法来测试期望原始 json POST 请求的控制器。谢谢。 'CONTENT_TYPE' 标题就够了 这个解决方案在 Rails 3.2.13 中对我不起作用。我的解决方法是写params = token: 0, format: :json
。还要删除示例中的 .to_json 及其后面的哈希。此外,您可能希望在spec_helper.rb
中有config.include Rails.application.routes.url_helpers
。使用 response.header['Content-Type'].should include 'application/json'
验证 json 响应
我是这样做的:使用 Rails 3.2.14 发布 "/user/reset_password", params.merge(format: 'json')
在 Rails 5 中,尝试使用 as: :json
而不是 format: :json
来转换有效负载。【参考方案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) 之后采用第一个参数,如果它是一个字符串,则将其设置为原始帖子正文,并解析其余的args 正常。
同样重要的是要指出 rspec 只是 Rails 测试架构之上的 DSL。上面的post
方法是 ActionController::TestCase#post 而不是一些 rspec 发明。
【讨论】:
太棒了,我已经为这个确切的答案挖掘了很长时间 发生了奇怪的事情,这对一个文件有效,对另一个文件无效。我总是遇到奇怪的问题。 :)【参考方案3】:我们在控制器测试中所做的是明确设置 RAW_POST_DATA:
before do
@request.env['RAW_POST_DATA'] = payload.to_json
post :my_action
end
【讨论】:
如果您正在构建一个需要这种设置的库,这是使用符合 Rails 3、4 和 5 的语法获得这种行为的唯一方法。【参考方案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 bug,它阻止我们在功能测试中发送原始的帖子数据(但允许我们在集成测试),这是一个丑陋的解决方法,但它有效(在 rails 4.1.8 和 rspec-rails 3.0.0 上)。
【讨论】:
【参考方案6】:在 Rails 4 上:
params = shop: shop_id: new_subscrip.shop.id
post api_v1_shop_stats_path, params.to_json, 'CONTENT_TYPE' => 'application/json',
'ACCEPT' => 'application/json'
【讨论】:
【参考方案7】:@daniel-vandersluis 回答的一个小替代品,rails 3.0.6
,rspec 2.99
和 rspec-rails 2.99
:
describe "Example", :type => :request do
params = token: 0
post "/user/reset_password", params.merge(format: 'json').to_json, 'CONTENT_TYPE' => 'application/json', 'HTTP_ACCEPT' => 'application/json'
end
HTTP_ACCEPT
标头没有太大区别(它可以是 HTTP_ACCEPT
或只是 ACCEPT
)。但在我的情况下,要使其工作,参数必须:拥有.merge(format: 'json')
和.to_json
另一种变化:
describe "Example", :type => :request do
params = token: 0
post "/user/reset_password", params.merge(format: 'json').to_json, 'CONTENT_TYPE' => Mime::JSON.to_s, 'HTTP_ACCEPT' => Mime::JSON
end
它使用Mime::JSON
和Mime::JSON.to_s
而不是application/json
作为标头值。
【讨论】:
以上是关于使用 Rails 3.2.11 和 RSpec 发布原始 JSON 数据的主要内容,如果未能解决你的问题,请参考以下文章
ruby Rails Rspec模型使用rspec-rails,shoulda-matcher,shoulda-callbacks和factory_girl_rails测试骨架和备忘单。差不多
ruby Rails Rspec模型使用rspec-rails,shoulda-matcher,shoulda-callbacks和factory_girl_rails测试骨架和备忘单。差不多
ruby Rails Rspec模型使用rspec-rails,shoulda-matcher,shoulda-callbacks和factory_girl_rails测试骨架和备忘单。差不多