diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 16:49:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 16:49:51 +0300 |
commit | 71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e (patch) | |
tree | 6a2d93ef3fb2d353bb7739e4b57e6541f51cdd71 /spec/support/shared_examples/models | |
parent | a7253423e3403b8c08f8a161e5937e1488f5f407 (diff) |
Add latest changes from gitlab-org/gitlab@15-9-stable-eev15.9.0-rc42
Diffstat (limited to 'spec/support/shared_examples/models')
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 |