require 'contextio/api'

describe ContextIO::API do
  describe ".version" do
    subject { ContextIO::API }

    it "uses API version 2.0" do
      expect(subject.version).to eq('2.0')
    end
  end

  describe "#version" do
    context "without version change" do
      subject { ContextIO::API.new(nil, nil) }

      it "uses API version 2.0" do
        expect(subject.version).to eq('2.0')
      end
    end

    context "with version change" do
      subject { ContextIO::API.new(nil, nil) }

      it "changes the API version used" do
        subject.version = '1.1'
        expect(subject.version).to eq('1.1')
      end
    end
  end

  describe ".base_url" do
    subject { ContextIO::API }

    it "is https://api.context.io" do
      expect(subject.base_url).to eq('https://api.context.io')
    end
  end

  describe "#base_url" do
    context "without base_url change" do
      subject { ContextIO::API.new(nil, nil) }

      it "is https://api.context.io" do
        expect(subject.base_url).to eq('https://api.context.io')
      end
    end

    context "with base_url change" do
      subject { ContextIO::API.new(nil, nil) }

      it "changes the base_url" do
        subject.base_url = 'https://api.example.com'
        expect(subject.base_url).to eq('https://api.example.com')
      end
    end
  end

  describe ".new" do
    subject { ContextIO::API.new('test_key', 'test_secret', {a:'b'}) }

    it "takes a key" do
      expect(subject.key).to eq('test_key')
    end

    it "takes a secret" do
      expect(subject.secret).to eq('test_secret')
    end

    it "takes an option hash" do
      expect(subject.opts).to eq(a:'b')
    end
  end

  describe "#path" do
    context "without params and default version" do
      subject { ContextIO::API.new(nil, nil).path('test_command') }

      it "puts the command in the path" do
        expect(subject).to eq('/2.0/test_command')
      end
    end

    context "without params and version change" do
      subject { ContextIO::API.new(nil, nil) }

      it "puts the command in the path" do
        subject.version = '2.5'
        expect(subject.path('test_command')).to eq('/2.5/test_command')
      end
    end

    context "with params" do
      subject { ContextIO::API.new(nil, nil).path('test_command', foo: 1, bar: %w(a b c)) }

      it "URL encodes the params" do
        expect(subject).to eq('/2.0/test_command?foo=1&bar=a%2Cb%2Cc')
      end
    end

    context "with a full URL" do
      subject { ContextIO::API.new(nil, nil).path('https://api.context.io/2.0/test_command') }

      it "strips out the command" do
        expect(subject).to eq('/2.0/test_command')
      end
    end

    context "with a full URL and version and base_url change" do
      subject { ContextIO::API.new(nil, nil) }

      it "strips out the command" do
        subject.version = '2.5'
        subject.base_url = 'https://api.example.com'
        expect(subject.path('https://api.example.com/2.5/test_command')).to eq('/2.5/test_command')
      end
    end
  end

  describe "#request" do
    subject { ContextIO::API.new(nil, nil).request(:get, 'test') }

    context "with a timeout response" do
      before do
        WebMock.stub_request(
          :get,
          'https://api.context.io/2.0/test'
        ).to_timeout.then.to_return(
          status: 200,
          body: JSON.dump('yep' => 'noap'),
        )
      end

      it "parses the JSON response" do
        expect { ContextIO::API.new(nil, nil).request(:get, 'test') }.to raise_error(Faraday::Error::TimeoutError)
      end
    end

    context "with a good response" do
      before do
        WebMock.stub_request(
          :get,
          'https://api.context.io/2.0/test'
        ).to_return(
          status: 200,
          body: JSON.dump('a' => 'b', 'c' => 'd')
        )
      end

      it "parses the JSON response" do
        expect(subject).to eq('a' => 'b', 'c' => 'd')
      end
    end

    context "with a bad response that has a body" do
      before do
        WebMock.stub_request(
          :get,
          'https://api.context.io/2.0/test'
        ).to_return(
          status: 400,
          body: JSON.dump('type' => 'error', 'value' => 'nope')
        )
      end

      it "raises an API error with the body message" do
        expect { subject }.to raise_error(ContextIO::API::Error, 'nope')
      end
    end

    context "with a bad response that has a different body" do
      before do
        WebMock.stub_request(
          :get,
          'https://api.context.io/2.0/test'
        ).to_return(
          status: 400,
          body: JSON.dump('success' => false, 'feedback_code' => 'nope')
        )
      end

      it "raises an API error with the body message" do
        expect { subject }.to raise_error(ContextIO::API::Error, 'nope')
      end
    end

    context "with a bad response that has no body" do
      before do
        WebMock.stub_request(
          :get,
          'https://api.context.io/2.0/test'
        ).to_return(
          status: 400
        )
      end

      it "raises an API error with the header message" do
        expect { subject }.to raise_error(ContextIO::API::Error, 'HTTP 400 Error')
      end
    end
  end

  describe ".url_for" do
    it "delegates to ContextIO::API::URLBuilder" do
      expect(ContextIO::API::URLBuilder).to receive(:url_for).with('foo')

      ContextIO::API.url_for('foo')
    end
  end

  describe "#url_for" do
    subject { ContextIO::API.new('test_key', 'test_secret') }

    it "delegates to the class" do
      expect(ContextIO::API).to receive(:url_for).with('foo')

      subject.url_for('foo')
    end
  end
end