Skip to content
This repository was archived by the owner on Jul 24, 2020. It is now read-only.

[1606] Write Tests for Ability #1626

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions spec/models/ability_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# frozen_string_literal: true
require 'spec_helper'
require 'cancan/matchers'

describe Ability, type: :model do
before(:each) do
mock_app_config(enable_renewals: true)
end

shared_examples 'all users' do
it { is_expected.to be_able_to(:hide, Announcement) }
end

shared_examples 'normal user' do
it { is_expected.to be_able_to(:update, User, id: user.id) }
it { is_expected.to be_able_to(:show, User, id: user.id) }

it { is_expected.to be_able_to(:read, EquipmentModel) }
it { is_expected.to be_able_to(:view_detailed, EquipmentModel) }

it { is_expected.to be_able_to(:read, Reservation, reserver_id: user.id) }
it { is_expected.to be_able_to(:create, Reservation, reserver_id: user.id) }
it do
expect(ability).to be_able_to(:destroy, Reservation, reserver_id: user.id,
checked_out: nil)
end
it { is_expected.to be_able_to(:renew, Reservation, reserver_id: user.id) }
it { is_expected.to be_able_to(:update_index_dates, Reservation) }
it { is_expected.to be_able_to(:view_all_dates, Reservation) }

it { is_expected.to be_able_to(:reload_catalog_cart, :all) }
it { is_expected.to be_able_to(:update_cart, :all) }
end

context 'superuser' do
subject(:ability) { Ability.new(UserMock.new(:superuser)) }
it_behaves_like 'all users'
it { is_expected.to be_able_to(:view_as, :superuser) }
it { is_expected.to be_able_to(:change, :views) }
it { is_expected.to be_able_to(:manage, :all) }
end

context 'admin' do
subject(:ability) { Ability.new(UserMock.new(:admin)) }
it_behaves_like 'all users'
it { is_expected.to be_able_to(:change, :views) }
it { is_expected.to be_able_to(:manage, :all) }
it { is_expected.not_to be_able_to(:view_as, :superuser) }
it { is_expected.not_to be_able_to(:appoint, :superuser) }
it { is_expected.not_to be_able_to(:destroy, User, role: 'superuser') }
it { is_expected.not_to be_able_to(:update, User, role: 'superuser') }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these two fail; I haven't been able to determine why

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll dig into cancancan and try to figure this out

it { is_expected.not_to be_able_to(:access, :rails_admin) }
end

context 'checkout person' do
let!(:user) { UserMock.new(:checkout_person) }
subject(:ability) { Ability.new(user) }
it_behaves_like 'all users'
it_behaves_like 'normal user'

it { is_expected.to be_able_to(:manage, Reservation) }
it { is_expected.not_to be_able_to(:archive, Reservation) }

it { is_expected.to be_able_to(:read, User) }
it { is_expected.to be_able_to(:update, User) }
it { is_expected.to be_able_to(:find, User) }
it { is_expected.to be_able_to(:autocomplete_user_last_name, User) }

it { is_expected.to be_able_to(:read, EquipmentItem) }

context 'checkout persons can edit' do
it do
mock_app_config(checkout_persons_can_edit: true)
ability = Ability.new(user)
expect(ability).to be_able_to(:update, Reservation)
end
end
context 'checkout persons cannot edit' do
it do
mock_app_config(checkout_persons_can_edit: false)
ability = Ability.new(user)
expect(ability).not_to be_able_to(:update, Reservation)
end
end
context 'new users enabled' do
it do
mock_app_config(enable_new_users: true)
ability = Ability.new(user)
expect(ability).to be_able_to(:create, User)
expect(ability).to be_able_to(:quick_new, User)
expect(ability).to be_able_to(:quick_create, User)
end
end
context 'new users disabled' do
it do
mock_app_config(enable_new_users: false)
ability = Ability.new(user)
expect(ability).not_to be_able_to(:create, User)
expect(ability).not_to be_able_to(:quick_new, User)
expect(ability).not_to be_able_to(:quick_create, User)
end
end
end

context 'normal user' do
let!(:user) { UserMock.new(:user) }
subject(:ability) { Ability.new(user) }
it_behaves_like 'all users'
it_behaves_like 'normal user'
end

context 'guest' do
before { mock_app_config(enable_guests: true) }
subject(:ability) { Ability.new(UserMock.new(:guest)) }
it_behaves_like 'all users'

it { is_expected.to be_able_to(:read, EquipmentModel) }
it { is_expected.to be_able_to(:empty_cart, :all) }
it { is_expected.to be_able_to(:reload_catalog_cart, :all) }
it { is_expected.to be_able_to(:update_cart, :all) }

context 'new users enabled' do
it do
mock_app_config(enable_new_users: true)
ability = Ability.new(UserMock.new(:guest))
expect(ability).to be_able_to(:create, User)
end
end
context 'new users disabled' do
it do
mock_app_config(enable_new_users: true)
ability = Ability.new(UserMock.new(:guest))
expect(ability).to be_able_to(:create, User)
end
end
end

context 'banned' do
subject(:ability) { Ability.new(UserMock.new(:banned)) }
it_behaves_like 'all users'
end

context 'renewals disabled' do
shared_examples 'cannot renew' do |role|
it do
mock_app_config(enable_renewals: false)
ability = Ability.new(UserMock.new(role))
expect(ability).not_to be_able_to(:renew, Reservation)
end
end
[:admin, :checkout_person, :user].each do |role|
it_behaves_like 'cannot renew', role
end
end
end
21 changes: 21 additions & 0 deletions spec/support/mockers/category_mock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true
require Rails.root.join('spec/support/mockers/mocker.rb')
require Rails.root.join('spec/support/mockers/equipment_model_mock.rb')

class CategoryMock < Mocker
def self.klass
Category
end

def self.klass_name
'Category'
end

private

def with_equipment_models(models: nil, count: 1)
models ||= Array.new(count) { EquipmentModelMock.new }
parent_has_many(mocked_children: models, parent_sym: :category,
child_sym: :equipment_models)
end
end
21 changes: 21 additions & 0 deletions spec/support/mockers/equipment_item_mock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true
require Rails.root.join('spec/support/mockers/mocker.rb')
require Rails.root.join('spec/support/mockers/equipment_model_mock.rb')

class EquipmentItemMock < Mocker
def self.klass
EquipmentItem
end

def self.klass_name
'EquipmentItem'
end

private

def with_model(model: nil)
model ||= EquipmentModelMock.new
child_of_has_many(mocked_parent: model, parent_sym: :equipment_model,
child_sym: :equipment_items)
end
end
32 changes: 32 additions & 0 deletions spec/support/mockers/equipment_model_mock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true
require Rails.root.join('spec/support/mockers/mocker.rb')
require Rails.root.join('spec/support/mockers/category_mock.rb')
require Rails.root.join('spec/support/mockers/equipment_item_mock.rb')

class EquipmentModelMock < Mocker
def self.klass
EquipmentModel
end

def self.klass_name
'EquipmentModel'
end

private

def with_item(item:)
with_items(items: [item])
end

def with_items(items: nil, count: 1)
items ||= Array.new(count) { EquipmentItemMock.new }
parent_has_many(mocked_children: items, parent_sym: :equipment_model,
child_sym: :equipment_items)
end

def with_category(cat: nil)
cat ||= CategoryMock.new
child_of_has_many(mocked_parent: cat, parent_sym: :category,
child_sym: :equipment_models)
end
end
103 changes: 103 additions & 0 deletions spec/support/mockers/mocker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# frozen_string_literal: true
require 'rspec/mocks/standalone'

# This class behaves as an extension of rspec-mocks' instance_spy.
# It is intended to be extended and used to make mocking models much simpler!
#
# To create a new subclass, the following methods must be overridden:
# - self.klass must return the class that the subclass is mocking
# - self.klass_name must return a string that matches the class being mocked
#
# Some examples using the EquipmentModelMock subclass:
# A mock that can be "found" with EquipmentModel#find:
# EquipmentModelMock.new(traits: [:findable])
# A mock with a set of attributes:
# EquipmentModelMock.new(name: 'Camera', late_fee: 3)
# A mock with attributes and method stubs:
# EquipmentModelMock.new(name: 'Camera', model_restriced: false)
# A findable mock with attributes:
# EquipmentModelMock.new(traits: [:findable], name: 'Camera')
#
# A trait can be any method that exists on the mocker superclass or child class.
# To create an EquipmentModel that belongs to an existing category, camera:
# EquipmentModelMock.new(traits: [[:with_category, cat: camera]])
#
# Use caution before adding methods -- any method defined here should be usable
# by all subclasses, with the exception of the association stub methods.

class Mocker < RSpec::Mocks::InstanceVerifyingDouble
include RSpec::Mocks

FIND_METHODS = [:find, :find_by_id].freeze

def initialize(traits: [], **attrs)
# from RSpec::Mocks::ExampleMethods
# combination of #declare_verifying_double and #declare_double
ref = ObjectReference.for(self.class.klass_name)
RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
block.call(ref)
end
attrs ||= {}
super(ref, attrs)
as_null_object
process_traits(traits)
end

def process_traits(traits)
traits.each { |t| send(*t) }
end

private

def klass
Object
end

def klass_name
'Object'
end

def spy
self
end

# lets us use rspec-mock syntax in mockers
def receive(method_name, &block)
Matchers::Receive.new(method_name, block)
end

def allow(target)
AllowanceTarget.new(target)
end

# Traits
def findable
id = FactoryGirl.generate(:unique_id)
allow(spy).to receive(:id).and_return(id)
FIND_METHODS.each do |method|
allow(self.class.klass).to receive(method)
allow(self.class.klass).to receive(method).with(id).and_return(spy)
allow(self.class.klass).to receive(method).with(id.to_s).and_return(spy)
end
end

# Generalized association stubs
def child_of_has_many(mocked_parent:, parent_sym:, child_sym:)
allow(spy).to receive(parent_sym).and_return(mocked_parent)
children = if mocked_parent.send(child_sym).is_a? Array
mocked_parent.send(child_sym) << spy
else
[spy]
end
allow(mocked_parent).to receive(child_sym).and_return(children)
end

def parent_has_many(mocked_children:, parent_sym:, child_sym:)
if mocked_children.is_a? Array
mocked_children.each do |child|
allow(child).to receive(parent_sym).and_return(spy)
end
end
allow(spy).to receive(child_sym).and_return(mocked_children)
end
end
19 changes: 19 additions & 0 deletions spec/support/mockers/reservation_mock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true
require Rails.root.join('spec/support/mockers/mocker.rb')

class ReservationMock < Mocker
def self.klass
Reservation
end

def self.klass_name
'Reservation'
end

private

def for_user(user:)
child_of_has_many(mocked_parent: user, parent_sym: :reserver,
child_sym: :reservations)
end
end
18 changes: 18 additions & 0 deletions spec/support/mockers/user_mock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require Rails.root.join('spec/support/mockers/mocker.rb')

class UserMock < Mocker
def initialize(role = :user, traits: [], **attrs)
attrs = FactoryGirl.attributes_for(role).merge attrs
traits = [:findable] if traits.empty?
super(traits: traits, **attrs)
end

def self.klass
User
end

def self.klass_name
'User'
end
end