1
votes

I am trying to get some controller tests passing but when they hit the update and delete action, CanCan keeps throwing the Access Denied error despite being set in the abilities. These errors only seem to occur for members, as admins work fine.

Abilities.rb

def initialize(user)
  if user.has_role? :admin
    can :manage, :all
  elsif user.has_role? :member
    can :manage, PaymentMethod, :user_id => user.id
  end
end

User_Factory

FactoryGirl.define do
  factory :user do 
    sequence(:first_name) { |n| "John_#{n}" }
    sequence(:last_name) { |n| "Rambo_#{n}" }
    sequence(:email) { |n| "john_rambo_#{n}@example.com" }
    sequence(:username) { |n| "john_rambo_#{n}" }
    date_of_birth "03/12/1982"
    password 'password'
    password_confirmation 'password'
    picture_url File.open('spec/support/pictures/test.png')

    address

    factory :member do
      sequence(:last_name) { |n| "Member_#{n}" }
      roles :member
    end
  end
end

Controller_Spec.rb

describe "PUT /api/users/:user_id/payment_method/:id" do
    before(:each) do
      @user = FactoryGirl.create(:member)
      sign_in_user @user

      @payment_method = FactoryGirl.create(:credit_card, {:user_id => @user.id})
    end

    it "updates a users payment method" do
      attr_to_change = {
        brand: "mastercard",
        user_id: @user.id, 
        id: @payment_method.id
      }
      put :update, attr_to_change
      response.status.should == 200
      JSON.parse(response.body)["payment_method"]["brand"]
        .should == "mastercard"
    end
  end

  describe "DELETE /api/users/:user_id/payment_methods/:id" do
    before(:each) do
      @user = FactoryGirl.create(:member)
      sign_in_user @user

      @payment_method = FactoryGirl.create(:credit_card, {:user_id => @user.id})
    end

    it "destroys a users payment method" do
      delete :destroy, {:user_id => @user, :id => @payment_method.id}
      response.status.should == 200
    end
  end

Controller

class Api::PaymentMethodsController < Api::ApiController
  before_filter :clean_params, only: [:update, :create]

  def index
    @user = User.find(params["user_id"])
    render json: @user.payment_methods
  end

  def update
    pm_id = params.delete("id")
    params.delete("user_id")
    @payment_method = PaymentMethod.find(pm_id)
    if @payment_method.update_attributes(params)
      return render status: 200, json: @payment_method, root: :payment_method
    else
      return render status: 422, json: {success: false, errors: @payment_method.errors.full_messages.map{|error|{error: error}}}
    end
  end

  def create
    @payment_method = PaymentMethod.create_payment_method(params)
    if @payment_method
      render json: @payment_method, root: :payment_method
    else
      return render status: 422, json: {success: false, errors: @payment_method.errors.full_messages.map{|error|{error: error}}}
    end
  end

  def destroy
    @payment_method = PaymentMethod.find(params["id"]) 
    if @payment_method.destroy
      return render status: 200, json: {:message => "PaymentMethod Destroyed"}
    else
      return render status: 422, json: {success: false, errors: @payment_method.errors.full_messages.map{|error|{error: error}}}
    end
  end

  def clean_params
    ["controller", "action"].each do |delete_me|
      params.delete(delete_me)
    end
    params
  end
end

ApiController

class Api::ApiController < ApplicationController
  before_filter :authenticate_user!
  load_and_authorize_resource

  rescue_from CanCan::AccessDenied do |exception|
    return render :status => 401, :json => {:success => false, :errors => [exception.message]}
  end
end

Result of calling the delete action in the test:

delete :destroy, {:user_id => @user, :id => @payment_method.id}

#<ActionController::TestResponse:0x007fb999cf0080
 @blank=false,
 @block=nil,
 @body=
  ["{\"success\":false,\"errors\":[\"You are not authorized to access this page.\"]}"],
 @cache_control={},
 @charset="utf-8",
 @content_type=application/json,
 @etag=nil,
 @header={"Content-Type"=>"application/json; charset=utf-8"},
 @length=0,
 @request=

The other actions seem to work but for Update and Destroy, I keep getting that AccessDenied error. Any idea what I could be doing wrong?

1
Without knowing anything about @user it's impossible to speculate. You can verify your user abilities in the console as a sanity check.Dave Newton
please copy and paste all the output , as well as the source code of your controller, rspec file.Siwei
I added more of the codebase to help clarify.JustNeph

1 Answers

1
votes

Your ApiController appears to be namespaced, you'll need to change the before_filter to the following:

before_filter :authenticate_api_user!

Then, you need to adjust Cancan to use the current_api_user instead of current_user:

def current_ability
  @current_ability ||= ::Ability.new(current_api_user)
end

These links will help: