Search code examples
rubyruby-on-rails-5factory-botrspec-rails

Rails 5 with Devise, testing Controllers with Rspec (destroy action)


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:"[email protected]", password:"password")}

    let(:creator) { User.create!(name: "creator", email: "[email protected]", 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

Solution

  • 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.