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