diff options
Diffstat (limited to 'spec/models/hooks')
-rw-r--r-- | spec/models/hooks/project_hook_spec.rb | 9 | ||||
-rw-r--r-- | spec/models/hooks/service_hook_spec.rb | 12 | ||||
-rw-r--r-- | spec/models/hooks/system_hook_spec.rb | 8 | ||||
-rw-r--r-- | spec/models/hooks/web_hook_log_archived_spec.rb | 52 | ||||
-rw-r--r-- | spec/models/hooks/web_hook_spec.rb | 198 |
5 files changed, 271 insertions, 8 deletions
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index 69fbc4c3b4f..88149465232 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -30,4 +30,13 @@ RSpec.describe ProjectHook do expect(described_class.tag_push_hooks).to eq([hook]) end end + + describe '#rate_limit' do + let_it_be(:hook) { create(:project_hook) } + let_it_be(:plan_limits) { create(:plan_limits, :default_plan, web_hook_calls: 100) } + + it 'returns the default limit' do + expect(hook.rate_limit).to be(100) + end + end end diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index f7045d7ac5e..651716c3280 100644 --- a/spec/models/hooks/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -4,11 +4,11 @@ require 'spec_helper' RSpec.describe ServiceHook do describe 'associations' do - it { is_expected.to belong_to :service } + it { is_expected.to belong_to :integration } end describe 'validations' do - it { is_expected.to validate_presence_of(:service) } + it { is_expected.to validate_presence_of(:integration) } end describe 'execute' do @@ -22,4 +22,12 @@ RSpec.describe ServiceHook do hook.execute(data) end end + + describe '#rate_limit' do + let(:hook) { build(:service_hook) } + + it 'returns nil' do + expect(hook.rate_limit).to be_nil + end + end end diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 02e630cbf27..a72034f1ac5 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -169,4 +169,12 @@ RSpec.describe SystemHook do hook.async_execute(data, hook_name) end end + + describe '#rate_limit' do + let(:hook) { build(:system_hook) } + + it 'returns nil' do + expect(hook.rate_limit).to be_nil + end + end end diff --git a/spec/models/hooks/web_hook_log_archived_spec.rb b/spec/models/hooks/web_hook_log_archived_spec.rb new file mode 100644 index 00000000000..ac726dbaf4f --- /dev/null +++ b/spec/models/hooks/web_hook_log_archived_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WebHookLogArchived do + let(:source_table) { WebHookLog } + let(:destination_table) { described_class } + + it 'has the same columns as the source table' do + column_names_from_source_table = column_names(source_table) + column_names_from_destination_table = column_names(destination_table) + + expect(column_names_from_destination_table).to match_array(column_names_from_source_table) + end + + it 'has the same null constraints as the source table' do + constraints_from_source_table = null_constraints(source_table) + constraints_from_destination_table = null_constraints(destination_table) + + expect(constraints_from_destination_table.to_a).to match_array(constraints_from_source_table.to_a) + end + + it 'inserts the same record as the one in the source table', :aggregate_failures do + expect { create(:web_hook_log) }.to change { destination_table.count }.by(1) + + event_from_source_table = source_table.connection.select_one( + "SELECT * FROM #{source_table.table_name} ORDER BY created_at desc LIMIT 1" + ) + event_from_destination_table = destination_table.connection.select_one( + "SELECT * FROM #{destination_table.table_name} ORDER BY created_at desc LIMIT 1" + ) + + expect(event_from_destination_table).to eq(event_from_source_table) + end + + def column_names(table) + table.connection.select_all(<<~SQL) + SELECT c.column_name + FROM information_schema.columns c + WHERE c.table_name = '#{table.table_name}' + SQL + end + + def null_constraints(table) + table.connection.select_all(<<~SQL) + SELECT c.column_name, c.is_nullable + FROM information_schema.columns c + WHERE c.table_name = '#{table.table_name}' + AND c.column_name != 'created_at' + SQL + end +end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 413e69fb071..b528dbedd2c 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -3,7 +3,15 @@ require 'spec_helper' RSpec.describe WebHook do - let(:hook) { build(:project_hook) } + include AfterNextHelpers + + let_it_be(:project) { create(:project) } + + let(:hook) { build(:project_hook, project: project) } + + around do |example| + freeze_time { example.run } + end describe 'associations' do it { is_expected.to have_many(:web_hook_logs) } @@ -69,18 +77,30 @@ RSpec.describe WebHook do let(:data) { { key: 'value' } } let(:hook_name) { 'project hook' } - before do - expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original + it '#execute' do + expect_next(WebHookService).to receive(:execute) + + hook.execute(data, hook_name) end - it '#execute' do - expect_any_instance_of(WebHookService).to receive(:execute) + it 'does not execute non-executable hooks' do + hook.update!(disabled_until: 1.day.from_now) + + expect(WebHookService).not_to receive(:new) hook.execute(data, hook_name) end it '#async_execute' do - expect_any_instance_of(WebHookService).to receive(:async_execute) + expect_next(WebHookService).to receive(:async_execute) + + hook.async_execute(data, hook_name) + end + + it 'does not async execute non-executable hooks' do + hook.update!(disabled_until: 1.day.from_now) + + expect(WebHookService).not_to receive(:new) hook.async_execute(data, hook_name) end @@ -94,4 +114,170 @@ RSpec.describe WebHook do expect { web_hook.destroy! }.to change(web_hook.web_hook_logs, :count).by(-3) end end + + describe '.executable' do + let(:not_executable) do + [ + [0, Time.current], + [0, 1.minute.from_now], + [1, 1.minute.from_now], + [3, 1.minute.from_now], + [4, nil], + [4, 1.day.ago], + [4, 1.minute.from_now] + ].map do |(recent_failures, disabled_until)| + create(:project_hook, project: project, recent_failures: recent_failures, disabled_until: disabled_until) + end + end + + let(:executables) do + [ + [0, nil], + [0, 1.day.ago], + [1, nil], + [1, 1.day.ago], + [3, nil], + [3, 1.day.ago] + ].map do |(recent_failures, disabled_until)| + create(:project_hook, project: project, recent_failures: recent_failures, disabled_until: disabled_until) + end + end + + it 'finds the correct set of project hooks' do + expect(described_class.where(project_id: project.id).executable).to match_array executables + end + + context 'when the feature flag is not enabled' do + before do + stub_feature_flags(web_hooks_disable_failed: false) + end + + it 'is the same as all' do + expect(described_class.where(project_id: project.id).executable).to match_array(executables + not_executable) + end + end + end + + describe '#executable?' do + let(:web_hook) { create(:project_hook, project: project) } + + where(:recent_failures, :not_until, :executable) do + [ + [0, :not_set, true], + [0, :past, true], + [0, :future, false], + [0, :now, false], + [1, :not_set, true], + [1, :past, true], + [1, :future, false], + [3, :not_set, true], + [3, :past, true], + [3, :future, false], + [4, :not_set, false], + [4, :past, false], + [4, :future, false] + ] + end + + with_them do + # Phasing means we cannot put these values in the where block, + # which is not subject to the frozen time context. + let(:disabled_until) do + case not_until + when :not_set + nil + when :past + 1.minute.ago + when :future + 1.minute.from_now + when :now + Time.current + end + end + + before do + web_hook.update!(recent_failures: recent_failures, disabled_until: disabled_until) + end + + it 'has the correct state' do + expect(web_hook.executable?).to eq(executable) + end + + context 'when the feature flag is enabled for a project' do + before do + stub_feature_flags(web_hooks_disable_failed: project) + end + + it 'has the expected value' do + expect(web_hook.executable?).to eq(executable) + end + end + + context 'when the feature flag is not enabled' do + before do + stub_feature_flags(web_hooks_disable_failed: false) + end + + it 'is executable' do + expect(web_hook).to be_executable + end + end + end + end + + describe '#next_backoff' do + context 'when there was no last backoff' do + before do + hook.backoff_count = 0 + end + + it 'is 10 minutes' do + expect(hook.next_backoff).to eq(described_class::INITIAL_BACKOFF) + end + end + + context 'when we have backed off once' do + before do + hook.backoff_count = 1 + end + + it 'is twice the initial value' do + expect(hook.next_backoff).to eq(20.minutes) + end + end + + context 'when we have backed off 3 times' do + before do + hook.backoff_count = 3 + end + + it 'grows exponentially' do + expect(hook.next_backoff).to eq(80.minutes) + end + end + + context 'when the previous backoff was large' do + before do + hook.backoff_count = 8 # last value before MAX_BACKOFF + end + + it 'does not exceed the max backoff value' do + expect(hook.next_backoff).to eq(described_class::MAX_BACKOFF) + end + end + end + + describe '#enable!' do + it 'makes a hook executable' do + hook.recent_failures = 1000 + + expect { hook.enable! }.to change(hook, :executable?).from(false).to(true) + end + end + + describe '#disable!' do + it 'disables a hook' do + expect { hook.disable! }.to change(hook, :executable?).from(true).to(false) + end + end end |