#!/usr/bin/env ruby

require File.dirname(__FILE__) + '/../../spec_helper'

require 'puppet/file_serving/configuration'

describe Puppet::FileServing::Configuration do
    it "should make :new a private method" do
        proc { Puppet::FileServing::Configuration.new }.should raise_error
    end

    it "should return the same configuration each time :create is called" do
        Puppet::FileServing::Configuration.create.should equal(Puppet::FileServing::Configuration.create)
    end

    it "should have a method for removing the current configuration instance" do
        old = Puppet::FileServing::Configuration.create
        Puppet::FileServing::Configuration.clear_cache
        Puppet::FileServing::Configuration.create.should_not equal(old)
    end

    after do
        Puppet::FileServing::Configuration.clear_cache
    end
end

describe Puppet::FileServing::Configuration do

    before :each do
        @path = "/path/to/configuration/file.conf"
        Puppet.settings.stubs(:value).with(:fileserverconfig).returns(@path)
    end

    after :each do
        Puppet::FileServing::Configuration.clear_cache
    end

    describe Puppet::FileServing::Configuration, " when initializing" do

        it "should work without a configuration file" do
            FileTest.stubs(:exists?).with(@path).returns(false)
            proc { Puppet::FileServing::Configuration.create }.should_not raise_error
        end

        it "should parse the configuration file if present" do
            FileTest.stubs(:exists?).with(@path).returns(true)
            @parser = mock 'parser'
            @parser.expects(:parse).returns({})
            Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser)
            Puppet::FileServing::Configuration.create
        end

        it "should determine the path to the configuration file from the Puppet settings" do
            Puppet::FileServing::Configuration.create
        end
    end

    describe Puppet::FileServing::Configuration, " when parsing the configuration file" do

        before do
            FileTest.stubs(:exists?).with(@path).returns(true)
            @parser = mock 'parser'
            Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser)
        end

        it "should set the mount list to the results of parsing" do
            @parser.expects(:parse).returns("one" => mock("mount"))
            config = Puppet::FileServing::Configuration.create
            config.mounted?("one").should be_true
        end

        it "should not raise exceptions" do
            @parser.expects(:parse).raises(ArgumentError)
            proc { Puppet::FileServing::Configuration.create }.should_not raise_error
        end

        it "should replace the existing mount list with the results of reparsing" do
            @parser.expects(:parse).returns("one" => mock("mount"))
            config = Puppet::FileServing::Configuration.create
            config.mounted?("one").should be_true
            # Now parse again
            @parser.expects(:parse).returns("two" => mock('other'))
            config.send(:readconfig, false)
            config.mounted?("one").should be_false
            config.mounted?("two").should be_true
        end

        it "should not replace the mount list until the file is entirely parsed successfully" do
            @parser.expects(:parse).returns("one" => mock("mount"))
            @parser.expects(:parse).raises(ArgumentError)
            config = Puppet::FileServing::Configuration.create
            # Now parse again, so the exception gets thrown
            config.send(:readconfig, false)
            config.mounted?("one").should be_true
        end
    end

    describe Puppet::FileServing::Configuration, " when finding files" do

        before do
            @parser = mock 'parser'
            @parser.stubs(:changed?).returns true
            FileTest.stubs(:exists?).with(@path).returns(true)
            Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser)

            @mount1 = stub 'mount', :name => "one"
            @mounts = {"one" => @mount1}

            Facter.stubs(:value).with("hostname").returns("whatever")

            @config = Puppet::FileServing::Configuration.create
        end

        it "should fail if the uri does not match a leading slash followed by a valid mount name" do
            @parser.expects(:parse).returns(@mounts)
            proc { @config.file_path("something") }.should raise_error(ArgumentError)
        end

        it "should use the first term after the first slash for the mount name" do
            @parser.expects(:parse).returns(@mounts)
            FileTest.stubs(:exists?).returns(true)
            @mount1.expects(:file)
            @config.file_path("/one")
        end

        it "should use the remainder of the URI after the mount name as the file name" do
            @parser.expects(:parse).returns(@mounts)
            @mount1.expects(:file).with("something/else", {})
            FileTest.stubs(:exists?).returns(true)
            @config.file_path("/one/something/else")
        end

        it "should treat a bare name as a mount and no relative file" do
            @parser.expects(:parse).returns(@mounts)
            @mount1.expects(:file).with(nil, {})
            FileTest.stubs(:exists?).returns(true)
            @config.file_path("/one")
        end

        it "should treat a name with a trailing slash equivalently to a name with no trailing slash" do
            @parser.expects(:parse).returns(@mounts)
            @mount1.expects(:file).with(nil, {})
            FileTest.stubs(:exists?).returns(true)
            @config.file_path("/one/")
        end

        it "should return nil if the mount cannot be found" do
            @parser.expects(:changed?).returns(true)
            @parser.expects(:parse).returns({})
            @config.file_path("/one/something").should be_nil
        end

        it "should return nil if the mount does not contain the file" do
            @parser.expects(:parse).returns(@mounts)
            @mount1.expects(:file).with("something/else", {}).returns(nil)
            @config.file_path("/one/something/else").should be_nil
        end

        it "should return the fully qualified path if the mount exists" do
            @parser.expects(:parse).returns(@mounts)
            @mount1.expects(:file).with("something/else", {}).returns("/full/path")
            @config.file_path("/one/something/else").should == "/full/path"
        end

        it "should reparse the configuration file when it has changed" do
            @mount1.stubs(:file).returns("whatever")
            @parser.expects(:changed?).returns(true)
            @parser.expects(:parse).returns(@mounts)
            FileTest.stubs(:exists?).returns(true)
            @config.file_path("/one/something")

            @parser.expects(:changed?).returns(true)
            @parser.expects(:parse).returns({})
            @config.file_path("/one/something").should be_nil
        end
    end

    describe Puppet::FileServing::Configuration, " when checking authorization" do

        before do
            @parser = mock 'parser'
            @parser.stubs(:changed?).returns true
            FileTest.stubs(:exists?).with(@path).returns(true)
            Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser)

            @mount1 = stub 'mount', :name => "one"
            @mounts = {"one" => @mount1}
            @parser.stubs(:parse).returns(@mounts)

            Facter.stubs(:value).with("hostname").returns("whatever")

            @config = Puppet::FileServing::Configuration.create
        end

        it "should return false if the mount cannot be found" do
            @config.authorized?("/nope/my/file").should be_false
        end

        it "should use the mount to determine authorization" do
            @mount1.expects(:allowed?)
            @config.authorized?("/one/my/file")
        end

        it "should pass the client's name to the mount if provided" do
            @mount1.expects(:allowed?).with("myhost", nil)
            @config.authorized?("/one/my/file", :node => "myhost")
        end

        it "should pass the client's IP to the mount if provided" do
            @mount1.expects(:allowed?).with("myhost", "myip")
            @config.authorized?("/one/my/file", :node => "myhost", :ipaddress => "myip")
        end

        it "should return true if the mount allows the client" do
            @mount1.expects(:allowed?).returns(true)
            @config.authorized?("/one/my/file").should be_true
        end

        it "should return false if the mount denies the client"  do
            @mount1.expects(:allowed?).returns(false)
            @config.authorized?("/one/my/file").should be_false
        end
    end
end