386 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			386 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| # rubocop:disable Metrics/BlockLength
 | |
| require 'spec_helper'
 | |
| 
 | |
| describe(Jekyll::Algolia::ErrorHandler) do
 | |
|   let(:current) { Jekyll::Algolia::ErrorHandler }
 | |
|   let(:configurator) { Jekyll::Algolia::Configurator }
 | |
|   let(:logger) { Jekyll::Algolia::Logger }
 | |
|   let(:utils) { Jekyll::Algolia::Utils }
 | |
| 
 | |
|   describe '.stop' do
 | |
|     subject { -> { current.stop(error) } }
 | |
| 
 | |
|     let(:error) { double('Error') }
 | |
|     let(:identified_error) { nil }
 | |
|     before do
 | |
|       allow(current).to receive(:identify).and_return(identified_error)
 | |
|       allow(logger).to receive(:log)
 | |
|     end
 | |
| 
 | |
|     context 'with unknown error' do
 | |
|       let(:identified_error) { false }
 | |
|       before do
 | |
|         expect(logger).to receive(:log).with("E:#{error}")
 | |
|       end
 | |
| 
 | |
|       it { is_expected.to raise_error SystemExit }
 | |
|     end
 | |
| 
 | |
|     context 'with known error' do
 | |
|       let(:identified_error) { { name: 'foo', details: 'bar' } }
 | |
|       before do
 | |
|         expect(logger).to receive(:known_message).with('foo', 'bar')
 | |
|       end
 | |
| 
 | |
|       it { is_expected.to raise_error SystemExit }
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '.identify' do
 | |
|     let(:error) { double('Error') }
 | |
|     let(:context) { 'context' }
 | |
| 
 | |
|     subject { current.identify(error, context) }
 | |
| 
 | |
|     before do
 | |
|       allow(current).to receive(:unknown_application_id?).and_return(false)
 | |
|       allow(current).to receive(:invalid_credentials?).and_return(false)
 | |
|       allow(current).to receive(:record_too_big_api?).and_return(false)
 | |
|       allow(current).to receive(:too_many_records?).and_return(false)
 | |
|       allow(current).to receive(:unknown_setting?).and_return(false)
 | |
|       allow(current).to receive(:invalid_index_name?).and_return(false)
 | |
|     end
 | |
| 
 | |
|     it 'should return false if nothing matches' do
 | |
|       should eq false
 | |
|     end
 | |
| 
 | |
|     describe 'should call all methods with error and context' do
 | |
|       before do
 | |
|         current.identify(error, context)
 | |
|       end
 | |
|       it do
 | |
|         expect(current)
 | |
|           .to have_received(:unknown_application_id?)
 | |
|           .with(error, context)
 | |
|         expect(current)
 | |
|           .to have_received(:invalid_credentials?)
 | |
|           .with(error, context)
 | |
|         expect(current)
 | |
|           .to have_received(:record_too_big_api?)
 | |
|           .with(error, context)
 | |
|         expect(current)
 | |
|           .to have_received(:too_many_records?)
 | |
|           .with(error, context)
 | |
|         expect(current)
 | |
|           .to have_received(:unknown_setting?)
 | |
|           .with(error, context)
 | |
|         expect(current)
 | |
|           .to have_received(:invalid_index_name?)
 | |
|           .with(error, context)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     describe 'should return the result of one if matches' do
 | |
|       before do
 | |
|         allow(current)
 | |
|           .to receive(:too_many_records?)
 | |
|           .and_return('foo')
 | |
|       end
 | |
| 
 | |
|       it do
 | |
|         should eq(name: 'too_many_records', details: 'foo')
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '.error_hash' do
 | |
|     subject { current.error_hash(message) }
 | |
| 
 | |
|     context 'with a regular error message' do
 | |
|       let(:message) do
 | |
|         'Cannot POST to '\
 | |
|         'https://MY_APP_ID.algolia.net/1/section/index_name/action: '\
 | |
|         '{"message":"Custom message","status":403}'\
 | |
|         "\n (403)"
 | |
|       end
 | |
| 
 | |
|       it do
 | |
|         should include('verb' => 'POST')
 | |
|         should include('scheme' => 'https')
 | |
|         should include('application_id' => 'MY_APP_ID')
 | |
|         should include('api_version' => 1)
 | |
|         should include('api_section' => 'section')
 | |
|         should include('index_name' => 'index_name')
 | |
|         should include('api_action' => 'action')
 | |
|         should include('message' => 'Custom message')
 | |
|         should include('status' => 403)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     context 'with a message with query parameters' do
 | |
|       let(:message) do
 | |
|         'Cannot POST to '\
 | |
|         'https://MY_APP_ID.algolia.net/1/section/index_name/action?foo=bar: '\
 | |
|         '{"message":"Custom message","status":403}'\
 | |
|         "\n (403)"
 | |
|       end
 | |
| 
 | |
|       it do
 | |
|         should include('foo' => 'bar')
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     context 'with an error message with weird characaters' do
 | |
|       let(:message) do
 | |
|         'Cannot POST to '\
 | |
|         'https://MY_APP_ID.algolia.net/1/section/index_name$`!</action: '\
 | |
|         '{"message":"Custom message","status":403}'\
 | |
|         "\n (403)"
 | |
|       end
 | |
| 
 | |
|       it do
 | |
|         should include('index_name' => 'index_name$`!<')
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     context 'with a malformed error message' do
 | |
|       let(:message) { 'Unable to even parse this' }
 | |
| 
 | |
|       it { should eq false }
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '.unknown_application_id?' do
 | |
|     let(:error) { double('Error', message: message) }
 | |
| 
 | |
|     subject { current.unknown_application_id?(error) }
 | |
| 
 | |
|     describe 'not matching' do
 | |
|       let(:message) { 'foo bar' }
 | |
|       it { should eq false }
 | |
|     end
 | |
| 
 | |
|     describe 'matching' do
 | |
|       let(:message) do
 | |
|         # rubocop:disable Metrics/LineLength
 | |
|         'Cannot reach any host: '\
 | |
|           'getaddrinfo: Name or service not known (MY_APP_ID-dsn.algolia.net:443), '\
 | |
|           'getaddrinfo: No address associated with hostname (MY_APP_ID-3.algolianet.com:443), '\
 | |
|           'getaddrinfo: No address associated with hostname (MY_APP_ID-1.algolianet.com:443), '\
 | |
|           'getaddrinfo: No address associated with hostname (MY_APP_ID-2.algolianet.com:443)'
 | |
|         # rubocop:enable Metrics/LineLength
 | |
|       end
 | |
| 
 | |
|       it { should eq('application_id' => 'MY_APP_ID') }
 | |
|     end
 | |
| 
 | |
|     describe 'matching with a DSN' do
 | |
|       let(:message) do
 | |
|         # rubocop:disable Metrics/LineLength
 | |
|         'Cannot reach any host: '\
 | |
|         'getaddrinfo: Name or service not known (MY_APP_ID.algolia.net:443), '\
 | |
|         'getaddrinfo: No address associated with hostname (MY_APP_ID-3.algolianet.com:443), '\
 | |
|         'getaddrinfo: No address associated with hostname (MY_APP_ID-1.algolianet.com:443), '\
 | |
|         'getaddrinfo: No address associated with hostname (MY_APP_ID-2.algolianet.com:443)'
 | |
|         # rubocop:enable Metrics/LineLength
 | |
|       end
 | |
| 
 | |
|       it { should eq('application_id' => 'MY_APP_ID') }
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '.invalid_credentials?' do
 | |
|     let(:error) { double('Error').as_null_object }
 | |
| 
 | |
|     subject { current.invalid_credentials?(error) }
 | |
| 
 | |
|     before do
 | |
|       allow(current).to receive(:error_hash).and_return(error_hash)
 | |
|     end
 | |
| 
 | |
|     describe 'not matching' do
 | |
|       let(:error_hash) { false }
 | |
|       it { should eq false }
 | |
|     end
 | |
| 
 | |
|     context 'with wrong API key' do
 | |
|       let(:error_hash) do
 | |
|         {
 | |
|           'message' => 'Invalid Application-ID or API key',
 | |
|           'application_id' => 'MY_APP_ID'
 | |
|         }
 | |
|       end
 | |
|       before do
 | |
|         allow(configurator)
 | |
|           .to receive(:index_name)
 | |
|           .and_return('foo')
 | |
|         allow(configurator)
 | |
|           .to receive(:index_object_ids_name)
 | |
|           .and_return('foo_object_ids')
 | |
|       end
 | |
|       it { should include('application_id' => 'MY_APP_ID') }
 | |
|       it { should include('index_name' => 'foo') }
 | |
|       it { should include('index_object_ids_name' => 'foo_object_ids') }
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '.record_too_big_api?' do
 | |
|     let(:error) { double('Error').as_null_object }
 | |
|     let(:error_hash) do
 | |
|       {
 | |
|         'message' => 'Record at the position 3 '\
 | |
|                      'objectID=deadbeef is too big size=109196 bytes. '\
 | |
|                      'Contact us if you need an extended quota',
 | |
|         'objectID' => 'object_id'
 | |
|       }
 | |
|     end
 | |
|     let(:context) do
 | |
|       {
 | |
|         operations: [
 | |
|           {
 | |
|             action: 'deleteObject',
 | |
|             body: { objectID: 'object_to_delete' }
 | |
|           },
 | |
|           {
 | |
|             action: 'addObject',
 | |
|             body: { objectID: 'object_id', title: 'foo', url: 'url' }
 | |
|           },
 | |
|           {
 | |
|             action: 'clear'
 | |
|           },
 | |
|           {
 | |
|             action: 'addObject',
 | |
|             body: { content: %w[object_id1 object_id2] }
 | |
|           }
 | |
|         ]
 | |
|       }
 | |
|     end
 | |
| 
 | |
|     subject { current.record_too_big_api?(error, context) }
 | |
| 
 | |
|     before do
 | |
|       allow(current).to receive(:error_hash).and_return(error_hash)
 | |
|       allow(utils).to receive(:find_by_key).and_return({})
 | |
|       allow(current).to receive(:readable_largest_record_keys)
 | |
|       allow(logger).to receive(:write_to_file)
 | |
|     end
 | |
| 
 | |
|     describe 'wrongly formatted message' do
 | |
|       let(:error_hash) { false }
 | |
| 
 | |
|       it { should eq false }
 | |
|     end
 | |
| 
 | |
|     describe 'not matching' do
 | |
|       let(:error_hash) { { 'message' => 'foo bar' } }
 | |
| 
 | |
|       it { should eq false }
 | |
|     end
 | |
| 
 | |
|     describe 'should get information from message' do
 | |
|       before do
 | |
|         allow(configurator)
 | |
|           .to receive(:algolia)
 | |
|           .with('max_record_size')
 | |
|           .and_return('max_size')
 | |
|       end
 | |
|       it do
 | |
|         should include('max_record_size' => 'max_size')
 | |
|         should include('record_size' => '109.20 Kb')
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '.unknown_setting?' do
 | |
|     let(:error) { double('Error').as_null_object }
 | |
|     let(:context) do
 | |
|       {
 | |
|         settings: {
 | |
|           'iDontExist' => 'foo'
 | |
|         }
 | |
|       }
 | |
|     end
 | |
| 
 | |
|     subject { current.unknown_setting?(error, context) }
 | |
| 
 | |
|     before do
 | |
|       allow(current).to receive(:error_hash).and_return(error_hash)
 | |
|     end
 | |
| 
 | |
|     describe 'not matching' do
 | |
|       let(:error_hash) { false }
 | |
|       it { should eq false }
 | |
|     end
 | |
| 
 | |
|     context 'with non-existent setting' do
 | |
|       let(:error_hash) do
 | |
|         {
 | |
|           'message' => 'Invalid object attributes: iDontExist '\
 | |
|                        'near line:1 column:456'
 | |
|         }
 | |
|       end
 | |
|       it do
 | |
|         should include('setting_name' => 'iDontExist')
 | |
|         should include('setting_value' => 'foo')
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '.invalid_index_name?' do
 | |
|     let(:error) { double('Error').as_null_object }
 | |
| 
 | |
|     subject { current.invalid_index_name?(error) }
 | |
| 
 | |
|     before do
 | |
|       allow(current).to receive(:error_hash).and_return(error_hash)
 | |
|       allow(configurator).to receive(:index_name).and_return('my_index')
 | |
|     end
 | |
| 
 | |
|     describe 'not matching' do
 | |
|       let(:error_hash) { false }
 | |
|       it { should eq false }
 | |
|     end
 | |
| 
 | |
|     context 'with invalid index name' do
 | |
|       let(:error_hash) do
 | |
|         {
 | |
|           'message' => 'indexName is not valid'
 | |
|         }
 | |
|       end
 | |
|       it do
 | |
|         should include('index_name' => 'my_index')
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe '.too_many_records?' do
 | |
|     let(:error) { double('Error').as_null_object }
 | |
| 
 | |
|     subject { current.too_many_records?(error) }
 | |
| 
 | |
|     before do
 | |
|       allow(current).to receive(:error_hash).and_return(error_hash)
 | |
|     end
 | |
| 
 | |
|     describe 'not matching' do
 | |
|       let(:error_hash) { false }
 | |
|       it { should eq false }
 | |
|     end
 | |
| 
 | |
|     context 'with quota exceeded' do
 | |
|       let(:error_hash) do
 | |
|         {
 | |
|           'message' => 'Record quota exceeded, change plan or delete records.'
 | |
|         }
 | |
|       end
 | |
|       it do
 | |
|         should eq({})
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | |
| # rubocop:enable Metrics/BlockLength
 |