1

I am implementing rspec test for destroy action, the concept is that signed in user can only destroy his own posts, and cannot destroy posts creates by other users.

The `new_post` is created by a user named `creator`, and another user named `user1` signed in and try to delete the `new_post`, it should not be able to delete it, because of the `  before_action :authenticate_user!, only: %i[create destroy]` in Posts controller

Posts controller.

class PostsController < ApplicationController
  before_action :set_post, only: %i[show edit update destroy]
  before_action :current_user, only: %i[create destroy]
  before_action :authenticate_user!, only: %i[create destroy]
  .
  .
  .

  def destroy
    @post.destroy
    respond_to do |format|
      format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

  def set_post
    @post = Post.find(params[:id])
  end

  
  def post_params
    params.require(:post).permit(:content, :picture)
  end
end

users controller spec

require 'rails_helper'
RSpec.describe PostsController, type: :controller do
  context  'DELETE #destroy' do

    let(:user1) {User.create!(name:"John", email:"john@mail.com", password:"password")}

    let(:creator) { User.create!(name: "creator", email: "creaor@gmail.com", password: "password") }

    let(:new_post){creator.posts.create!(content: "Neque porro quisquam est qui dolorem ipsum")}

    
    it 'A user cannot delete a post created by other user' do
      sign_in user1
      p (new_post)
      expect { delete :destroy, params: { id: new_post.id } }.to change(Post, :count).by(0)
      
    end
  end
end

Failures:

  1) PostsController DELETE #destroy A user cannot delete a post created by other user
     Failure/Error: expect { delete :destroy, params: { id: new_post.id } }.to change(Post, :count).by(0)
       expected `Post.count` to have changed by 0, but was changed by -1
Community
  • 1
  • 1

2 Answers2

2

I believe you need to add an authorization check to your code. authenticate_user! authenticates that the person making the request is logged in. However, it does not check if the user is authorized to make the request they're making.

See Authentication versus Authorization for a bit more discussion on the two concepts. And take a look at https://stackoverflow.com/a/25654844/868533 for a good overview of popular authorization gems in Rails. To be clear, you almost definitely want a way to authenticate users (Devise) along with an authorization gem.

Assuming you decide to go with CanCanCan (which is a common option that I've used in past), you'd add an Ability class like:

class Ability
  include CanCan::Ability

  def initialize(user)
    if user.present?
      can :destroy, Post, user_id: user.id
    end
  end
end

Then you could add before_action :check_authorization, only: %i[destroy] as a new before_action on your controller and your tests should pass without any modification.

supersam654
  • 2,638
  • 28
  • 28
  • cancancan or pundit are some notable options. – BKSpurgeon Sep 29 '19 at 21:48
  • Thank you for clarification, that's what I really need, it was authorization and not only authentication, so I just created a simple `#authorized?` method and the test passed `def authorized? redirect to root_path unless @post.user_id == current_user.id end` – Othmane Namani Sep 30 '19 at 09:12
1

Remember. you are writing a controller test. So this test is unit test. There are two main ways to authorize in Devise. They are authorize routes and authorize controller. If you using authorize routes, when you write rspec for controller, you must use stub to counterfeit an authorize access.

Bùi Nhật Duy
  • 311
  • 2
  • 9
  • Thank you for clarification, that's what I really need, it was authorization and not only authentication, so I just created a simple #authorized? method and the test passed def authorized? redirect to root_path unless @post.user_id == current_user.id end – Othmane Namani Sep 30 '19 at 09:14