Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-20 16:49:51 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-20 16:49:51 +0300
commit71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e (patch)
tree6a2d93ef3fb2d353bb7739e4b57e6541f51cdd71 /spec/support/shared_examples/models
parenta7253423e3403b8c08f8a161e5937e1488f5f407 (diff)
Add latest changes from gitlab-org/gitlab@15-9-stable-eev15.9.0-rc42
Diffstat (limited to 'spec/support/shared_examples/models')
-rw-r--r--spec/support/shared_examples/models/chat_integration_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb272
-rw-r--r--spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb64
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb177
-rw-r--r--spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb107
-rw-r--r--spec/support/shared_examples/models/exportable_shared_examples.rb73
-rw-r--r--spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/resource_event_shared_examples.rb20
9 files changed, 704 insertions, 21 deletions
diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
index 6d0462a9ee8..085fec6ff1e 100644
--- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb
@@ -33,7 +33,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
describe "#execute" do
let_it_be(:user) { create(:user) }
- let_it_be_with_reload(:project) { create(:project, :repository) }
+ let_it_be_with_refind(:project) { create(:project, :repository) }
let(:webhook_url) { "https://example.gitlab.com/" }
let(:webhook_url_regex) { /\A#{webhook_url}.*/ }
@@ -165,7 +165,7 @@ RSpec.shared_examples "chat integration" do |integration_name|
context "with issue events" do
let(:opts) { { title: "Awesome issue", description: "please fix" } }
let(:sample_data) do
- service = Issues::CreateService.new(project: project, current_user: user, params: opts, spam_params: nil)
+ service = Issues::CreateService.new(container: project, current_user: user, params: opts, spam_params: nil)
issue = service.execute[:issue]
service.hook_data(issue, "open")
end
diff --git a/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb
new file mode 100644
index 00000000000..122774a9028
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/auto_disabling_hooks_shared_examples.rb
@@ -0,0 +1,272 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a hook that gets automatically disabled on failure' do
+ shared_examples 'is tolerant of invalid records' do
+ specify do
+ hook.url = nil
+
+ expect(hook).to be_invalid
+ run_expectation
+ end
+ end
+
+ describe '.executable/.disabled', :freeze_time do
+ let!(:not_executable) do
+ [
+ [4, nil], # Exceeded the grace period, set by #fail!
+ [4, 1.second.from_now], # Exceeded the grace period, set by #backoff!
+ [4, Time.current] # Exceeded the grace period, set by #backoff!, edge-case
+ ].map do |(recent_failures, disabled_until)|
+ create(hook_factory, **default_factory_arguments, recent_failures: recent_failures,
+disabled_until: disabled_until)
+ end
+ end
+
+ let!(:executables) do
+ expired = 1.second.ago
+ borderline = Time.current
+ suspended = 1.second.from_now
+
+ [
+ # Most of these are impossible states, but are included for completeness
+ [0, nil],
+ [1, nil],
+ [3, nil],
+ [4, expired],
+
+ # Impossible cases:
+ [3, suspended],
+ [3, expired],
+ [3, borderline],
+ [1, suspended],
+ [1, expired],
+ [1, borderline],
+ [0, borderline],
+ [0, suspended],
+ [0, expired]
+ ].map do |(recent_failures, disabled_until)|
+ create(hook_factory, **default_factory_arguments, recent_failures: recent_failures,
+disabled_until: disabled_until)
+ end
+ end
+
+ it 'finds the correct set of project hooks' do
+ expect(find_hooks.executable).to match_array executables
+ expect(find_hooks.executable).to all(be_executable)
+
+ # As expected, and consistent
+ expect(find_hooks.disabled).to match_array not_executable
+ expect(find_hooks.disabled.map(&:executable?)).not_to include(true)
+
+ # Nothing is missing
+ expect(find_hooks.executable.to_a + find_hooks.disabled.to_a).to match_array(find_hooks.to_a)
+ end
+ end
+
+ describe '#executable?', :freeze_time do
+ let(:web_hook) { create(hook_factory, **default_factory_arguments) }
+
+ where(:recent_failures, :not_until, :executable) do
+ [
+ [0, :not_set, true],
+ [0, :past, true],
+ [0, :future, true],
+ [0, :now, true],
+ [1, :not_set, true],
+ [1, :past, true],
+ [1, :future, true],
+ [3, :not_set, true],
+ [3, :past, true],
+ [3, :future, true],
+ [4, :not_set, false],
+ [4, :past, true], # expired suspension
+ [4, :now, false], # active suspension
+ [4, :future, false] # active suspension
+ ]
+ 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
+ end
+ end
+
+ describe '#enable!' do
+ it 'makes a hook executable if it was marked as failed' do
+ hook.recent_failures = 1000
+
+ expect { hook.enable! }.to change { hook.executable? }.from(false).to(true)
+ end
+
+ it 'makes a hook executable if it is currently backed off' do
+ hook.recent_failures = 1000
+ hook.disabled_until = 1.hour.from_now
+
+ expect { hook.enable! }.to change { hook.executable? }.from(false).to(true)
+ end
+
+ it 'does not update hooks unless necessary' do
+ hook
+
+ sql_count = ActiveRecord::QueryRecorder.new { hook.enable! }.count
+
+ expect(sql_count).to eq(0)
+ end
+
+ include_examples 'is tolerant of invalid records' do
+ def run_expectation
+ hook.recent_failures = 1000
+
+ expect { hook.enable! }.to change { hook.executable? }.from(false).to(true)
+ end
+ end
+ end
+
+ describe '#backoff!' do
+ context 'when we have not backed off before' do
+ it 'does not disable the hook' do
+ expect { hook.backoff! }.not_to change { hook.executable? }.from(true)
+ end
+ end
+
+ context 'when we have exhausted the grace period' do
+ before do
+ hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD)
+ end
+
+ context 'when the hook is permanently disabled' do
+ before do
+ allow(hook).to receive(:permanently_disabled?).and_return(true)
+ end
+
+ it 'does not set disabled_until' do
+ expect { hook.backoff! }.not_to change { hook.disabled_until }
+ end
+
+ it 'does not increment the backoff count' do
+ expect { hook.backoff! }.not_to change { hook.backoff_count }
+ end
+ end
+
+ include_examples 'is tolerant of invalid records' do
+ def run_expectation
+ expect { hook.backoff! }.to change { hook.backoff_count }.by(1)
+ end
+ end
+ end
+ end
+
+ describe '#failed!' do
+ include_examples 'is tolerant of invalid records' do
+ def run_expectation
+ expect { hook.failed! }.to change { hook.recent_failures }.by(1)
+ end
+ end
+ end
+
+ describe '#disable!' do
+ it 'disables a hook' do
+ expect { hook.disable! }.to change { hook.executable? }.from(true).to(false)
+ end
+
+ it 'does nothing if the hook is already disabled' do
+ allow(hook).to receive(:permanently_disabled?).and_return(true)
+
+ sql_count = ActiveRecord::QueryRecorder.new { hook.disable! }.count
+
+ expect(sql_count).to eq(0)
+ end
+
+ include_examples 'is tolerant of invalid records' do
+ def run_expectation
+ expect { hook.disable! }.to change { hook.executable? }.from(true).to(false)
+ end
+ end
+ end
+
+ describe '#temporarily_disabled?' do
+ it 'is false when not temporarily disabled' do
+ expect(hook).not_to be_temporarily_disabled
+ end
+
+ it 'allows FAILURE_THRESHOLD initial failures before we back-off' do
+ WebHook::FAILURE_THRESHOLD.times do
+ hook.backoff!
+ expect(hook).not_to be_temporarily_disabled
+ end
+
+ hook.backoff!
+ expect(hook).to be_temporarily_disabled
+ end
+
+ context 'when hook has been told to back off' do
+ before do
+ hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD)
+ hook.backoff!
+ end
+
+ it 'is true' do
+ expect(hook).to be_temporarily_disabled
+ end
+ end
+ end
+
+ describe '#permanently_disabled?' do
+ it 'is false when not disabled' do
+ expect(hook).not_to be_permanently_disabled
+ end
+
+ context 'when hook has been disabled' do
+ before do
+ hook.disable!
+ end
+
+ it 'is true' do
+ expect(hook).to be_permanently_disabled
+ end
+ end
+ end
+
+ describe '#alert_status' do
+ subject(:status) { hook.alert_status }
+
+ it { is_expected.to eq :executable }
+
+ context 'when hook has been disabled' do
+ before do
+ hook.disable!
+ end
+
+ it { is_expected.to eq :disabled }
+ end
+
+ context 'when hook has been backed off' do
+ before do
+ hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1)
+ hook.disabled_until = 1.hour.from_now
+ end
+
+ it { is_expected.to eq :temporarily_disabled }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
index f98be12523d..5755b9a56b1 100644
--- a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb
@@ -15,7 +15,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
describe attribute do
describe '#increment_counter', :redis do
let(:amount) { 10 }
- let(:increment) { Gitlab::Counters::Increment.new(amount: amount) }
+ let(:increment) { Gitlab::Counters::Increment.new(amount: amount, ref: 3) }
let(:counter_key) { model.counter(attribute).key }
subject { model.increment_counter(attribute, increment) }
@@ -31,6 +31,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
attribute: attribute,
project_id: model.project_id,
increment: amount,
+ ref: increment.ref,
new_counter_value: 0 + amount,
current_db_value: model.read_attribute(attribute),
'correlation_id' => an_instance_of(String),
@@ -74,27 +75,36 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
end
describe '#bulk_increment_counter', :redis do
- let(:increments) { [Gitlab::Counters::Increment.new(amount: 10), Gitlab::Counters::Increment.new(amount: 5)] }
+ let(:increments) do
+ [
+ Gitlab::Counters::Increment.new(amount: 10, ref: 1),
+ Gitlab::Counters::Increment.new(amount: 5, ref: 2)
+ ]
+ end
+
let(:total_amount) { increments.sum(&:amount) }
let(:counter_key) { model.counter(attribute).key }
subject { model.bulk_increment_counter(attribute, increments) }
context 'when attribute is a counter attribute' do
- it 'increments the counter in Redis and logs it' do
- expect(Gitlab::AppLogger).to receive(:info).with(
- hash_including(
- message: 'Increment counter attribute',
- attribute: attribute,
- project_id: model.project_id,
- increment: total_amount,
- new_counter_value: 0 + total_amount,
- current_db_value: model.read_attribute(attribute),
- 'correlation_id' => an_instance_of(String),
- 'meta.feature_category' => 'test',
- 'meta.caller_id' => 'caller'
+ it 'increments the counter in Redis and logs each increment' do
+ increments.each do |increment|
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Increment counter attribute',
+ attribute: attribute,
+ project_id: model.project_id,
+ increment: increment.amount,
+ ref: increment.ref,
+ new_counter_value: 0 + total_amount,
+ current_db_value: model.read_attribute(attribute),
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
)
- )
+ end
subject
@@ -104,6 +114,30 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
end
end
+ context 'when feature flag split_log_bulk_increment_counter is disabled' do
+ before do
+ stub_feature_flags(split_log_bulk_increment_counter: false)
+ end
+
+ it 'logs a single total increment' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Increment counter attribute',
+ attribute: attribute,
+ project_id: model.project_id,
+ increment: increments.sum(&:amount),
+ new_counter_value: 0 + total_amount,
+ current_db_value: model.read_attribute(attribute),
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
+ )
+
+ subject
+ end
+ end
+
it 'does not increment the counter for the record' do
expect { subject }.not_to change { model.reset.read_attribute(attribute) }
end
diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
index 974fc8f402a..0ef9ab25505 100644
--- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb
@@ -276,7 +276,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
describe 'Push events' do
let_it_be(:user) { create(:user) }
- let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
+ let_it_be_with_refind(:project) { create(:project, :repository, creator: user) }
before do
allow(chat_integration).to receive_messages(
@@ -520,7 +520,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
describe 'Pipeline events' do
let_it_be(:user) { create(:user) }
- let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
+ let_it_be_with_refind(:project) { create(:project, :repository, creator: user) }
let(:pipeline) do
create(:ci_pipeline,
project: project, status: status,
@@ -671,7 +671,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name
describe 'Deployment events' do
let_it_be(:user) { create(:user) }
- let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
+ let_it_be_with_refind(:project) { create(:project, :repository, creator: user) }
let_it_be(:deployment) do
create(:deployment, :success, project: project, sha: project.commit.sha, ref: project.default_branch)
diff --git a/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb
new file mode 100644
index 00000000000..848840ee297
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/unstoppable_hooks_shared_examples.rb
@@ -0,0 +1,177 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a hook that does not get automatically disabled on failure' do
+ describe '.executable/.disabled', :freeze_time do
+ let!(:executables) 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],
+ [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(hook_factory, **default_factory_arguments, recent_failures: recent_failures,
+disabled_until: disabled_until)
+ end
+ end
+
+ it 'finds the correct set of project hooks' do
+ expect(find_hooks).to all(be_executable)
+ expect(find_hooks.executable).to match_array executables
+ expect(find_hooks.disabled).to be_empty
+ end
+ end
+
+ describe '#executable?', :freeze_time do
+ let(:web_hook) { create(hook_factory, **default_factory_arguments) }
+
+ where(:recent_failures, :not_until) do
+ [
+ [0, :not_set],
+ [0, :past],
+ [0, :future],
+ [0, :now],
+ [1, :not_set],
+ [1, :past],
+ [1, :future],
+ [3, :not_set],
+ [3, :past],
+ [3, :future],
+ [4, :not_set],
+ [4, :past], # expired suspension
+ [4, :now], # active suspension
+ [4, :future] # active suspension
+ ]
+ 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).to be_executable
+ end
+ end
+ end
+
+ describe '#enable!' do
+ it 'makes a hook executable if it was marked as failed' do
+ hook.recent_failures = 1000
+
+ expect { hook.enable! }.not_to change { hook.executable? }.from(true)
+ end
+
+ it 'makes a hook executable if it is currently backed off' do
+ hook.recent_failures = 1000
+ hook.disabled_until = 1.hour.from_now
+
+ expect { hook.enable! }.not_to change { hook.executable? }.from(true)
+ end
+
+ it 'does not update hooks unless necessary' do
+ hook
+
+ sql_count = ActiveRecord::QueryRecorder.new { hook.enable! }.count
+
+ expect(sql_count).to eq(0)
+ end
+ end
+
+ describe '#backoff!' do
+ context 'when we have not backed off before' do
+ it 'does not disable the hook' do
+ expect { hook.backoff! }.not_to change { hook.executable? }.from(true)
+ end
+ end
+
+ context 'when we have exhausted the grace period' do
+ before do
+ hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD)
+ end
+
+ it 'does not disable the hook' do
+ expect { hook.backoff! }.not_to change { hook.executable? }.from(true)
+ end
+ end
+ end
+
+ describe '#disable!' do
+ it 'does not disable a group hook' do
+ expect { hook.disable! }.not_to change { hook.executable? }.from(true)
+ end
+ end
+
+ describe '#temporarily_disabled?' do
+ it 'is false' do
+ # Initially
+ expect(hook).not_to be_temporarily_disabled
+
+ # Backing off
+ WebHook::FAILURE_THRESHOLD.times do
+ hook.backoff!
+ expect(hook).not_to be_temporarily_disabled
+ end
+
+ hook.backoff!
+ expect(hook).not_to be_temporarily_disabled
+ end
+ end
+
+ describe '#permanently_disabled?' do
+ it 'is false' do
+ # Initially
+ expect(hook).not_to be_permanently_disabled
+
+ hook.disable!
+
+ expect(hook).not_to be_permanently_disabled
+ end
+ end
+
+ describe '#alert_status' do
+ subject(:status) { hook.alert_status }
+
+ it { is_expected.to eq :executable }
+
+ context 'when hook has been disabled' do
+ before do
+ hook.disable!
+ end
+
+ it { is_expected.to eq :executable }
+ end
+
+ context 'when hook has been backed off' do
+ before do
+ hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1)
+ hook.disabled_until = 1.hour.from_now
+ end
+
+ it { is_expected.to eq :executable }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb b/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb
new file mode 100644
index 00000000000..cd6eb8c77fa
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/web_hooks/has_web_hooks_shared_examples.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'something that has web-hooks' do
+ describe '#any_hook_failed?', :clean_gitlab_redis_shared_state do
+ subject { object.any_hook_failed? }
+
+ context 'when there are no hooks' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when there are hooks' do
+ before do
+ create_hook
+ create_hook
+ end
+
+ it { is_expected.to eq(false) }
+
+ context 'when there is a failed hook' do
+ before do
+ hook = create_hook
+ hook.update!(recent_failures: WebHook::FAILURE_THRESHOLD + 1)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+ end
+ end
+
+ describe '#cache_web_hook_failure', :clean_gitlab_redis_shared_state do
+ context 'when no value is passed' do
+ it 'stores the return value of #any_hook_failed? and passes it back' do
+ allow(object).to receive(:any_hook_failed?).and_return(true)
+
+ Gitlab::Redis::SharedState.with do |r|
+ expect(r).to receive(:set)
+ .with(object.web_hook_failure_redis_key, 'true', ex: 1.hour)
+ .and_call_original
+ end
+
+ expect(object.cache_web_hook_failure).to eq(true)
+ end
+ end
+
+ context 'when a value is passed' do
+ it 'stores the value and passes it back' do
+ expect(object).not_to receive(:any_hook_failed?)
+
+ Gitlab::Redis::SharedState.with do |r|
+ expect(r).to receive(:set)
+ .with(object.web_hook_failure_redis_key, 'foo', ex: 1.hour)
+ .and_call_original
+ end
+
+ expect(object.cache_web_hook_failure(:foo)).to eq(:foo)
+ end
+ end
+ end
+
+ describe '#get_web_hook_failure', :clean_gitlab_redis_shared_state do
+ subject { object.get_web_hook_failure }
+
+ context 'when no value is stored' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'when stored as true' do
+ before do
+ object.cache_web_hook_failure(true)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when stored as false' do
+ before do
+ object.cache_web_hook_failure(false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '#fetch_web_hook_failure', :clean_gitlab_redis_shared_state do
+ context 'when a value has not been stored' do
+ it 'does not call #any_hook_failed?' do
+ expect(object.get_web_hook_failure).to be_nil
+ expect(object).to receive(:any_hook_failed?).and_return(true)
+
+ expect(object.fetch_web_hook_failure).to eq(true)
+ expect(object.get_web_hook_failure).to eq(true)
+ end
+ end
+
+ context 'when a value has been stored' do
+ before do
+ object.cache_web_hook_failure(true)
+ end
+
+ it 'does not call #any_hook_failed?' do
+ expect(object).not_to receive(:any_hook_failed?)
+
+ expect(object.fetch_web_hook_failure).to eq(true)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/exportable_shared_examples.rb b/spec/support/shared_examples/models/exportable_shared_examples.rb
new file mode 100644
index 00000000000..37c3e68fd5f
--- /dev/null
+++ b/spec/support/shared_examples/models/exportable_shared_examples.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'resource with exportable associations' do
+ before do
+ stub_licensed_features(stubbed_features) if stubbed_features.any?
+ end
+
+ describe '#exportable_association?' do
+ let(:association) { single_association }
+
+ subject { resource.exportable_association?(association, current_user: user) }
+
+ it { is_expected.to be_falsey }
+
+ context 'when user can read resource' do
+ before do
+ group.add_developer(user)
+ end
+
+ it { is_expected.to be_falsey }
+
+ context "when user can read resource's association" do
+ before do
+ other_group.add_developer(user)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'for an unknown association' do
+ let(:association) { :foo }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'for an unauthenticated user' do
+ let(:user) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+ end
+
+ describe '#readable_records' do
+ subject { resource.readable_records(association, current_user: user) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ context 'when association not supported' do
+ let(:association) { :foo }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when association is `:notes`' do
+ let(:association) { :notes }
+
+ it { is_expected.to match_array([readable_note]) }
+
+ context 'when user have access' do
+ before do
+ other_group.add_developer(user)
+ end
+
+ it 'returns all records' do
+ is_expected.to match_array([readable_note, restricted_note])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb
index 3d7d97bbeae..ac4ad4525aa 100644
--- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb
+++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze|
- let_it_be(:distribution_with_suite, freeze: can_freeze) { create(factory, suite: 'mysuite') }
+ let_it_be(:distribution_with_suite, freeze: can_freeze) { create(factory, :with_suite) }
let_it_be(:distribution_with_same_container, freeze: can_freeze) { create(factory, container: distribution_with_suite.container ) }
let_it_be(:distribution_with_same_codename, freeze: can_freeze) { create(factory, codename: distribution_with_suite.codename ) }
let_it_be(:distribution_with_same_suite, freeze: can_freeze) { create(factory, suite: distribution_with_suite.suite ) }
diff --git a/spec/support/shared_examples/models/resource_event_shared_examples.rb b/spec/support/shared_examples/models/resource_event_shared_examples.rb
index 8cab2de076d..038ff33c68a 100644
--- a/spec/support/shared_examples/models/resource_event_shared_examples.rb
+++ b/spec/support/shared_examples/models/resource_event_shared_examples.rb
@@ -160,6 +160,16 @@ RSpec.shared_examples 'a resource event for merge requests' do
end
end
end
+
+ context 'on callbacks' do
+ it 'does not trigger note created subscription' do
+ event = build(described_class.name.underscore.to_sym, merge_request: merge_request1)
+
+ expect(GraphqlTriggers).not_to receive(:work_item_note_created)
+ expect(event).not_to receive(:trigger_note_subscription_create)
+ event.save!
+ end
+ end
end
RSpec.shared_examples 'a note for work item resource event' do
@@ -172,4 +182,14 @@ RSpec.shared_examples 'a note for work item resource event' do
expect(event.work_item_synthetic_system_note.class.name).to eq(event.synthetic_note_class.name)
end
+
+ context 'on callbacks' do
+ it 'triggers note created subscription' do
+ event = build(described_class.name.underscore.to_sym, issue: work_item)
+
+ expect(GraphqlTriggers).to receive(:work_item_note_created)
+ expect(event).to receive(:trigger_note_subscription_create).and_call_original
+ event.save!
+ end
+ end
end