434 lines
12 KiB
Ruby
434 lines
12 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?).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?)
|
|
.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 '.readable_largest_record_keys' do
|
|
let(:record) { { foo: foo, bar: bar, baz: baz, small: 'xxx' } }
|
|
let(:foo) { 'x' * 1000 }
|
|
let(:bar) { 'x' * 10_000 }
|
|
let(:baz) { 'x' * 100_000 }
|
|
|
|
subject { current.readable_largest_record_keys(record) }
|
|
|
|
it { should eq 'baz (100.00 Kb), bar (10.00 Kb), foo (1.00 Kb)' }
|
|
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?' 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?(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
|
|
|
|
it 'should get information from message' do
|
|
should include('object_id' => 'object_id')
|
|
should include('size' => '109.20 Kb')
|
|
should include('size_limit' => '10 Kb')
|
|
end
|
|
|
|
describe 'includes the nodes to index' do
|
|
before do
|
|
allow(configurator).to receive(:algolia).and_return('nodes')
|
|
end
|
|
|
|
it do
|
|
should include('nodes_to_index' => 'nodes')
|
|
end
|
|
end
|
|
|
|
describe 'includes information about the bad record' do
|
|
before do
|
|
allow(current)
|
|
.to receive(:readable_largest_record_keys)
|
|
.and_return('wrong_keys')
|
|
end
|
|
|
|
it do
|
|
should include('object_title' => 'foo')
|
|
should include('object_url' => 'url')
|
|
should include('probable_wrong_keys' => 'wrong_keys')
|
|
end
|
|
end
|
|
|
|
describe 'save log file' do
|
|
before do
|
|
expect(::JSON)
|
|
.to receive(:pretty_generate)
|
|
.with(objectID: 'object_id', title: 'foo', url: 'url')
|
|
.and_return('{json}')
|
|
expect(logger)
|
|
.to receive(:write_to_file)
|
|
.with(
|
|
'jekyll-algolia-record-too-big-object_id.log',
|
|
'{json}'
|
|
)
|
|
.and_return('/path/to/file.log')
|
|
end
|
|
|
|
it 'should return the path of the log file in the output' do
|
|
should include('record_log_path' => '/path/to/file.log')
|
|
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
|