require "spec_helper"

describe Whois::Record do

  before(:each) do
    @server   = Whois::Server.factory(:tld, ".foo", "whois.example.test")
    @parts    = [
        Whois::Record::Part.new(:body => "This is a record from foo.", :host => "foo.example.test"),
        Whois::Record::Part.new(:body => "This is a record from bar.", :host => "bar.example.test")
    ]
    @content  = @parts.map(&:body).join("\n")
  end


  describe "#initialize" do
    it "requires a server and parts" do
      lambda { klass.new }.should raise_error(ArgumentError)
      lambda { klass.new(@server) }.should raise_error(ArgumentError)
      lambda { klass.new(@server, @parts) }.should_not raise_error
    end
    
    it "sets server and parts from arguments" do
      instance = klass.new(@server, @parts)
      instance.server.should be(@server)
      instance.parts.should be(@parts)

      instance = klass.new(nil, nil)
      instance.server.should be_nil
      instance.parts.should be_nil
    end
  end


  describe "#to_s" do
    it "delegates to #content" do
      klass.new(nil, [@parts[0]]).to_s.should == @parts[0].body
      klass.new(nil, @parts).to_s.should == @parts.map(&:body).join("\n")
      klass.new(nil, []).to_s.should == ""
    end
  end

  describe "#inspect" do
    it "inspects the record content" do
      klass.new(nil, [@parts[0]]).inspect.should == @parts[0].body.inspect
    end

    it "joins multiple parts" do
      klass.new(nil, @parts).inspect.should == @parts.map(&:body).join("\n").inspect
    end

    it "joins empty parts" do
      klass.new(nil, []).inspect.should == "".inspect
    end
  end

  describe "#==" do
    it "returns true when other is the same instance" do
      one = two = klass.new(@server, @parts)

      (one == two).should be_true
      (one.eql? two).should be_true
    end

    it "returns true when other has same class and has the same parts" do
      one, two = klass.new(@server, @parts), klass.new(@server, @parts)

      (one == two).should be_true
      (one.eql? two).should be_true
    end

    it "returns true when other has descendant class and has the same parts" do
      subklass = Class.new(klass)
      one, two = klass.new(@server, @parts), subklass.new(@server, @parts)

      (one == two).should be_true
      (one.eql? two).should be_true
    end

    it "returns true when other has same class and has equal parts" do
      one, two = klass.new(@server, @parts), klass.new(@server, @parts.dup)
      (one == two).should be_true
      (one.eql? two).should be_true
    end

    it "returns true when other has same class, different server and the same parts" do
      one, two = klass.new(@server, @parts), klass.new(nil, @parts)
      (one == two).should be_true
      (one.eql? two).should be_true
    end

    it "returns false when other has different class and has the same parts" do
      one, two = klass.new(@server, @parts), Struct.new(:server, :parts).new(@server, @parts)

      (one == two).should be_false
      (one.eql? two).should be_false
    end

    it "returns false when other has different parts" do
      one, two = klass.new(@server, @parts), klass.new(@server, [])

      (one == two).should be_false
      (one.eql? two).should be_false
    end

    it "returns false when other is string and has the same content" do
      one, two = klass.new(@server, @parts), klass.new(@server, @parts).to_s

      (one == two).should be_false
      (one.eql? two).should be_false
    end

    it "returns false when other is string and has different content" do
      one, two = klass.new(@server, @parts), "different"

      (one == two).should be_false
      (one.eql? two).should be_false
    end
  end


  describe "match" do
    it "delegates to content" do
      klass.new(@server, @parts).match(/record/).should be_a(MatchData)
      klass.new(@server, @parts).match(/record/)[0].should == "record"

      klass.new(@server, @parts).match(/nomatch/).should be_nil
    end
  end

  describe "match" do
    it "calls match and checks for match" do
      klass.new(@server, @parts).match?(/record/).should  == true
      klass.new(@server, @parts).match?(/nomatch/).should == false
    end
  end


  describe "#content" do
    it "returns the part body" do
      klass.new(nil, [@parts[0]]).content.should == @parts[0].body
    end

    it "joins multiple parts" do
      klass.new(nil, @parts).content.should == @parts.map(&:body).join("\n")
    end

    it "returns an empty string when no parts" do
      klass.new(nil, []).content.should == ""
    end
  end

  describe "#parser" do
    it "returns a Parser" do
      klass.new(nil, @parts).parser.should be_a(Whois::Record::Parser)
    end

    it "initializes the parser with self" do
      record = klass.new(nil, @parts)
      record.parser.record.should be(record)
    end

    it "memoizes the value" do
      record = klass.new(nil, @parts)
      record.instance_eval { @parser }.should be_nil
      parser = record.parser
      record.instance_eval { @parser }.should be(parser)
    end
  end


  describe "#properties" do
    it "returns a Hash" do
      klass.new(nil, []).properties.should be_a(Hash)
    end

    it "returns both nil and not-nil values" do
      r = klass.new(nil, [])
      r.expects(:domain).returns("")
      r.expects(:created_on).returns(nil)
      r.expects(:expires_on).returns(Time.parse("2010-10-10"))
      p = r.properties
      p[:domain].should == ""
      p[:created_on].should == nil
      p[:expires_on].should == Time.parse("2010-10-10")
    end

    it "fetches all parser property" do
      klass.new(nil, []).properties.keys.should =~ Whois::Record::Parser::PROPERTIES 
    end
  end


  class Whois::Record::Parser::WhoisPropertiesTest < Whois::Record::Parser::Base
    property_supported :status do
      nil
    end
    property_supported :created_on do
      Date.parse("2010-10-20")
    end
    property_not_supported :updated_on
    # property_not_defined :expires_on
  end

  describe "#property_supported?" do
    it "returns true if the property is supported" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.property_supported?(:status).should == true
      r.property_supported?(:created_on).should == true
    end

    it "returns false if the property is not supported" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.property_supported?(:updated_on).should == false
    end

    it "returns false if the property is not defined" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.property_supported?(:expires_on).should == false
    end
  end

  describe "property" do
    it "returns value when the property is supported" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.created_on.should == Date.parse("2010-10-20")
    end

    it "returns nil when the property is not supported" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.updated_on.should be_nil
    end

    it "returns nil when the property is not implemented" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.expires_on.should be_nil
    end
  end

  describe "property?" do
    it "returns true when the property is supported and has no value" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.status?.should == false
    end

    it "returns false when the property is supported and has q value" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.created_on?.should == true
    end

    it "returns false when the property is not supported" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.updated_on?.should == false
    end

    it "returns false when the property is not implemented" do
      r = klass.new(nil, [Whois::Record::Part.new(:body => "", :host => "whois.properties.test")])
      r.expires_on?.should == false
    end
  end


  describe "#changed?" do
    it "raises if the argument is not an instance of the same class" do
      lambda do
        klass.new(nil, []).changed?(Object.new)
      end.should raise_error

      lambda do
        klass.new(nil, []).changed?(klass.new(nil, []))
      end.should_not raise_error
    end
  end

  describe "#unchanged?" do
    it "raises if the argument is not an instance of the same class" do
      lambda do
        klass.new(nil, []).unchanged?(Object.new)
      end.should raise_error

      lambda do
        klass.new(nil, []).unchanged?(klass.new(nil, []))
      end.should_not raise_error
    end

    it "returns true if self and other references the same object" do
      instance = klass.new(nil, [])
      instance.unchanged?(instance).should be_true
    end

    it "delegates to #parser if self and other references different objects" do
      other = klass.new(nil, @parts)
      instance = klass.new(nil, @parts)
      instance.parser.expects(:unchanged?).with(other.parser)

      instance.unchanged?(other)
    end
  end

  describe "#contacts" do
    it "delegates to parser" do
      record = klass.new(nil, [])
      record.parser.expects(:contacts).returns([:one, :two])
      record.contacts.should == [:one, :two]
    end
  end


  describe "#response_incomplete?" do
    it "delegates to #parser" do
      i = klass.new(nil, [])
      i.parser.expects(:response_incomplete?)
      i.response_incomplete?
    end
  end

  describe "#response_throttled?" do
    it "delegates to #parser" do
      i = klass.new(nil, [])
      i.parser.expects(:response_throttled?)
      i.response_throttled?
    end
  end

  describe "#response_unavailable?" do
    it "delegates to #parser" do
      i = klass.new(nil, [])
      i.parser.expects(:response_unavailable?)
      i.response_unavailable?
    end
  end


  describe "method_missing" do
    context "when a parser property"
    context "when a parser method"

    context "when a parser question method/property" do
      it "calls the corresponding no-question method" do
        record = klass.new(nil, [])
        record.expects(:status)
        record.status?
      end

      it "returns true if the property is not nil" do
        record = klass.new(nil, [])
        record.expects(:status).returns("available")
        record.status?.should == true
      end

      it "returns false if the property is nil" do
        record = klass.new(nil, [])
        record.expects(:status).returns(nil)
        record.status?.should == false
      end
    end

    context "when a simple method" do
      it "passes the request to super" do
        Object.class_eval do
          def happy; "yes"; end
        end

        record = klass.new(nil, [])
        lambda do
          record.happy.should == "yes"
        end.should_not raise_error
        lambda do
          record.sad
        end.should raise_error(NoMethodError)
      end

      it "does not catch all methods" do
        lambda do
          klass.new(nil, []).i_am_not_defined
        end.should raise_error(NoMethodError)
      end

      it "does not catch all question methods" do
        lambda do
          klass.new(nil, []).i_am_not_defined?
        end.should raise_error(NoMethodError)
      end
    end
  end

end