#! /usr/bin/env ruby

require 'spec_helper'
require 'facter/util/fact'

describe Facter::Util::Fact do
  it "should require a name" do
    lambda { Facter::Util::Fact.new }.should raise_error(ArgumentError)
  end

  it "should always downcase the name and convert it to a symbol" do
    Facter::Util::Fact.new("YayNess").name.should == :yayness
  end

  it "should default to its name converted to a string as its ldapname" do
    Facter::Util::Fact.new("YayNess").ldapname.should == "yayness"
  end

  it "should allow specifying the ldap name at initialization" do
    Facter::Util::Fact.new("YayNess", :ldapname => "fooness").ldapname.should == "fooness"
  end

  it "should fail if an unknown option is provided" do
    lambda { Facter::Util::Fact.new('yay', :foo => :bar) }.should raise_error(ArgumentError)
  end

  it "should have a method for adding resolution mechanisms" do
    Facter::Util::Fact.new("yay").should respond_to(:add)
  end

  describe "when adding resolution mechanisms" do
    before do
      @fact = Facter::Util::Fact.new("yay")

      @resolution = Facter::Util::Resolution.new("yay")
    end

    it "should be to create a new resolution instance with a block" do
      Facter::Util::Resolution.expects(:new).returns @resolution

      @fact.add { }
    end
    it "should instance_eval the passed block on the new resolution" do
      @fact.add {
        setcode { "foo" }
      }
      @fact.value.should == "foo"
    end

    it "should re-sort the resolutions by weight, so the most restricted resolutions are first" do
      @fact.add { self.value = "1"; self.weight = 1 }
      @fact.add { self.value = "2"; self.weight = 2 }
      @fact.add { self.value = "0"; self.weight = 0 }
      @fact.value.should == "2"
    end
  end

  it "should be able to return a value" do
    Facter::Util::Fact.new("yay").should respond_to(:value)
  end

  describe "when returning a value" do
    before do
      @fact = Facter::Util::Fact.new("yay")
    end

    it "should return nil if there are no resolutions" do
      Facter::Util::Fact.new("yay").value.should be_nil
    end

    it "should return the first value returned by a resolution" do
      r1 = stub 'r1', :weight => 2, :value => nil, :suitable? => true
      r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true
      r3 = stub 'r3', :weight => 0, :value => "foo", :suitable? => true
      Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3)
      @fact.add { }
      @fact.add { }
      @fact.add { }

      @fact.value.should == "yay"
    end

    it "should short-cut returning the value once one is found" do
      r1 = stub 'r1', :weight => 2, :value => "foo", :suitable? => true
      r2 = stub 'r2', :weight => 1, :suitable? => true # would fail if 'value' were asked for
      Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2)
      @fact.add { }
      @fact.add { }

      @fact.value
    end

    it "should skip unsuitable resolutions" do
      r1 = stub 'r1', :weight => 2, :suitable? => false # would fail if 'value' were asked for'
      r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true
      Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2)
      @fact.add { }
      @fact.add { }

      @fact.value.should == "yay"
    end

    it "should return nil if the value is the empty string" do
      r1 = stub 'r1', :suitable? => true, :value => ""
      Facter::Util::Resolution.expects(:new).returns r1
      @fact.add { }

      @fact.value.should be_nil
    end
  end

  describe '#flush' do
    subject do
      Facter::Util::Fact.new(:foo)
    end
    context 'basic facts using setcode' do
      it "flushes the cached value when invoked" do
        system = mock('some_system_call')
        system.expects(:data).twice.returns(100,200)

        subject.add { setcode { system.data } }
        5.times { subject.value.should == 100 }
        subject.flush
        subject.value.should == 200
      end
    end
    context 'facts using setcode and on_flush' do
      it 'invokes the block passed to on_flush' do
        model = { :data => "Hello World" }
        subject.add do
          on_flush { model[:data] = "FLUSHED!" }
          setcode { model[:data] }
        end
        subject.value.should == "Hello World"
        subject.flush
        subject.value.should == "FLUSHED!"
      end
    end
  end
end
