我正在做Michael Hartl的精彩Ruby on Rails Tutorial而且我看到很多人遇到Chapter 9, Exercise 9的问题。我也有相当多的问题,但我没有看到其他任何问题(或解决方案)。所以这里是供参考,我希望它能帮助别人。我的环境是Ruby 2.1.1,Rails 4.1.4,RSpec 3.0.0和Capybara 2.4.1。这与书中描述的环境不同,但使用最新的工具和版本是乐趣的一部分。
问题是设计测试并修改UsersController#destroy
操作以防止管理员用户自行删除。由于“用户”页面中的“删除”链接对于每个用户都是隐藏的,即使是admin: true
用户,也可以向DELETE
发出users_path(user)
请求。
describe 'Admin cannot delete himself/herself' do
let(:admin_user) { FactoryGirl.create(:admin) }
before { log_in admin_user }
specify { expect { delete user_path(admin_user) }.not_to change(User, :count) }
end
然而,运行此测试会通过:
$ rspec -e "cannot delete himself"
Run options: include {:full_description=>/cannot\ delete\ himself/}
.
Finished in 1.59 seconds (files took 0.98179 seconds to load)
1 example, 0 failures
有点可疑。这是log/test.log
的输出:
ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations"
(0.1ms) begin transaction
(0.0ms) SAVEPOINT active_record_1
User Exists (0.1ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('person_1@example.com') LIMIT 1
Binary data inserted for `string` type on column `password_digest`
SQL (0.2ms) INSERT INTO "users" ("admin", "created_at", "email", "name", "password_digest", "remember_token", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?) [["admin", "t"], ["created_at", "2014-08-13 13:56:11.310082"], ["email", "person_1@example.com"], ["name", "Person 1"], ["password_digest", "$2a$04$.ScHVB84mm4/4G12Vcpudu/k741nyrXM4vLtZa7XgtecNB6uOHNPy"], ["remember_token", "c527083d0ba4581ed92f258eede7d1377dcf7d5b"], ["updated_at", "2014-08-13 13:56:11.310082"]]
(0.0ms) RELEASE SAVEPOINT active_record_1
Started GET "/login" for 127.0.0.1 at 2014-08-13 15:56:11 +0200
Processing by SessionsController#new as HTML
Rendered sessions/new.html.erb within layouts/application (1.8ms)
Rendered layouts/_internet_explorer_shim.html.erb (0.3ms)
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' LIMIT 1
Rendered layouts/_header.html.erb (3.0ms)
Rendered layouts/_footer.html.erb (0.4ms)
Completed 200 OK in 1456ms (Views: 1450.2ms | ActiveRecord: 0.1ms)
Started POST "/sessions" for 127.0.0.1 at 2014-08-13 15:56:12 +0200
Processing by SessionsController#create as HTML
Parameters: {"utf8"=>"✓", "email"=>"person_1@example.com", "password"=>"[FILTERED]", "commit"=>"Log in"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'person_1@example.com' LIMIT 1
(0.0ms) SAVEPOINT active_record_1
SQL (0.2ms) UPDATE "users" SET "remember_token" = ?, "updated_at" = ? WHERE "users"."id" = 362 [["remember_token", "3df781e414c64f2f72cf1e08594f92595922329a"], ["updated_at", "2014-08-13 13:56:12.870234"]]
(0.0ms) RELEASE SAVEPOINT active_record_1
Redirected to http://www.example.com/users/362
Completed 302 Found in 4ms (ActiveRecord: 0.4ms)
Started GET "/users/362" for 127.0.0.1 at 2014-08-13 15:56:12 +0200
Processing by UsersController#show as HTML
Parameters: {"id"=>"362"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 362]]
Rendered users/show.html.erb within layouts/application (0.4ms)
Rendered layouts/_internet_explorer_shim.html.erb (0.0ms)
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = '3df781e414c64f2f72cf1e08594f92595922329a' LIMIT 1
Rendered layouts/_header.html.erb (1.0ms)
Rendered layouts/_footer.html.erb (0.1ms)
Completed 200 OK in 5ms (Views: 3.5ms | ActiveRecord: 0.2ms)
(0.1ms) SELECT COUNT(*) FROM "users"
Started DELETE "/users/362" for 127.0.0.1 at 2014-08-13 15:56:12 +0200
Processing by UsersController#destroy as HTML
Parameters: {"id"=>"362"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' LIMIT 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' LIMIT 1
Redirected to http://www.example.com/login
Filter chain halted as :logged_in_user rendered or redirected
Completed 302 Found in 1ms (ActiveRecord: 0.1ms)
(0.1ms) SELECT COUNT(*) FROM "users"
(0.1ms) rollback transaction
测试通过,因为在尝试删除用户时,过滤器
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
按照日志中此行的建议开始:
Redirected to http://www.example.com/login
Filter chain halted as :logged_in_user rendered or redirected
由于某种原因,用户未登录,并且当他/她到达受保护的页面时,会被重定向到登录页面。实际上,您可以在logger.warn "<message>"
中添加UsersController#destroy
,或者在puts User.count
块内的expect
行之后添加specify
,以确定没有删除任何用户。< / p>
为什么:admin_user
没有登录?
答案 0 :(得分:0)
根本问题是你最终使用capybara方法登录(访问,点击)等,但是删除请求的rails集成测试方法。
由于这两者完全分开,因此您的删除请求不会使用登录过程设置的Cookie。请注意,在您链接的页面中,它们会
before { sign_in non_admin, no_capybara: true }
这会导致sign_in
帮助程序直接设置cookie而不是转到页面,填写表单等。
答案 1 :(得分:0)
简而言之,问题在于Capybara的模拟会话与RSpec不同。登录是使用Capybara完成的,而DELETE
请求是直接从RSpec发送的,当RSpec尝试删除管理员用户时,存储在数据库中的remember_token
不匹配。
要执行纯粹的Capybara操作序列,我们可以使用带有this Gist的Rails控制台(省略长输出):
$ rails c -e test
Loading test environment (Rails 4.1.4)
2.1.1 :001 > User.create(name: 'Foo Bar', email: 'foo@bar.net', password: 'foobar', password_confirmation: 'foobar', admin: true)
...
2.1.1 :002 > Capybara.app = app.instance_variable_get("@app"); nil
=> nil
2.1.1 :003 > cap = Object.new.instance_eval { extend Capybara::DSL; self }
=> #<Object:0x00000004a5b758>
2.1.1 :004 > cap.visit app.login_path
...
2.1.1 :005 > cap.fill_in 'Email', with: 'foo@bar.net'
=> "foo@bar.net"
2.1.1 :006 > cap.fill_in 'Password', with: 'foobar'
=> "foobar"
2.1.1 :007 > cap.click_button 'Log in'
...
2.1.1 :009 > admin_user = User.find_by(email: 'foo@bar.net')
...
2.1.1 :010 > cap.page.driver.delete app.user_path(admin_user)
...
2.1.1 :011 > User.count
(0.1ms) SELECT COUNT(*) FROM "users"
=> 0
2.1.1 :013 >
此外,查看问题中的原始日志,我们可以看到登录后的remember_token
与DELETE
请求中设置的不同。
总之,编写测试的正确方法是使用no_capybara: true
:
describe 'Admin cannot delete himself/herself' do
let(:admin_user) { FactoryGirl.create(:admin) }
before { log_in admin_user, no_capybara: true }
specify { expect { delete user_path(admin_user) }.not_to change(User, :count) }
end
产生以下结果:
$ rspec -e "cannot delete himself"
Run options: include {:full_description=>/cannot\ delete\ himself/}
F
Failures:
1) Admin cannot delete himself with a DELETE request should not change #count
Failure/Error: specify { expect { delete user_path(admin_user) }.not_to change(User, :count) }
expected #count not to have changed, but did change from 1 to 0
# ./spec/requests/admin_autodelete_spec.rb:7:in `block (2 levels) in <top (required)>'
Finished in 0.04263 seconds (files took 0.99411 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/requests/admin_autodelete_spec.rb:7 # Admin cannot delete himself with a DELETE request should not change #count
现在你可以继续解决这个问题。我希望这有帮助!