#!/usr/bin/env ruby

$:.unshift("../../lib") if __FILE__ =~ /\.rb$/

require 'puppettest'

class TestUserProvider < Test::Unit::TestCase
    include PuppetTest::FileTesting

    def setup
        super
        setme()
        @@tmpusers = []
        @provider = nil
        assert_nothing_raised {
            @provider = Puppet::Type.type(:user).defaultprovider
        }

        assert(@provider, "Could not find default user provider")

    end

    def teardown
        @@tmpusers.each { |user|
            unless missing?(user)
                remove(user)
            end
        }
        super
    end

    case Facter["operatingsystem"].value
    when "Darwin":
        def missing?(user)
            output = %x{nidump -r /users/#{user} / 2>/dev/null}.chomp

            if output == ""
                return true
            else
                return false
            end

            assert_equal("", output, "User %s is present:\n%s" % [user, output])
        end

        def current?(param, user)
            property = Puppet.type(:user).properties.find { |st|
                st.name == param
            }

            prov = Puppet::Type.type(:user).defaultprovider
            output = prov.report(param)
            # output = %x{nireport / /users name #{prov.netinfokey(param)}}
            output.each { |hash|
                if hash[:name] == user.name
                    val = hash[param]
                    if val =~ /^[-0-9]+$/
                        return Integer(val)
                    else
                        return val
                    end
                end
            }

            return nil
        end

        def remove(user)
            system("niutil -destroy / /users/%s" % user)
        end
    else
        def missing?(user)
            begin
                obj = Etc.getpwnam(user)
                return false
            rescue ArgumentError
                return true
            end
        end

        def current?(param, user)
            property = Puppet.type(:user).properties.find { |st|
                st.name == param
            }

            assert_nothing_raised {
                obj = Etc.getpwnam(user.name)
                return obj.send(user.posixmethod(param))
            }

            return nil
        end

        def remove(user)
            system("userdel %s" % user)
        end
    end


    def eachproperty
        Puppet::Type.type(:user).validproperties.each do |property|
            yield property
        end
    end

    def findshell(old = nil)
        %w{/bin/sh /bin/bash /sbin/sh /bin/ksh /bin/zsh /bin/csh /bin/tcsh
            /usr/bin/sh /usr/bin/bash /usr/bin/ksh /usr/bin/zsh /usr/bin/csh
            /usr/bin/tcsh}.find { |shell|
                if old
                    FileTest.exists?(shell) and shell != old
                else
                    FileTest.exists?(shell)
                end
        }
    end

    def fakedata(name, param)
        case param
        when :name: name
        when :ensure: :present
        when :comment: "Puppet's Testing User %s" % name # use a single quote a la #375
        when :gid: nonrootgroup.gid
        when :shell: findshell()
        when :home: "/home/%s" % name
        else
            return nil
        end
    end

    def fakemodel(*args)
        model = super

        # Set boolean methods as necessary.
        class << model
            def allowdupe?
                self[:allowdupe]
            end
            def managehome?
                self[:managehome]
            end
        end
        model
    end

    def mkuser(name)
        fakemodel = fakemodel(:user, name)
        user = nil
        assert_nothing_raised {
            user = @provider.new(fakemodel)
        }
        assert(user, "Could not create provider user")

        return user
    end

    def test_list
        names = nil
        assert_nothing_raised {
            names = @provider.listbyname
        }

        assert(names.length > 0, "Listed no users")

        # Now try it by object
        assert_nothing_raised {
            names = @provider.list
        }
        assert(names.length > 0, "Listed no users as objects")

        names.each do |obj|
            assert_instance_of(Puppet::Type.type(:user), obj)
            assert(obj[:provider], "Provider was not set")
        end
    end

    def test_infocollection
        fakemodel = fakemodel(:user, @me)

        user = nil
        assert_nothing_raised {
            user = @provider.new(fakemodel)
        }
        assert(user, "Could not create user provider")

        Puppet::Type.type(:user).validproperties.each do |property|
            next if property == :ensure
            # This is mostly in place for the 'password' stuff.
            next unless user.class.supports_parameter?(property)
            val = nil
            assert_nothing_raised {
                val = user.send(property)
            }

            assert(val != :absent,
                   "Property %s is missing" % property)

            assert(val, "Did not get value for %s" % property)
        end
    end

    def test_exists
        user = mkuser("nosuchuserok")

        assert(! user.exists?,
               "Fake user exists?")

        user = mkuser(@me)
        assert(user.exists?,
               "I don't exist?")
    end

    def attrtest_ensure(user)
        old = user.ensure
        assert_nothing_raised {
            user.ensure = :absent
        }

        assert(missing?(user.name), "User is still present")
        assert_nothing_raised {
            user.ensure = :present
        }
        assert(!missing?(user.name), "User is absent")
        assert_nothing_raised {
            user.ensure = :absent
        }

        unless old == :absent
            user.ensure = old
        end
    end

    def attrtest_comment(user)
        old = user.comment

        newname = "Billy O'Neal" # use a single quote, a la #372
        assert_nothing_raised {
            user.comment = newname
        }

        assert_equal(newname, current?(:comment, user),
            "Comment was not changed")

        assert_nothing_raised {
            user.comment = old
        }

        assert_equal(old, current?(:comment, user),
            "Comment was not reverted")
    end

    def attrtest_home(user)
        old = current?(:home, user)

        assert_nothing_raised {
            user.home = "/tmp"
        }

        assert_equal("/tmp", current?(:home, user), "Home was not changed")
        assert_nothing_raised {
            user.home = old
        }

        assert_equal(old, current?(:home, user), "Home was not reverted")
    end

    def attrtest_shell(user)
        old = current?(:shell, user)

        newshell = findshell(old)

        unless newshell
            $stderr.puts "Cannot find alternate shell; skipping shell test"
            return
        end

        assert_nothing_raised {
            user.shell = newshell
        }

        assert_equal(newshell, current?(:shell, user),
            "Shell was not changed")

        assert_nothing_raised {
            user.shell = old
        }

        assert_equal(old, current?(:shell, user), "Shell was not reverted")
    end

    def attrtest_gid(user)
        old = current?(:gid, user)

        newgroup = %w{nogroup nobody staff users daemon}.find { |gid|
                begin
                    group = Etc.getgrnam(gid)
                rescue ArgumentError => detail
                    next
                end
                old != group.gid
        }
        group = Etc.getgrnam(newgroup)

        unless newgroup
            $stderr.puts "Cannot find alternate group; skipping gid test"
            return
        end

        # Stupid netinfo
        if Facter.value(:operatingsystem) == "Darwin"
            assert_raise(ArgumentError, "gid allowed a non-integer value") do
                user.gid = group.name
            end
        end

        assert_nothing_raised("Failed to specify group by id") {
            user.gid = group.gid
        }

        assert_equal(group.gid, current?(:gid,user), "GID was not changed")

        assert_nothing_raised("Failed to change back to old gid") {
            user.gid = old
        }
    end

    def attrtest_uid(user)
        old = current?(:uid, user)

        newuid = old
        while true
            newuid += 1

            if newuid - old > 1000
                $stderr.puts "Could not find extra test UID"
                return
            end
            begin
                newuser = Etc.getpwuid(newuid)
            rescue ArgumentError => detail
                break
            end
        end

        assert_nothing_raised("Failed to change user id") {
            user.uid = newuid
        }

        assert_equal(newuid, current?(:uid, user), "UID was not changed")

        assert_nothing_raised("Failed to change user id") {
            user.uid = old
        }
        assert_equal(old, current?(:uid, user), "UID was not changed back")
    end

    def attrtest_groups(user)
        Etc.setgrent
        max = 0
        while group = Etc.getgrent
            if group.gid > max and group.gid < 5000
                max = group.gid
            end
        end

        groups = []
        main = []
        extra = []
        5.times do |i|
            i += 1
            name = "pptstgr%s" % i
            tmpgroup = Puppet.type(:group).create(
                :name => name,
                :gid => max + i
            )

            groups << tmpgroup

            cleanup do
                tmpgroup.provider.delete if tmpgroup.provider.exists?
            end

            if i < 3
                main << name
            else
                extra << name
            end
        end

        # Create our test groups
        assert_apply(*groups)

        # Now add some of them to our user
        assert_nothing_raised {
            user.model[:groups] = extra.join(",")
        }

        # Some tests to verify that groups work correctly startig from nothing
        # Remove our user
        user.ensure = :absent

        # And add it again
        user.ensure = :present

        # Make sure that the group list is added at creation time.
        # This is necessary because we don't have default fakedata for groups.
        assert(user.groups, "Did not retrieve group list")

        list = user.groups.split(",")
        assert_equal(extra.sort, list.sort, "Group list was not set at creation time")

        # Now set to our main list of groups
        assert_nothing_raised {
            user.groups = main.join(",")
        }

        list = user.groups.split(",")
        assert_equal(main.sort, list.sort, "Group list is not equal")
    end

    if Puppet::Util::SUIDManager.uid == 0
        def test_simpleuser
            name = "pptest"

            assert(missing?(name), "User %s is present" % name)

            user = mkuser(name)

            eachproperty do |property|
                if val = fakedata(user.name, property)
                    user.model[property] = val
                end
            end

            @@tmpusers << name

            assert_nothing_raised {
                user.create
            }

            assert_equal("Puppet's Testing User pptest",
                 user.comment,
                "Comment was not set")

            assert_nothing_raised {
                user.delete
            }

            assert(missing?(user.name), "User was not deleted")
        end

        def test_alluserproperties
            user = nil
            name = "pptest"

            assert(missing?(name), "User %s is present" % name)

            user = mkuser(name)

            eachproperty do |property|
                if val = fakedata(user.name, property)
                    user.model[property] = val
                end
            end

            @@tmpusers << name

            assert_nothing_raised {
                user.create
            }
            assert_equal("Puppet's Testing User pptest", user.comment,
                "Comment was not set")

            tests = Puppet::Type.type(:user).validproperties

            just = nil
            tests.each { |test|
                if self.respond_to?("attrtest_%s" % test)
                    self.send("attrtest_%s" % test, user)
                else
                    Puppet.err "Not testing attr %s of user" % test
                end
            }

            assert_nothing_raised {
                user.delete
            }
        end

        # This is a weird method that shows how annoying the interface between
        # types and providers is.  Grr.
        def test_duplicateIDs
            user1 = mkuser("user1")
            user1.create
            user1.uid = 125
            user2 = mkuser("user2")
            user2.model[:uid] = 125

            cleanup do
                user1.ensure = :absent
                user2.ensure = :absent
            end

            # Not all OSes fail here, so we can't test that it doesn't work with
            # it off, only that it does work with it on.
            assert_nothing_raised {
                user2.model[:allowdupe] = :true
            }
            assert_nothing_raised { user2.create }
            assert_equal(:present, user2.ensure,
                         "User did not get created")
        end
    else
        $stderr.puts "Not root; skipping user creation/modification tests"
    end

    # Here is where we test individual providers
    def test_useradd_flags
        useradd = nil
        assert_nothing_raised {
            useradd = Puppet::Type.type(:user).provider(:useradd)
        }
        assert(useradd, "Did not retrieve useradd provider")

        user = nil
        assert_nothing_raised {
            fakemodel = fakemodel(:user, @me)
            user = useradd.new(fakemodel)
        }

        assert_equal("-d", user.send(:flag, :home),
                    "Incorrect home flag")

        assert_equal("-s", user.send(:flag, :shell),
                    "Incorrect shell flag")
    end
    
    def test_autogen
        provider = nil
        user = Puppet::Type.type(:user).create(:name => nonrootuser.name)
        provider = user.provider
        assert(provider, "did not get provider")
        
        # Everyone should be able to autogenerate a uid
        assert_instance_of(Fixnum, provider.autogen(:uid))
        
        # If we're Darwin, then we should get results, but everyone else should
        # get nil
        darwin = (Facter.value(:operatingsystem) == "Darwin")

        should = {
            :comment => user[:name].capitalize,
            :home => "/var/empty",
            :shell => "/usr/bin/false"
        }
        
        should.each do |param, value|
            if darwin
                assert_equal(value, provider.autogen(param), "did not autogen %s for darwin correctly" % param)
            else
                assert_nil(provider.autogen(param), "autogenned %s for non-darwin os" % param)
            end
        end
    end
end

# $Id: user.rb 2444 2007-05-01 03:14:09Z luke $


syntax highlighted by Code2HTML, v. 0.9.1