This repository was archived by the owner on Jul 24, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 57
[1606] Write Tests for Ability #1626
Open
esoterik
wants to merge
3
commits into
master
Choose a base branch
from
1606_ability_tests
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') } | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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