#!/usr/bin/env ruby

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

require 'facter/util/collection'

describe Facter::Util::Collection do
    it "should have a method for adding facts" do
        Facter::Util::Collection.new.should respond_to(:add)
    end

    it "should have a method for returning a loader" do
        Facter::Util::Collection.new.should respond_to(:loader)
    end

    it "should use an instance of the Loader class as its loader" do
        Facter::Util::Collection.new.loader.should be_instance_of(Facter::Util::Loader)
    end

    it "should cache its loader" do
        coll = Facter::Util::Collection.new
        coll.loader.should equal(coll.loader)
    end

    it "should have a method for loading all facts" do
        Facter::Util::Collection.new.should respond_to(:load_all)
    end

    it "should delegate its load_all method to its loader" do
        coll = Facter::Util::Collection.new
        coll.loader.expects(:load_all)
        coll.load_all
    end

    describe "when adding facts" do
        before do
            @coll = Facter::Util::Collection.new
        end

        it "should create a new fact if no fact with the same name already exists" do
            fact = mock 'fact'
            Facter::Util::Fact.expects(:new).with { |name, *args| name == :myname }.returns fact

            @coll.add(:myname)
        end

        it "should accept options" do
            @coll.add(:myname, :ldapname => "whatever") { }
        end

        it "should set any appropriate options on the fact instances" do
            # Use a real fact instance, because we're using respond_to?
            fact = Facter::Util::Fact.new(:myname)
            fact.expects(:ldapname=).with("testing")
            Facter::Util::Fact.expects(:new).with(:myname).returns fact

            @coll.add(:myname, :ldapname => "testing")
        end

        it "should set appropriate options on the resolution instance" do
            fact = Facter::Util::Fact.new(:myname)
            Facter::Util::Fact.expects(:new).with(:myname).returns fact

            resolve = Facter::Util::Resolution.new(:myname) {}
            fact.expects(:add).returns resolve

            @coll.add(:myname, :timeout => "myval") {}
        end

        it "should not pass fact-specific options to resolutions" do
            fact = Facter::Util::Fact.new(:myname)
            Facter::Util::Fact.expects(:new).with(:myname).returns fact

            resolve = Facter::Util::Resolution.new(:myname) {}
            fact.expects(:add).returns resolve

            fact.expects(:ldapname=).with("foo")
            resolve.expects(:timeout=).with("myval")

            @coll.add(:myname, :timeout => "myval", :ldapname => "foo") {}
        end

        it "should fail if invalid options are provided" do
            lambda { @coll.add(:myname, :foo => :bar) }.should raise_error(ArgumentError)
        end

        describe "and a block is provided" do
            it "should use the block to add a resolution to the fact" do
                fact = mock 'fact'
                Facter::Util::Fact.expects(:new).returns fact

                fact.expects(:add)

                @coll.add(:myname) {}
            end
        end
    end

    it "should have a method for retrieving facts by name" do
        Facter::Util::Collection.new.should respond_to(:fact)
    end

    describe "when retrieving facts" do
        before do
            @coll = Facter::Util::Collection.new

            @fact = @coll.add("YayNess")
        end

        it "should return the fact instance specified by the name" do
            @coll.fact("YayNess").should equal(@fact)
        end

        it "should be case-insensitive" do
            @coll.fact("yayness").should equal(@fact)
        end

        it "should treat strings and symbols equivalently" do
            @coll.fact(:yayness).should equal(@fact)
        end

        it "should use its loader to try to load the fact if no fact can be found" do
            @coll.loader.expects(:load).with(:testing)
            @coll.fact("testing")
        end

        it "should return nil if it cannot find or load the fact" do
            @coll.loader.expects(:load).with(:testing)
            @coll.fact("testing").should be_nil
        end
    end

    it "should have a method for returning a fact's value" do
        Facter::Util::Collection.new.should respond_to(:value)
    end

    describe "when returning a fact's value" do
        before do
            @coll = Facter::Util::Collection.new
            @fact = @coll.add("YayNess")

            @fact.stubs(:value).returns "result"
        end

        it "should use the 'fact' method to retrieve the fact" do
            @coll.expects(:fact).with(:yayness).returns @fact
            @coll.value(:yayness)
        end

        it "should return the result of calling :value on the fact" do
            @fact.expects(:value).returns "result"

            @coll.value("YayNess").should == "result"
        end

        it "should be case-insensitive" do
            @coll.value("yayness").should_not be_nil
        end

        it "should treat strings and symbols equivalently" do
            @coll.value(:yayness).should_not be_nil
        end
    end

    it "should return the fact's value when the array index method is used" do
        @coll = Facter::Util::Collection.new
        @coll.expects(:value).with("myfact").returns "foo"
        @coll["myfact"].should == "foo"
    end

    it "should have a method for flushing all facts" do
        @coll = Facter::Util::Collection.new
        @fact = @coll.add("YayNess")

        @fact.expects(:flush)

        @coll.flush
    end

    it "should have a method that returns all fact names" do
        @coll = Facter::Util::Collection.new
        @coll.add(:one)
        @coll.add(:two)

        @coll.list.sort { |a,b| a.to_s <=> b.to_s }.should == [:one, :two]
    end

    it "should have a method for returning a hash of fact values" do
        Facter::Util::Collection.new.should respond_to(:to_hash)
    end

    describe "when returning a hash of values" do
        before do
            @coll = Facter::Util::Collection.new
            @fact = @coll.add(:one)
            @fact.stubs(:value).returns "me"
        end

        it "should return a hash of fact names and values with the fact names as strings" do
            @coll.to_hash.should == {"one" => "me"}
        end

        it "should not include facts that did not return a value" do
            f = @coll.add(:two)
            f.stubs(:value).returns nil
            @coll.to_hash.should_not be_include(:two)
        end
    end

    it "should have a method for iterating over all facts" do
        Facter::Util::Collection.new.should respond_to(:each)
    end

    it "should include Enumerable" do
        Facter::Util::Collection.ancestors.should be_include(Enumerable)
    end

    describe "when iterating over facts" do
        before do
            @coll = Facter::Util::Collection.new
            @one = @coll.add(:one)
            @two = @coll.add(:two)
        end

        it "should yield each fact name and the fact value" do
            @one.stubs(:value).returns "ONE"
            @two.stubs(:value).returns "TWO"
            facts = {}
            @coll.each do |fact, value|
                facts[fact] = value
            end
            facts.should == {"one" => "ONE", "two" => "TWO"}
        end

        it "should convert the fact name to a string" do
            @one.stubs(:value).returns "ONE"
            @two.stubs(:value).returns "TWO"
            facts = {}
            @coll.each do |fact, value|
                fact.should be_instance_of(String)
            end
        end

        it "should only yield facts that have values" do
            @one.stubs(:value).returns "ONE"
            @two.stubs(:value).returns nil
            facts = {}
            @coll.each do |fact, value|
                facts[fact] = value
            end

            facts.should_not be_include("two")
        end
    end
end