require 'spec_helper'

describe ZendeskAPI::DataResource do
  context "ZendeskAPI.get_class" do
    it "should create a new class if there is none" do
      ZendeskAPI.const_defined?("Blergh").should be_false
      ZendeskAPI.get_class(:blergh).should == ZendeskAPI::Blergh
    end

    it "should find the class if it exists" do
      ZendeskAPI.get_class(:tickets).should == ZendeskAPI::Tickets
    end

    it "should handle 'nil' be passed in" do
      ZendeskAPI.get_class(nil).should be_false
    end
  end

  specify "singular resource name" do
    ZendeskAPI::Ticket.singular_resource_name.should == "ticket"
    ZendeskAPI::TicketField.singular_resource_name.should == "ticket_field"
  end

  specify "resource name" do
    ZendeskAPI::Ticket.resource_name.should == "tickets"
    ZendeskAPI::TicketField.resource_name.should == "ticket_fields"
    ZendeskAPI::Category.resource_name.should == "categories"
  end

  context "user" do
    context "with first order attributes" do
      subject { ZendeskAPI::TestResource.new(client) }
      before(:each) { subject.attributes[:priority] = "normal" }

      it "should be able to access underlying attributes" do
        subject.priority.should == "normal"
      end

      it "should be able to change underlying attributes" do
        expect { subject.priority = "urgent" }.to_not raise_error
      end

      it "should be able to iterate over underlying attributes" do
        expect do
          subject.map do |k, v|
            [k.to_sym, v]
          end
        end.to_not raise_error
      end
    end

    context "with second order attributes" do
      subject { ZendeskAPI::TestResource.new(client) }
      before(:each) { subject.priority = "normal" }

      it "should be able to change underlying attributes" do
        subject.priority.should == "normal"
      end

      it "should be able to change underlying attributes" do
        expect { subject.priority = "urgent" }.to_not raise_error
      end

      it "should be able to iterate over underlying attributes" do
        expect do
          subject.map do |k, v|
            [k.to_sym, v]
          end
        end.to_not raise_error
      end
    end
  end

  context "has" do
    before(:each) { ZendeskAPI::TestResource.has :foo }

    context "class methods" do
      subject { ZendeskAPI::TestResource }
      it "should define a method with the same name" do
        subject.instance_methods.map(&:to_s).should include("foo")
      end

      it "should create a class if none exists" do
        ZendeskAPI.const_defined?("Foo").should be_true
      end

      context "with explicit class name" do
        before(:all) { ZendeskAPI::TestResource.has :baz, :class => :foo }

        it "should not create a baz class" do
          ZendeskAPI.const_defined?("Baz").should be_false
        end
      end
    end

    context "instance method" do
      context "with no side-loading" do
        subject { ZendeskAPI::TestResource.new(client, :id => 1) }
        before(:each) { stub_json_request(:get, %r{test_resources/\d+/foo}, json(:foo => {})) }

        it "should attempt to grab the resource from the host" do
          subject.foo.should be_instance_of(ZendeskAPI::Foo)
        end

        it "should pass the path on to the resource" do
          subject.foo.path.should == "foos"
        end

        context "with a client error" do
          before(:each) { stub_request(:get, %r{test_resources/\d+/foo}).to_return(:status => 500) }

          it "should handle it properly" do
            expect { silence_logger{ subject.foo.should be_nil } }.to_not raise_error
          end
        end
        
        context "with an explicit path set" do
          before(:each) do
            ZendeskAPI::TestResource.has :foo, :path => "blergh"
            stub_json_request(:get, %r{test_resources/\d+/blergh}, json(:foo => {}))
          end

          it "should call the right path" do
            subject.foo.should be_instance_of(ZendeskAPI::Foo)
          end
        end
      end

      context "with side-loading of resource" do
        let(:foo) { { :message => "FOO_OBJ" } }
        subject { ZendeskAPI::TestResource.new(client, :foo => foo) }

        it "should load foo from the hash" do
          subject.foo.should be_instance_of(ZendeskAPI::Foo)
        end
      end

      context "with side-loading of id" do
        subject { ZendeskAPI::TestResource.new(client, :foo_id => 1) }
        before(:each) do
          stub_json_request(:get, %r{foos/1}, json("foo" => {}))
        end

        it "should find foo_id and load it from the api" do
          subject.foo
        end

        it "should handle nil response from find api" do
          ZendeskAPI::Foo.should_receive(:find).twice.and_return(nil)
          subject.foo.should be_nil
          subject.foo
        end
      end
    end
  end

  context "has_many" do
    before(:each) { ZendeskAPI::TestResource.has_many :bars }

    context "class methods" do
      subject { ZendeskAPI::TestResource }
      it "should define a method with the same name" do
        subject.instance_methods.map(&:to_s).should include("bars")
      end

      it "should create a class if none exists" do
        ZendeskAPI.const_defined?("Bar").should be_true
      end

      context "with explicit class name" do
        before(:each) { ZendeskAPI::TestResource.has_many :cats, :class => :foo }

        it "should not create a baz class" do
          ZendeskAPI.const_defined?("Cat").should be_false
        end
      end
    end

    context "instance method" do
      context "with no side-loading" do
        subject { ZendeskAPI::TestResource.new(client, :id => 1) }

        it "should not attempt to grab the resource from the host" do
          subject.bars.should be_instance_of(ZendeskAPI::Collection)
        end

        it "should pass the path on to the resource" do
          subject.bars.path.should == "test_resources/1/bars"
        end

        context "with an explicit path set" do
          before(:each) do
            ZendeskAPI::TestResource.has_many :bars, :path => "blargh"
          end

          it "should call the right path" do
            subject.bars.path.should == "test_resources/1/blargh"
          end
        end
      end

      context "with side-loading of resource" do
        let(:bars) { [{ :message => "FOO_OBJ" }] }
        subject { ZendeskAPI::TestResource.new(client, :bars => bars) }

        it "should map bars onto Bar class" do
          subject.bars.first.should be_instance_of(ZendeskAPI::Bar)
        end
      end

      context "with side-loading of id" do
        let(:bars) { [1, 2, 3] }
        subject { ZendeskAPI::TestResource.new(client, :bar_ids => bars) }

        it "should find foo_id and load it from the api" do
          ZendeskAPI::Bar.should_receive(:find).with(client, kind_of(Hash)).exactly(bars.length).times
          subject.bars
        end

        it "should handle nil response from find api" do
          ZendeskAPI::Bar.should_receive(:find).with(client, kind_of(Hash)).exactly(bars.length).times.and_return(nil)
          subject.bars.should be_empty
          subject.bars # Test expectations
        end
      end
    end
  end
end