require 'test_helper'
require 'support/models'
load 'support/schema.rb'

class ActiveSupport::TestCase
  def self.should_handle_basic_role_manipulation(subject_class)
    context "for #{subject_class.to_s}: " do
      setup do
        Role.delete_all
        [subject_class, Foo, Bar, Uuid].each { |c| c.delete_all }
        @subject1 = subject_class.create!
        @subject2 = subject_class.create!
        @foo = Foo.create!
        @bar = Bar.create!
        @uuid = Uuid.create!(:uuid => "C41642EE-2780-0001-189F-17F3101B26E0")
      end

      should "not have any roles by default" do
        %w(user manager admin owner).each do |role|
          assert_false @subject1.has_role?(role)
          assert_false @subject1.has_role?(role, @foo)
        end
      end

      context "Given a global role" do
        setup do
          @subject1.has_role!(:admin)
        end

        should_change "create a role successfully", :by => 1 do  
          Role.count
        end

        should "not distinguish between symbols and strings" do
          assert @subject1.has_role?("admin")
        end

        should "assign roles to a specific user" do
          assert @subject1.has_role?(:admin)
          assert_false @subject2.has_role?(:admin)
        end

        should "distinguish between names of roles" do
          assert_false @subject1.has_role?(:manager)
        end

        should "not count a global role as an object role" do
          assert_false @subject1.has_role?(:admin, @foo)
          assert_false @subject1.has_roles_for?(@foo)
        end

        should "not let objects accept it" do
          [@foo,@bar].each do |obj|
            assert_false obj.accepts_role?(:admin, @subject1)
          end
        end

        should "reading roles should not add another role" do
          assert_no_difference "Role.count" do
            @subject1.has_role!(:admin)
          end
        end

        context "with two users" do
          setup do
            @subject2.has_role!(:admin)
          end
          
          should "remove assignments to roles through has_no_role!" do
            @subject1.has_no_role!(:admin)
            assert_false @subject1.has_role?(:admin)
          end

          should "only delete roles when they have no assignments" do
            assert_no_difference 'Role.count' do
              @subject1.has_no_role!(:admin)
            end

            assert_difference 'Role.count', -1 do
              @subject2.has_no_role!(:admin)
            end

            assert_false @subject1.has_role?(:admin)          
            assert_false @subject2.has_role?(:admin)
          end

        end
      end

      context "Given an object role" do
        setup do
          @subject1.has_role!(:admin, @foo)
        end

        should_change "create a role successfully", :by => 1 do 
          Role.count 
        end

        should "assign roles to a specific user" do
          assert @subject1.has_role?(:admin, @foo)
          assert @subject1.has_roles_for?(@foo)
          assert @subject1.has_role_for?(@foo)

          roles = @subject1.roles_for(@foo)
          assert_equal 1, roles.length
          assert_equal "admin", roles.first.name

          assert_false @subject1.has_role?(:manager, @foo)
          assert_false @subject2.has_role?(:admin, @foo)
          assert_false @subject2.has_roles_for?(@foo)
        end

        should "let the object accept it" do 
          assert @foo.accepts_role?(:admin, @subject1)
          assert_false @bar.accepts_role?(:admin, @subject1)
          
          @bar.accepts_role!(:admin, @subject1)
          assert @bar.accepts_role?(:admin, @subject1)
          
          @bar.accepts_no_role!(:admin, @subject1)
          assert_false @bar.accepts_role?(:admin, @subject1)
        end

        should "allow the object to list its roles" do 
          assert_equal 1, @foo.roles.size
        end

        should "not count an object role as an global role" do
          assert_false @subject1.has_role?(:admin)
        end

        should "reuse roles" do
          assert_no_difference 'Role.count' do
            @subject2.has_role!(:admin, @foo)
          end

          assert_difference 'Role.count', 1 do
            @subject2.has_role!(:manager, @foo)
          end
        end

        context "with two users" do
          setup do
            @subject1.has_role!(:admin)
            @subject2.has_role!(:admin, @foo)
            @subject1.has_role!(:admin, @bar)
          end

          should "let objects be able to query their subjects" do
            assert_same_elements @foo.list_subjects_for(:admin), [@subject1, @subject2]
            assert_same_elements @foo.list_subjects_all_roles, [@subject1, @subject2]
          end

          should "dynamically build methods to query" do
            assert_same_elements @foo.list_subjects_for(:admin), @foo.list_admins
          end

          should "remove assignments to object roles through has_no_role!" do
            @subject1.has_no_role!(:admin, @foo)

            assert @subject1.has_role?(:admin)
            assert @subject1.has_role?(:admin, @bar)
            assert_false @subject1.has_role?(:admin, @foo)
          end

          should "only delete roles when they have no assignments" do
            assert_difference 'Role.count', 0 do
              @subject2.has_no_role!(:admin, @foo)
            end

            assert_false @subject2.has_role?(:admin)

            assert_difference 'Role.count', -1 do
              @subject1.has_no_role!(:admin, @foo)
            end

            assert_false @subject1.has_role?(:admin, @foo)          
          end
          
          should "delete all roles for a given object with has_no_roles_for!" do
            assert_difference 'Role.count', 1 do
              @subject1.has_role!(:manager, @bar)
            end
            
            assert_difference '@subject1.assigned_roles.size', -2 do 
              @subject1.has_no_roles_for!(@bar)
            end
            
            assert_false @subject1.has_roles_for?(@bar)
            assert @subject1.has_role?(:admin)
            assert @subject1.has_role?(:admin, @foo)
          end
          
          should "delete all roles with has_no_roles!" do
            @subject1.has_no_roles!
            assert_equal @subject1.assigned_roles.length, 0
          end
        end

      end

    end
  end
end

class RolesTest < ActiveSupport::TestCase
  context "role testing" do
    should_handle_basic_role_manipulation(User)
    should_handle_basic_role_manipulation(Group)
    should_handle_basic_role_manipulation(UserNoGroup)

    should "models without groups enabled raise errors when group commands are queried" do
      @usernogroup = UserNoGroup.create!
      assert_raises RuntimeError do 
        @usernogroup.groups
      end
    end
    
    context "group testing: " do
      setup do
        @user1 = User.create!
        @user2 = User.create!
        @user3 = User.create!
        @group1 = Group.create!
        @group2 = Group.create!
        @group3 = Group.create!
        @group4 = Group.create!
        @group5 = Group.create!

        
        @foo = Foo.create!
        @bar = Bar.create!
      end
      
      should "be able to assign users to a group" do
        @user1.in_group!(@group1)
        @user2.in_group!(@group1)
        @user1.in_group!(@group2)
        
        assert_same_elements @user1.groups, [@group1, @group2]
        assert_same_elements @group1.children, [@user1, @user2]
        assert_equal @group2.children, [@user1]
        
        @user1.not_in_group!(@group1)
        
        assert_same_elements @user1.groups, [@group2]
      end
      
      context "given test users and groups" do
        setup do
          @user1.in_group!(@group1)
          @group1.in_group!(@group2)
          @group2.in_group!(@group4)

          @user1.in_group!(@group3)
          @user2.in_group!(@group2)
        end

        should "avoid circularity" do
          @group4.in_group!(@group5)
          
          assert_raises RuntimeError do 
            @group5.in_group!(@group4)
          end

          assert_raises RuntimeError do 
            @group5.in_group!(@group1)
          end
        end
        
        should "not follow meaningless member assignments" do
          @group5.has_role!(:member, @user1)
          assert_equal @group5.children, []
        end

        should "handle nested groups" do
          assert_same_elements @user1.groups(:nested => false), [@group1, @group3]
          assert_same_elements @user1.groups, [@group1, @group2, @group3, @group4]
          assert_same_elements @user2.groups, [@group2, @group4]
          
          assert_same_elements @group4.children, [@group2, @group1, @user1, @user2]
          assert_same_elements @group2.children, [@group1, @user1, @user2]
          
          assert @group4.accepts_role?(:member, @user1)
          assert @group2.accepts_role?(:member, @user1)
          
          assert_false @group4.accepts_role?(:member, @user1, :check_groups => false)
        end
        
        should "check roles based on groups" do

          assert @user2.has_role?(:member, @group2)
        
          @group2.has_role!(:owner, @foo)
          
          assert @user2.has_role?(:owner, @foo)
          assert_false @user2.has_role?(:owner, @foo, :check_groups => false)
          
          assert @user1.has_role?(:owner, @foo)
          assert_false @user1.has_role?(:owner, @foo, :check_groups => false)
          
          
          assert_same_elements @foo.list_owners, [@group2, @group1, @user1, @user2]
          assert_same_elements @foo.list_owners(:check_groups => false), [@group2]
          
          assert @foo.accepts_role?(:owner, @user1)
          assert_false @foo.accepts_role?(:owner, @user1, :check_groups => false)
        end
        
      end
      
    end
  end
  
end