# frozen_string_literal: true

require 'spec_helper'

describe LinkThumbnailer::Processor do

  let(:page)      { ::LinkThumbnailer::Page.new(url, {}) }
  let(:instance)  { described_class.new }
  let(:url)       { 'http://foo.com' }
  before do
    allow(LinkThumbnailer).to receive(:page).and_return(page)
  end

  describe '#call' do

    let(:action) { instance.call(url) }
    let(:https_action) { instance.call(https_url) }

    context 'when redirect_count is greater than config' do

      it { expect { instance.call(url, 10) } .to raise_error(LinkThumbnailer::RedirectLimit) }

    end

    context 'on no http error' do

      let(:body) { 'foo' }

      before do
        stub_request(:get, url).to_return(status: 200, body: body, headers: {})
      end

      it { expect(action.body).to eq(body) }

    end

    context 'on http error' do

      before do
        stub_request(:get, url).to_return(status: 500, body: '', headers: {})
      end

      it { expect { action }.to raise_error(LinkThumbnailer::HTTPError) }

    end

    context 'on http unauthorized error' do

      before do
        stub_request(:get, url).to_return(status: 401, body: '', headers: {})
      end

      it { expect { action }.to raise_error(LinkThumbnailer::HTTPError) }

    end

    context 'on http redirection' do

      let(:body) { 'foo' }

      context 'with relative uri' do

        let(:another_url) { '/bar' }

        before do
          stub_request(:get, url).to_return(status: 300, body: '', headers: { 'Location' => another_url })
          stub_request(:get, url + another_url).to_return(status: 200, body: body, headers: {})
        end

        it { expect(action.body).to eq(body) }

      end

      context 'with absolute uri' do

        let(:another_url) { 'http://bar.com' }

        before do
          stub_request(:get, url).to_return(status: 300, body: '', headers: { 'Location' => another_url })
          stub_request(:get, another_url).to_return(status: 200, body: body, headers: {})
        end

        it { expect(action.body).to eq(body) }

      end

    end

  end

  describe '#with_valid_url' do

    context 'when valid' do

      before do
        allow(instance).to receive(:valid_url_format?).and_return(true)
      end

      it 'yields' do
        expect(instance).to receive(:with_valid_url).and_yield
        instance.send(:with_valid_url) {}
      end

    end

    context 'when not valid' do

      before do
        allow(instance).to receive(:valid_url_format?).and_return(false)
      end

      it { expect { instance.send(:with_valid_url) }.to raise_error(LinkThumbnailer::BadUriFormat) }

    end

  end

  describe '#set_http_headers' do

    let(:user_agent)  { 'foo' }
    let(:headers)     { instance.send(:http).override_headers }
    let(:action)      { instance.send(:set_http_headers) }

    before do
      allow(instance).to receive(:user_agent).and_return(user_agent)
      action
    end

    it { expect(headers['User-Agent']).to eq(user_agent) }

  end

  describe '#set_http_options' do

    let(:http)    { instance.send(:http) }
    let(:action)  { instance.send(:set_http_options) }

    describe 'verify_mode' do

      context 'when verify_ssl is true' do

        before do
          allow(instance).to receive(:ssl_required?).and_return(true)
          action
        end

        it { expect(http.verify_mode).to_not eq(::OpenSSL::SSL::VERIFY_NONE) }

      end

      context 'when verify_ssl is not true' do

        before do
          allow(instance).to receive(:ssl_required?).and_return(false)
          action
        end

        it { expect(http.verify_mode).to eq(::OpenSSL::SSL::VERIFY_NONE) }

      end

    end

    describe 'open_timeout' do

      let(:http_open_timeout) { 1 }

      before do
        allow(instance).to receive(:http_open_timeout).and_return(http_open_timeout)
        action
      end

      it { expect(http.open_timeout).to eq(http_open_timeout) }

    end

    describe 'read_timeout' do

      let(:http_read_timeout) { 1 }

      before do
        allow(instance).to receive(:http_read_timeout).and_return(http_read_timeout)
        action
      end

      it { expect(http.read_timeout).to eq(http_read_timeout) }

    end
  end

  describe '#perform_request' do

    let(:action) { instance.send(:perform_request) }
    let(:http) { double }

    before do
      allow(instance).to receive(:http).and_return(http)
      allow(http).to receive(:request).and_return(response)
    end

    context 'when http success' do

      let(:code)      { 200 }
      let(:body)      { 'body' }
      let(:response)  { ::Net::HTTPSuccess.new('', code, body) }

      before do
        allow(response).to receive(:body).and_return(body)
      end

      it { expect(action.body).to eq(body) }

    end

    context 'when access non-utf8 encoded website' do

      let(:code) { 200 }
      let(:utf8_encoded_body) { File.read(File.expand_path('fixtures/google_utf8.html', File.dirname(__FILE__))) }
      let(:shift_jis_encoded_body) { File.read(File.expand_path('fixtures/google_shift_jis.html', File.dirname(__FILE__))) }
      let(:response) { ::Net::HTTPSuccess.new('', code, body) }

      before do
        allow(response).to receive(:body).and_return(body)
      end

      context 'when http success with valid charset provided in Content-Type header' do

        let(:body) { shift_jis_encoded_body }

        before do
          response['Content-Type'] = 'text/html; charset=Shift-JIS'
        end

        it { expect(action.body).to eq(shift_jis_encoded_body) }

      end

      context 'when http success with valid charset provided in Content-Type header' do

        let(:body) { shift_jis_encoded_body }

        before do
          response['Content-Type'] = 'text/html; charset=Shift_JIS'
        end

        it { expect(action.body).to eq(utf8_encoded_body) }

      end

    end

    context 'when http redirection' do

      let(:code)      { 200 }
      let(:body)      { 'body' }
      let(:response)  { ::Net::HTTPRedirection.new('', code, body) }
      let(:new_url)   { 'http://foo.com/bar' }

      before do
        allow(instance).to receive(:redirect_count).and_return(0)
        allow(instance).to receive(:resolve_relative_url).and_return(new_url)
      end

      it 'calls call method' do
        expect(instance).to receive(:resolve_relative_url)
        expect(instance).to receive(:call).with(new_url, instance.redirect_count + 1, {})
        action
      end

    end

  end

  describe '#build_absolute_url_for' do

    let(:scheme)        { 'https' }
    let(:host)          { 'foo.com' }
    let(:url)           { URI("#{scheme}://#{host}") }
    let(:relative_url)  { '/bar' }
    let(:action)        { instance.send(:build_absolute_url_for, relative_url).to_s }

    before do
      allow(instance).to receive(:url).and_return(url)
    end

    it { expect(action).to eq("#{scheme}://#{host}#{relative_url}") }

  end

  describe '#redirect_limit' do

    let(:redirect_limit)  { 1 }
    let(:action)          { instance.send(:redirect_limit) }

    before do
      instance.config.redirect_limit = redirect_limit
    end

    it { expect(action).to eq(redirect_limit) }

  end

  describe '#user_agent' do

    let(:user_agent)  { 'foo' }
    let(:action)      { instance.send(:user_agent) }

    before do
      instance.config.user_agent = user_agent
    end

    it { expect(action).to eq(user_agent) }

  end

  describe '#http_open_timeout' do

    let(:http_open_timeout)  { 1 }
    let(:action)        { instance.send(:http_open_timeout) }

    before do
      instance.config.http_open_timeout = http_open_timeout
    end

    it { expect(action).to eq(http_open_timeout) }

  end

  describe '#http_read_timeout' do

    let(:http_read_timeout)  { 1 }
    let(:action)        { instance.send(:http_read_timeout) }

    before do
      instance.config.http_read_timeout = http_read_timeout
    end

    it { expect(action).to eq(http_read_timeout) }

  end

  describe '#ssl_required?' do

    let(:verify_ssl)  { true }
    let(:action)      { instance.send(:ssl_required?) }

    before do
      instance.config.verify_ssl = verify_ssl
    end

    it { expect(action).to eq(verify_ssl) }

  end

  describe '#valid_url_format?' do

    let(:action) { instance.send(:valid_url_format?) }

    context 'when bad format' do

      before do
        allow(instance).to receive(:url).and_return("http://foo.com")
      end

      it { expect(action).to be_falsey }

    end

    context 'when valid format' do

      before do
        allow(instance).to receive(:url).and_return(URI("http://foo.com"))
      end

      it { expect(action).to be_truthy }

    end

  end

  describe '#too_many_redirections?' do

    let(:action) { instance.send(:too_many_redirections?) }

    context 'when redirect count is greater than redirect limit' do

      before do
        allow(instance).to receive(:redirect_count).and_return(5)
        allow(instance).to receive(:redirect_limit).and_return(4)
      end

      it { expect(action).to be_truthy }

    end

    context 'when redirect count is less than redirect limit' do

      before do
        allow(instance).to receive(:redirect_count).and_return(4)
        allow(instance).to receive(:redirect_limit).and_return(5)
      end

      it { expect(action).to be_falsey }

    end

    context 'when redirect count is equal to redirect limit' do

      before do
        allow(instance).to receive(:redirect_count).and_return(5)
        allow(instance).to receive(:redirect_limit).and_return(5)
      end

      it { expect(action).to be_falsey }

    end

  end

  describe '#url=' do

    before do
      instance.send(:url=, url)
    end

    it { expect(instance.url).to be_a(URI) }

  end

end
