First, change spec / spec_helper.rb
as follows.
As stated in the pundit formula, you can use the rspec method for pundit by adding the following.
spec/spec_helper.rb
RSpec.configure do |config|
...
+ require "pundit/rspec"
end
Next, we will incorporate the test into spec / policies / post_policy_spec.rb
.
spec/policies/post_policy_spec.rb
# frozen_string_literal: true
require "rails_helper"
RSpec.describe PostPolicy, type: :policy do
let(:user) { create(:user) }
let(:post) { create(:post) }
subject { described_class }
permissions :index?, :show? do
it "Allowed when not logged in" do
expect(subject).to permit(nil, post)
end
end
permissions :create? do
it "Not allowed when not logged in" do
expect(subject).not_to permit(nil, post)
end
it "Allow when logged in" do
expect(subject).to permit(user, post)
end
end
permissions :update?, :destroy? do
it "Not allowed when not logged in" do
expect(subject).not_to permit(nil, post)
end
it "Not allowed when logged in but another user" do
expect(subject).not_to permit(user, post)
end
it "Allowed when logged in and the same user" do
post.user = user
expect(subject).to permit(user, post)
end
end
end
I think you can read it somehow, but just in case, please explain it.
permissions :index?, :show? do
it "Allowed when not logged in" do
expect(subject).to permit(nil, post)
end
end
Since index? And show? have the same conditions, they are tested together.
permit (nil, post)
specifies the login user model in the first argument and the target model in the second argument.
Then, it tests whether the user of the first argument has the authority of index? And show? Of the object of the second argument.
permissions :update?, :destroy? do
...
it "Not allowed when logged in but another user" do
expect(subject).not_to permit(user, post)
end
it "Allowed when logged in and the same user" do
post.user = user
expect(subject).to permit(user, post)
end
end
I'm still looking at not_to, but it's a test of not being allowed. And the final test passes by matching the owner user of the post.
Let's move rspec once.
Then spec / requests / v1 / posts_request_spec.rb
will be quite moss.
The cause is the same as above, because if #update or #destory is not the login of the user to which it belongs, it will be 403.
However, the ʻauthorized_user_headers helper used in
posts_request_spec.rbdoes
create (: user)` internally, so the logged-in user and post user cannot be matched.
Therefore, make the following modifications.
spec/support/authorization_spec_helper.rb
module AuthorizationSpecHelper
- def authorized_user_headers
- user = create(:user)
+ def authorized_user_headers(user = nil)
+ user = create(:user) if user.nil?
post v1_user_session_url, params: { email: user.email, password: "password" }
Now, if you pass it to ʻauthorized_user_headers` with no arguments, a user will be created internally, and if you pass a user as an argument, it will be used.
However, ʻauthorized_user_headers` becomes a little complicated and gets stuck in AbcSize of rubocop, so we will take the following measures.
diff:.rubocop.yml
...
+
+#AbcSize Default 15 is hard, so raise it to 20
+Metrics/AbcSize:
+ Max: 20
Now, it's finally time to fix the request spec.
spec/requests/v1/posts_request_spec.rb
...
it "Normal response code is returned" do
- put v1_post_url({ id: update_param[:id] }), params: update_param
+ post = Post.find(update_param[:id])
+ put v1_post_url({ id: update_param[:id] }), params: update_param, headers: authorized_user_headers(post.user)
expect(response.status).to eq 200
end
it "subject,body returns correctly" do
- put v1_post_url({ id: update_param[:id] }), params: update_param
+ post = Post.find(update_param[:id])
+ put v1_post_url({ id: update_param[:id] }), params: update_param, headers: authorized_user_headers(post.user)
json = JSON.parse(response.body)
expect(json["post"]["subject"]).to eq("update_subject test")
expect(json["post"]["body"]).to eq("update_body test")
end
it "Errors are returned when the parameter is invalid" do
- put v1_post_url({ id: update_param[:id] }), params: { subject: "" }
+ post = Post.find(update_param[:id])
+ put v1_post_url({ id: update_param[:id] }), params: { subject: "" }, headers: authorized_user_headers(post.user)
json = JSON.parse(response.body)
expect(json.key?("errors")).to be true
end
@@ -106,13 +109,13 @@ RSpec.describe "V1::Posts", type: :request do
create(:post)
end
it "Normal response code is returned" do
- delete v1_post_url({ id: delete_post.id })
+ delete v1_post_url({ id: delete_post.id }), headers: authorized_user_headers(delete_post.user)
expect(response.status).to eq 200
end
it "One less and returns" do
delete_post
expect do
- delete v1_post_url({ id: delete_post.id })
+ delete v1_post_url({ id: delete_post.id }), headers: authorized_user_headers(delete_post.user)
end.to change { Post.count }.by(-1)
end
There is no big change so much. Pass authorization by passing the owner of the post to ʻauthorized_user_headers`.
I will change this a little more intuitively. The process of determining whether it belongs to you is not limited to post, and it seems that it will be diverted to various models in the future.
def update?
@record.user == @user
end
def destroy?
@record.user == @user
end
Therefore, create a private method in application_policy.rb to determine if it belongs to you.
app/policies/application_policy.rb
class ApplicationPolicy
...
+ private
+
+ def mine?
+ @record.user == @user
+ end
#
# scope
#
class Scope
...
I will reflect it in post_policy.
app/policies/post_policy.rb
def update?
- @record.user == @user
+ mine?
end
def destroy?
- @record.user == @user
+ mine?
end
It was refreshing.
Now, from now on, the action to be executed only when the model related to user owns it is just to create a policy file and place the mine?
method. It's super easy.
→ Building a bulletin board API with authentication authorization in Rails 6 # 17 Adding administrator privileges [To the serial table of contents]