spec/core/scheduler_spec.rb (315 lines of code) (raw):

require 'core/native_scheduler' require 'core/scheduler' require 'core/elastic_connector_actions' require 'core/filtering/validation_status' require 'connectors/registry' describe Core::Scheduler do subject { Core::NativeScheduler.new(poll_interval, heartbeat_interval) } let(:poll_interval) { 999 } let(:heartbeat_interval) { 999 } let(:connector_settings) { double } before(:each) do allow(connector_settings).to receive(:service_type).and_return('mongodb') end shared_examples_for 'triggers' do |task| it "yields #{task} task" do expect { |b| subject.when_triggered(&b) }.to yield_with_args(connector_settings, task) end end shared_examples_for 'does not trigger' do |task| it "does not yield #{task} task" do expect { |b| subject.when_triggered(&b) }.to_not yield_control end end describe '#when_triggered' do before(:each) do allow(subject).to receive(:connector_settings).and_return([connector_settings]) subject.instance_variable_set(:@is_shutting_down, true) end context 'with sync task' do let(:allow_sync) { true } let(:sync_now) { false } let(:sync_enabled) { true } let(:sync_interval) { '0 0 * * * ?' } let(:full_sync_scheduling) do { :enabled => sync_enabled, :interval => sync_interval } end let(:valid_index_name) { true } let(:cron_parser) { instance_double(Fugit::Cron) } let(:next_trigger_time) { Time.now } before(:each) do allow(subject).to receive(:heartbeat_triggered?).with(connector_settings).and_return(false) allow(subject).to receive(:configuration_triggered?).with(connector_settings).and_return(false) allow(subject).to receive(:filtering_validation_triggered?).with(connector_settings).and_return(false) allow(connector_settings).to receive(:connector_status_allows_sync?).and_return(allow_sync) allow(connector_settings).to receive(:id).and_return('123') allow(connector_settings).to receive(:connector_status).and_return('configured') allow(connector_settings).to receive(:sync_now?).and_return(sync_now) allow(connector_settings).to receive(:full_sync_scheduling).and_return(full_sync_scheduling) allow(connector_settings).to receive(:valid_index_name?).and_return(valid_index_name) allow(connector_settings).to receive(:formatted).and_return('') allow(Utility::Cron).to receive(:quartz_to_crontab).with(sync_interval) allow(Fugit::Cron).to receive(:parse).and_return(cron_parser) allow(cron_parser).to receive(:next_time).and_return(next_trigger_time) end it 'yields sync task' do expect { |b| subject.when_triggered(&b) }.to yield_with_args(connector_settings, :sync) end it_behaves_like 'triggers', :sync context 'when index name is invalid' do let(:valid_index_name) { false } it_behaves_like 'does not trigger', :sync end context 'when connector is not ready to sync' do let(:allow_sync) { false } it_behaves_like 'does not trigger', :sync end context 'when connector is set to sync now' do let(:sync_now) { true } it_behaves_like 'triggers', :sync end context 'when connector sync is disabled' do let(:sync_enabled) { false } it_behaves_like 'does not trigger', :sync end context 'when connector sync interval is not configured' do let(:sync_interval) { nil } it_behaves_like 'does not trigger', :sync end context 'when connector sync interval is not an invalid quartz' do before(:each) do allow(Utility::Cron).to receive(:quartz_to_crontab).with(sync_interval).and_raise(StandardError) end it_behaves_like 'does not trigger', :sync end context 'when connector sync interval cannot be parsed as a cron' do let(:cron_parser) { nil } it_behaves_like 'does not trigger', :sync end context 'when next trigger time is before the next poll' do let(:next_trigger_time) { Time.now + poll_interval - 10 } it_behaves_like 'triggers', :sync end context 'when next trigger time is after the next poll' do let(:next_trigger_time) { Time.now + poll_interval + 10 } it_behaves_like 'does not trigger', :sync end context 'when an error is thrown' do before(:each) do allow(subject).to receive(:sync_triggered?).and_raise(StandardError.new('Oh no!')) end it_behaves_like 'does not trigger', :sync end end context 'with heartbeat task' do let(:last_seen) { (Time.now - heartbeat_interval - 60 * 10).to_s } before(:each) do allow(subject).to receive(:sync_triggered?).with(connector_settings).and_return(false) allow(subject).to receive(:configuration_triggered?).with(connector_settings).and_return(false) allow(subject).to receive(:filtering_validation_triggered?).with(connector_settings).and_return(false) allow(connector_settings).to receive(:[]).with(:last_seen).and_return(last_seen) end it_behaves_like 'triggers', :heartbeat context 'when there\'s no last_seen' do let(:last_seen) { nil } it_behaves_like 'triggers', :heartbeat end context 'when last_seen is an invalid time' do before(:each) do allow(Time).to receive(:parse).with(last_seen).and_raise(ArgumentError) end it_behaves_like 'triggers', :heartbeat end context 'when last_sean is within the interval' do let(:last_seen) { Time.now.to_s } it_behaves_like 'does not trigger', :heartbeat end end context 'with configuration task' do let(:connector_status) { Connectors::ConnectorStatus::CREATED } let(:needs_service_type) { false } before(:each) do allow(subject).to receive(:sync_triggered?).with(connector_settings).and_return(false) allow(subject).to receive(:heartbeat_triggered?).with(connector_settings).and_return(false) allow(subject).to receive(:filtering_validation_triggered?).with(connector_settings).and_return(false) allow(connector_settings).to receive(:connector_status).and_return(connector_status) allow(connector_settings).to receive(:needs_service_type?).and_return(needs_service_type) end it_behaves_like 'triggers', :configuration # Regression bug!!! # We need to trigger configuration for the connector that was created with no service_type. # Otherwise on-prem connectors won't be able to start the flow at all. context 'when connector has no service_type' do let(:needs_service_type) { true } it_behaves_like 'triggers', :configuration end (Connectors::ConnectorStatus::STATUSES - [Connectors::ConnectorStatus::CREATED]).each do |status| context "when connector status is #{status}" do let(:connector_status) { status } it_behaves_like 'does not trigger', :configuration end end end context 'with filtering validation task' do let(:any_filtering_feature_enabled) { true } let(:features) { { :filtering_advanced_config => true, :filtering_rules => true } } let(:state) { Core::Filtering::ValidationStatus::EDITED } let(:advanced_config) { { :find => { :filter => { :$text => { :$search => 'garden', :$caseSensitive => false } }, :options => { :skip => 10, :limit => 1000 } } } } let(:validation) { { :state => state, :errors => [] } } let(:filtering) { { :domain => 'DEFAULT', :active => {}, :draft => { :rules => [], :advanced_snippet => advanced_config, :validation => validation } } } before(:each) do allow(subject).to receive(:sync_triggered?).with(connector_settings).and_return(false) allow(subject).to receive(:heartbeat_triggered?).with(connector_settings).and_return(false) allow(subject).to receive(:configuration_triggered?).with(connector_settings).and_return(false) allow(connector_settings).to receive(:filtering).and_return(filtering) allow(connector_settings).to receive(:formatted).and_return('MOCKED_VALUE') allow(connector_settings).to receive(:features).and_return(features) allow(connector_settings).to receive(:any_filtering_feature_enabled?).and_return(any_filtering_feature_enabled) end context 'when filtering feature flags are not set' do let(:state) { Core::Filtering::ValidationStatus::EDITED } context 'when all filtering features are disabled' do let(:any_filtering_feature_enabled) { false } it_behaves_like 'does not trigger', :filter_validation end end context 'filtering feature flags are set' do let(:any_filtering_feature_enabled) { true } it_behaves_like 'triggers', :filter_validation end context 'filtering is not present' do context 'filtering is nil' do let(:filtering) { nil } it_behaves_like 'does not trigger', :filter_validation end context 'filtering is an empty array' do let(:filtering) { [] } it_behaves_like 'does not trigger', :filter_validation end context 'filtering is an empty hash' do let(:filtering) { {} } it_behaves_like 'does not trigger', :filter_validation end end context 'filtering does not contain draft field' do let(:filtering) { { :domain => 'DEFAULT', :active => {}, :validation => {} } } it_behaves_like 'does not trigger', :filter_validation end context 'filtering draft advanced config is not present' do context 'advanced config is nil' do let(:advanced_config) { nil } context 'validation state is \'valid\'' do let(:state) { Core::Filtering::ValidationStatus::VALID } it_behaves_like 'does not trigger', :filter_validation end context 'validation state is \'edited\'' do let(:state) { Core::Filtering::ValidationStatus::EDITED } it_behaves_like 'triggers', :filter_validation end end context 'advanced config is empty' do let(:advanced_config) { {} } context 'validation state is \'valid\'' do let(:state) { Core::Filtering::ValidationStatus::VALID } it_behaves_like 'does not trigger', :filter_validation end context 'validation state is \'edited\'' do let(:state) { Core::Filtering::ValidationStatus::EDITED } it_behaves_like 'triggers', :filter_validation end end end context 'filtering validation is nil' do let(:validation) { nil } it_behaves_like 'does not trigger', :filter_validation end context 'filtering validation state is \'invalid\'' do let(:state) { Core::Filtering::ValidationStatus::INVALID } it_behaves_like 'does not trigger', :filter_validation end context 'filtering validation state is \'valid\'' do let(:state) { Core::Filtering::ValidationStatus::VALID } it_behaves_like 'does not trigger', :filter_validation end context 'filtering validation state is \'edited\'' do let(:state) { Core::Filtering::ValidationStatus::EDITED } it_behaves_like 'triggers', :filter_validation end end end end