Created
March 18, 2015 16:45
-
-
Save alexshenia/b3f82c6e9dda63c1f162 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
# https://gist.github.com/rilian/e89d9dbc096f56ebcb1d | |
# | |
# Custom rspec matcher for testing CanCan abilities. | |
# Originally inspired by https://github.com/ryanb/cancan/wiki/Testing-Abilities | |
# | |
# Usage: | |
# expect.to have_abilities(:create).on(Post.new) | |
# expect.to have_abilities([:read, :update].on(post) | |
# expect.to have_abilities({manage: false, destroy: true}.on(post) | |
# expect.to have_abilities({create: false}.on(Post.new) | |
# expect.to not_have_abilities(:update.on(post) | |
# expect.to not_have_abilities([:update, :destroy].on(post) | |
# | |
# WARNING: never use "should_not have_abilities" or you may get false positives due to | |
# whitelisting/blacklisting issues. Use "should not_have_abilities" instead. | |
RSpec::Matchers.define :have_abilities do |actions| | |
include HaveAbilitiesMixin | |
chain :on do |obj| | |
@obj = obj | |
end | |
match do |ability| | |
verify_ability_type(ability) | |
@expected_hash = build_expected_hash(actions, default_expectation: true) | |
@actual_hash = {} | |
@expected_hash.each do |action, _| | |
@actual_hash[action] = ability.can?(action, @obj) | |
end | |
@actual_hash == @expected_hash | |
end | |
description do | |
obj_name = @obj.class.name | |
obj_name = @obj.to_s.capitalize if [Class, Module, Symbol].include?(@obj.class) | |
"have abilities #{@expected_hash.keys.join(', ')} on #{obj_name}" | |
end | |
failure_message do | |
obj_name = @obj.class.name | |
obj_name = @obj.to_s.capitalize if [Class, Module, Symbol].include?(@obj.class) | |
"expected user to have abilities: #{@expected_hash} for #{obj_name}, but got #{@actual_hash}" | |
end | |
end | |
RSpec::Matchers.define :not_have_abilities do |actions| | |
include HaveAbilitiesMixin | |
chain :on do |obj| | |
@obj = obj | |
end | |
match do |ability| | |
verify_ability_type(ability) | |
if actions.is_a?(Hash) | |
raise ArgumentError.new('You cannot pass a hash to not_have_abilities. Use have_abilities instead.') | |
end | |
@expected_hash = build_expected_hash(actions, default_expectation: false) | |
@actual_hash = {} | |
@expected_hash.each do |action, _| | |
@actual_hash[action] = ability.can?(action, @obj) | |
end | |
@actual_hash == @expected_hash | |
end | |
description do | |
obj_name = @obj.class.name | |
obj_name = @obj.to_s.capitalize if [Class, Module, Symbol].include?(@obj.class) | |
"not have abilities #{@expected_hash.keys.join(', ')} on #{obj_name}" if @expected_hash.present? | |
end | |
failure_message do | |
obj_name = @obj.class.name | |
obj_name = @obj.to_s.capitalize if [Class, Module, Symbol].include?(@obj.class) | |
"expected user NOT to have abilities #{@expected_hash.keys.join(', ')} for #{obj_name}, but got #{@actual_hash}" | |
end | |
end | |
module HaveAbilitiesMixin | |
def build_expected_hash(actions, default_expectation:) | |
return actions if actions.is_a?(Hash) | |
expected_hash = {} | |
if actions.is_a?(Array) | |
# If given an array like [:create, read] build a hash like: | |
# {create: default_expectation, read: default_expectation} | |
actions.each { |action| expected_hash[action] = default_expectation } | |
elsif actions.is_a?(Symbol) | |
# Build a hash if it's just a symbol. | |
expected_hash = { actions => default_expectation } | |
end | |
expected_hash | |
end | |
def verify_ability_type(ability) | |
unless ability.class.ancestors.include?(CanCan::Ability) | |
raise TypeError.new("subject must mixin CanCan::Ability, got a #{ability.class.name} class.") | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment