summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support/shared_examples')
-rw-r--r--spec/support/shared_examples/chat_slash_commands_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/ci/pipeline_email_examples.rb20
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/common_system_notes_examples.rb27
-rw-r--r--spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb69
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/cycle_analytics_event_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/cycle_analytics_stage_shared_examples.rb (renamed from spec/support/shared_examples/cycle_analytics_stage_examples.rb)37
-rw-r--r--spec/support/shared_examples/diff_file_collections.rb8
-rw-r--r--spec/support/shared_examples/evidence_updated_exposed_fields.rb29
-rw-r--r--spec/support/shared_examples/lfs_http_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/lib/gitlab/ci/build/rules/rule/clause/clause_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb19
-rw-r--r--spec/support/shared_examples/models/cluster_application_version_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/clusters/providers/provider_status.rb73
-rw-r--r--spec/support/shared_examples/models/concern/issuable_shared_examples.rb58
-rw-r--r--spec/support/shared_examples/models/with_uploads_shared_examples.rb3
-rw-r--r--spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/relative_positioning_shared_examples.rb36
-rw-r--r--spec/support/shared_examples/repo_type_shared_examples.rb31
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/services/boards/boards_create_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/boards_list_service.rb4
-rw-r--r--spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb74
-rw-r--r--spec/support/shared_examples/snippet_visibility_shared_examples.rb26
-rw-r--r--spec/support/shared_examples/trackable_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/updating_mentions_shared_examples.rb80
-rw-r--r--spec/support/shared_examples/versioned_description_shared_examples.rb65
29 files changed, 823 insertions, 70 deletions
diff --git a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
index a99068ab678..370f2072705 100644
--- a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
@@ -94,16 +94,32 @@ RSpec.shared_examples 'chat slash commands service' do
subject.trigger(params)
end
+ shared_examples_for 'blocks command execution' do
+ it do
+ expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute)
+
+ result = subject.trigger(params)
+ expect(result[:text]).to match(error_message)
+ end
+ end
+
context 'when user is blocked' do
before do
chat_name.user.block
end
- it 'blocks command execution' do
- expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute)
+ it_behaves_like 'blocks command execution' do
+ let(:error_message) { 'you do not have access to the GitLab project' }
+ end
+ end
- result = subject.trigger(params)
- expect(result).to include(text: /^You are not allowed/)
+ context 'when user is deactivated' do
+ before do
+ chat_name.user.deactivate
+ end
+
+ it_behaves_like 'blocks command execution' do
+ let(:error_message) { 'your account has been deactivated by your administrator' }
end
end
end
diff --git a/spec/support/shared_examples/ci/pipeline_email_examples.rb b/spec/support/shared_examples/ci/pipeline_email_examples.rb
new file mode 100644
index 00000000000..f72d8af3c65
--- /dev/null
+++ b/spec/support/shared_examples/ci/pipeline_email_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+shared_examples_for 'correct pipeline information for pipelines for merge requests' do
+ context 'when pipeline for merge request' do
+ let(:pipeline) { merge_request.all_pipelines.first }
+
+ let(:merge_request) do
+ create(:merge_request, :with_detached_merge_request_pipeline,
+ source_project: project,
+ target_project: project)
+ end
+
+ it 'renders a source ref of the pipeline' do
+ render
+
+ expect(rendered).to have_content pipeline.source_ref
+ expect(rendered).not_to have_content pipeline.ref
+ end
+ end
+end
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index e2b4b50d41d..441d3f4ccb9 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -423,7 +423,7 @@ shared_examples_for 'trace with disabled live trace feature' do
expect(build.job_artifacts_trace.file.filename).to eq('job.log')
expect(File.exist?(src_path)).to be_falsy
expect(src_checksum)
- .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest)
+ .to eq(described_class.hexdigest(build.job_artifacts_trace.file.path))
expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum)
end
end
@@ -449,7 +449,7 @@ shared_examples_for 'trace with disabled live trace feature' do
expect(build.job_artifacts_trace.file.filename).to eq('job.log')
expect(build.old_trace).to be_nil
expect(src_checksum)
- .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest)
+ .to eq(described_class.hexdigest(build.job_artifacts_trace.file.path))
expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum)
end
end
@@ -787,7 +787,7 @@ shared_examples_for 'trace with enabled live trace feature' do
expect(build.job_artifacts_trace.file.filename).to eq('job.log')
expect(Ci::BuildTraceChunk.where(build: build)).not_to be_exist
expect(src_checksum)
- .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest)
+ .to eq(described_class.hexdigest(build.job_artifacts_trace.file.path))
expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum)
end
end
diff --git a/spec/support/shared_examples/common_system_notes_examples.rb b/spec/support/shared_examples/common_system_notes_examples.rb
index 75f93a32d78..ca79603a022 100644
--- a/spec/support/shared_examples/common_system_notes_examples.rb
+++ b/spec/support/shared_examples/common_system_notes_examples.rb
@@ -27,3 +27,30 @@ shared_examples 'WIP notes creation' do |wip_action|
expect(Note.second.note).to match('changed title')
end
end
+
+shared_examples_for 'a note with overridable created_at' do
+ let(:noteable) { create(:issue, project: project, system_note_timestamp: Time.at(42)) }
+
+ it 'the note has the correct time' do
+ expect(subject.created_at).to eq Time.at(42)
+ end
+end
+
+shared_examples_for 'a system note' do |params|
+ let(:expected_noteable) { noteable }
+ let(:commit_count) { nil }
+
+ it 'has the correct attributes', :aggregate_failures do
+ exclude_project = !params.nil? && params[:exclude_project]
+
+ expect(subject).to be_valid
+ expect(subject).to be_system
+
+ expect(subject.noteable).to eq expected_noteable
+ expect(subject.project).to eq project unless exclude_project
+ expect(subject.author).to eq author
+
+ expect(subject.system_note_metadata.action).to eq(action)
+ expect(subject.system_note_metadata.commit_count).to eq(commit_count)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
index d3cadf2ba7c..5dea17069f9 100644
--- a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
+++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
@@ -25,13 +25,17 @@ shared_examples 'a controller that can serve LFS files' do |options = {}|
context 'when lfs is enabled' do
before do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+ allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
+ allow(controller).to receive(:send_file) { controller.head :ok }
end
- context 'when project has access' do
+ def link_project(project)
+ project.lfs_objects << lfs_object
+ end
+
+ context 'when the project is linked to the LfsObject' do
before do
- project.lfs_objects << lfs_object
- allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
- allow(controller).to receive(:send_file) { controller.head :ok }
+ link_project(project)
end
it 'serves the file' do
@@ -76,13 +80,68 @@ shared_examples 'a controller that can serve LFS files' do |options = {}|
end
end
- context 'when project does not have access' do
+ context 'when project is not linked to the LfsObject' do
it 'does not serve the file' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
+
+ context 'when the project is part of a fork network' do
+ shared_examples 'a controller that correctly serves lfs files within a fork network' do
+ it do
+ expect(fork_network_member).not_to eq(fork_network.root_project)
+ end
+
+ it 'does not serve the file if no members are linked to the LfsObject' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'serves the file when the fork network root is linked to the LfsObject' do
+ link_project(fork_network.root_project)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'serves the file when the fork network member is linked to the LfsObject' do
+ link_project(fork_network_member)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'when the project is the root of the fork network' do
+ let!(:fork_network) { create(:fork_network, root_project: project) }
+ let!(:fork_network_member) { create(:fork_network_member, fork_network: fork_network).project }
+
+ before do
+ project.reload
+ end
+
+ it_behaves_like 'a controller that correctly serves lfs files within a fork network'
+ end
+
+ context 'when the project is a downstream member of the fork network' do
+ let!(:fork_network) { create(:fork_network) }
+ let!(:fork_network_member) do
+ create(:fork_network_member, project: project, fork_network: fork_network)
+ project
+ end
+
+ before do
+ project.reload
+ end
+
+ it_behaves_like 'a controller that correctly serves lfs files within a fork network'
+ end
+ end
end
context 'when lfs is not enabled' do
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 4bc22861d58..c24418b2f90 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -104,7 +104,7 @@ shared_examples 'handle uploads' do
context "when neither the uploader nor the model exists" do
before do
- allow_any_instance_of(Upload).to receive(:build_uploader).and_return(nil)
+ allow_any_instance_of(Upload).to receive(:retrieve_uploader).and_return(nil)
allow(controller).to receive(:find_model).and_return(nil)
end
@@ -338,7 +338,7 @@ shared_examples 'handle uploads authorize' do
it_behaves_like 'a valid response' do
it 'responds with status 200, location of uploads remote store and object details' do
- expect(json_response['TempPath']).to eq(uploader_class.workhorse_local_upload_path)
+ expect(json_response).not_to have_key('TempPath')
expect(json_response['RemoteObject']).to have_key('ID')
expect(json_response['RemoteObject']).to have_key('GetURL')
expect(json_response['RemoteObject']).to have_key('StoreURL')
diff --git a/spec/support/shared_examples/cycle_analytics_event_shared_examples.rb b/spec/support/shared_examples/cycle_analytics_event_shared_examples.rb
new file mode 100644
index 00000000000..dce1dbe1cd1
--- /dev/null
+++ b/spec/support/shared_examples/cycle_analytics_event_shared_examples.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+shared_examples_for 'cycle analytics event' do
+ let(:instance) { described_class.new({}) }
+
+ it { expect(described_class.name).to be_a_kind_of(String) }
+ it { expect(described_class.identifier).to be_a_kind_of(Symbol) }
+ it { expect(instance.object_type.ancestors).to include(ApplicationRecord) }
+ it { expect(instance).to respond_to(:timestamp_projection) }
+
+ describe '#apply_query_customization' do
+ it 'expects an ActiveRecord::Relation object as argument and returns a modified version of it' do
+ input_query = instance.object_type.all
+
+ output_query = instance.apply_query_customization(input_query)
+ expect(output_query).to be_a_kind_of(ActiveRecord::Relation)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/cycle_analytics_stage_examples.rb b/spec/support/shared_examples/cycle_analytics_stage_shared_examples.rb
index 151f5325e84..afa035d039a 100644
--- a/spec/support/shared_examples/cycle_analytics_stage_examples.rb
+++ b/spec/support/shared_examples/cycle_analytics_stage_shared_examples.rb
@@ -46,13 +46,20 @@ shared_examples_for 'cycle analytics stage' do
expect(stage).not_to be_valid
expect(stage.errors.details[:end_event]).to eq([{ error: :not_allowed_for_the_given_start_event }])
end
+
+ context 'disallows default stage names when creating custom stage' do
+ let(:invalid_params) { valid_params.merge(name: Gitlab::Analytics::CycleAnalytics::DefaultStages.names.first, custom: true) }
+ let(:stage) { described_class.new(invalid_params) }
+
+ it { expect(stage).not_to be_valid }
+ end
end
- describe '#subject_model' do
+ describe '#subject_class' do
it 'infers the model from the start event' do
stage = described_class.new(valid_params)
- expect(stage.subject_model).to eq(MergeRequest)
+ expect(stage.subject_class).to eq(MergeRequest)
end
end
@@ -71,4 +78,30 @@ shared_examples_for 'cycle analytics stage' do
expect(stage.end_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged)
end
end
+
+ describe '#matches_with_stage_params?' do
+ let(:params) { Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_test_stage }
+
+ it 'matches with default stage params' do
+ stage = described_class.new(params)
+
+ expect(stage).to be_default_stage
+ expect(stage).to be_matches_with_stage_params(params)
+ end
+
+ it "mismatches when the stage is custom" do
+ stage = described_class.new(params.merge(custom: true))
+
+ expect(stage).not_to be_default_stage
+ expect(stage).not_to be_matches_with_stage_params(params)
+ end
+ end
+
+ describe '#parent_id' do
+ it "delegates to 'parent_name'_id attribute" do
+ stage = described_class.new(parent: parent)
+
+ expect(stage.parent_id).to eq(parent.id)
+ end
+ end
end
diff --git a/spec/support/shared_examples/diff_file_collections.rb b/spec/support/shared_examples/diff_file_collections.rb
index 367ddf06c28..4c64abd2a97 100644
--- a/spec/support/shared_examples/diff_file_collections.rb
+++ b/spec/support/shared_examples/diff_file_collections.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
shared_examples 'diff statistics' do |test_include_stats_flag: true|
+ subject { described_class.new(diffable, collection_default_args) }
+
def stub_stats_find_by_path(path, stats_mock)
expect_next_instance_of(Gitlab::Git::DiffStatsCollection) do |collection|
allow(collection).to receive(:find_by_path).and_call_original
@@ -10,8 +12,6 @@ shared_examples 'diff statistics' do |test_include_stats_flag: true|
context 'when should request diff stats' do
it 'Repository#diff_stats is called' do
- subject = described_class.new(diffable, collection_default_args)
-
expect(diffable.project.repository)
.to receive(:diff_stats)
.with(diffable.diff_refs.base_sha, diffable.diff_refs.head_sha)
@@ -21,8 +21,6 @@ shared_examples 'diff statistics' do |test_include_stats_flag: true|
end
it 'Gitlab::Diff::File is initialized with diff stats' do
- subject = described_class.new(diffable, collection_default_args)
-
stats_mock = double(Gitaly::DiffStats, path: '.gitignore', additions: 758, deletions: 120)
stub_stats_find_by_path(stub_path, stats_mock)
@@ -37,8 +35,6 @@ shared_examples 'diff statistics' do |test_include_stats_flag: true|
it 'Repository#diff_stats is not called' do
collection_default_args[:diff_options][:include_stats] = false
- subject = described_class.new(diffable, collection_default_args)
-
expect(diffable.project.repository).not_to receive(:diff_stats)
subject.diff_files
diff --git a/spec/support/shared_examples/evidence_updated_exposed_fields.rb b/spec/support/shared_examples/evidence_updated_exposed_fields.rb
new file mode 100644
index 00000000000..2a02fdd7666
--- /dev/null
+++ b/spec/support/shared_examples/evidence_updated_exposed_fields.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+shared_examples 'updated exposed field' do
+ it 'creates another Evidence object' do
+ model.send("#{updated_field}=", updated_value)
+
+ expect(model.evidence_summary_keys).to include(updated_field)
+ expect { model.save! }.to change(Evidence, :count).by(1)
+ expect(updated_json_field).to eq(updated_value)
+ end
+end
+
+shared_examples 'updated non-exposed field' do
+ it 'does not create any Evidence object' do
+ model.send("#{updated_field}=", updated_value)
+
+ expect(model.evidence_summary_keys).not_to include(updated_field)
+ expect { model.save! }.not_to change(Evidence, :count)
+ end
+end
+
+shared_examples 'updated field on non-linked entity' do
+ it 'does not create any Evidence object' do
+ model.send("#{updated_field}=", updated_value)
+
+ expect(model.evidence_summary_keys).to be_empty
+ expect { model.save! }.not_to change(Evidence, :count)
+ end
+end
diff --git a/spec/support/shared_examples/lfs_http_shared_examples.rb b/spec/support/shared_examples/lfs_http_shared_examples.rb
new file mode 100644
index 00000000000..bcd30fe9654
--- /dev/null
+++ b/spec/support/shared_examples/lfs_http_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+shared_examples 'LFS http 200 response' do
+ it_behaves_like 'LFS http expected response code and message' do
+ let(:response_code) { 200 }
+ end
+end
+
+shared_examples 'LFS http 401 response' do
+ it_behaves_like 'LFS http expected response code and message' do
+ let(:response_code) { 401 }
+ end
+end
+
+shared_examples 'LFS http 403 response' do
+ it_behaves_like 'LFS http expected response code and message' do
+ let(:response_code) { 403 }
+ let(:message) { 'Access forbidden. Check your access level.' }
+ end
+end
+
+shared_examples 'LFS http 501 response' do
+ it_behaves_like 'LFS http expected response code and message' do
+ let(:response_code) { 501 }
+ let(:message) { 'Git LFS is not enabled on this GitLab server, contact your admin.' }
+ end
+end
+
+shared_examples 'LFS http 404 response' do
+ it_behaves_like 'LFS http expected response code and message' do
+ let(:response_code) { 404 }
+ end
+end
+
+shared_examples 'LFS http expected response code and message' do
+ let(:response_code) { }
+ let(:message) { }
+
+ it 'responds with the expected response code and message' do
+ expect(response).to have_gitlab_http_status(response_code)
+ expect(json_response['message']).to eq(message) if message
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/ci/build/rules/rule/clause/clause_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/build/rules/rule/clause/clause_shared_examples.rb
new file mode 100644
index 00000000000..1934fd584f3
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/ci/build/rules/rule/clause/clause_shared_examples.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a glob matching rule' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:case_name, :globs, :files, :satisfied) do
+ 'exact top-level match' | ['Dockerfile'] | { 'Dockerfile' => '', 'Gemfile' => '' } | true
+ 'exact top-level no match' | ['Dockerfile'] | { 'Gemfile' => '' } | false
+ 'pattern top-level match' | ['Docker*'] | { 'Dockerfile' => '', 'Gemfile' => '' } | true
+ 'pattern top-level no match' | ['Docker*'] | { 'Gemfile' => '' } | false
+ 'exact nested match' | ['project/build.properties'] | { 'project/build.properties' => '' } | true
+ 'exact nested no match' | ['project/build.properties'] | { 'project/README.md' => '' } | false
+ 'pattern nested match' | ['src/**/*.go'] | { 'src/gitlab.com/goproject/goproject.go' => '' } | true
+ 'pattern nested no match' | ['src/**/*.go'] | { 'src/gitlab.com/goproject/README.md' => '' } | false
+ 'ext top-level match' | ['*.go'] | { 'main.go' => '', 'cmd/goproject/main.go' => '' } | true
+ 'ext nested no match' | ['*.go'] | { 'cmd/goproject/main.go' => '' } | false
+ 'ext slash no match' | ['/*.go'] | { 'main.go' => '', 'cmd/goproject/main.go' => '' } | false
+ end
+
+ with_them do
+ it { is_expected.to eq(satisfied) }
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
new file mode 100644
index 00000000000..f26a8554055
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/import_export/project_tree_restorer_shared_examples.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# Shared examples for ProjectTreeRestorer (shared to allow the testing
+# of EE-specific features)
+RSpec.shared_examples 'restores project correctly' do |**results|
+ it 'restores the project' do
+ expect(shared.errors).to be_empty
+ expect(restored_project_json).to be_truthy
+ end
+
+ it 'has labels' do
+ labels_size = results.fetch(:labels, 0)
+
+ expect(project.labels.size).to eq(labels_size)
+ end
+
+ it 'has label priorities' do
+ label_with_priorities = results[:label_with_priorities]
+
+ if label_with_priorities
+ expect(project.labels.find_by(title: label_with_priorities).priorities).not_to be_empty
+ end
+ end
+
+ it 'has milestones' do
+ expect(project.milestones.size).to eq(results.fetch(:milestones, 0))
+ end
+
+ it 'has issues' do
+ expect(project.issues.size).to eq(results.fetch(:issues, 0))
+ end
+
+ it 'does not set params that are excluded from import_export settings' do
+ expect(project.import_type).to be_nil
+ expect(project.creator_id).not_to eq 123
+ end
+end
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 5341aacb445..a6653f89377 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -11,6 +11,20 @@ shared_examples 'cluster application status specs' do |application_name|
end
end
+ describe '#status_states' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ subject { described_class.new(cluster: cluster) }
+
+ it 'returns a hash of state values' do
+ expect(subject.status_states).to include(:installed)
+ end
+
+ it 'returns an integer for installed state value' do
+ expect(subject.status_states[:installed]).to eq(3)
+ end
+ end
+
describe '.available' do
subject { described_class.available }
@@ -61,7 +75,7 @@ shared_examples 'cluster application status specs' do |application_name|
subject.reload
- expect(subject.version).to eq(subject.class.const_get(:VERSION))
+ expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
end
context 'application is updating' do
@@ -90,13 +104,14 @@ shared_examples 'cluster application status specs' do |application_name|
subject.reload
- expect(subject.version).to eq(subject.class.const_get(:VERSION))
+ expect(subject.version).to eq(subject.class.const_get(:VERSION, false))
end
end
end
describe '#make_errored' do
subject { create(application_name, :installing) }
+
let(:reason) { 'some errors' }
it 'is errored' do
diff --git a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
index 181b102e685..ba02da41b53 100644
--- a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
@@ -12,7 +12,7 @@ shared_examples 'cluster application version specs' do |application_name|
context 'version is the same as VERSION' do
let(:application) { build(application_name) }
- let(:version) { application.class.const_get(:VERSION) }
+ let(:version) { application.class.const_get(:VERSION, false) }
it { is_expected.to be_falsey }
end
diff --git a/spec/support/shared_examples/models/clusters/providers/provider_status.rb b/spec/support/shared_examples/models/clusters/providers/provider_status.rb
new file mode 100644
index 00000000000..63cb9a56f5b
--- /dev/null
+++ b/spec/support/shared_examples/models/clusters/providers/provider_status.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+shared_examples 'provider status' do |factory|
+ describe 'state_machine' do
+ context 'when any => [:created]' do
+ let(:provider) { build(factory, :creating) }
+
+ it 'nullifies API credentials' do
+ expect(provider).to receive(:nullify_credentials).and_call_original
+ provider.make_created
+
+ expect(provider).to be_created
+ end
+ end
+
+ context 'when any => [:creating]' do
+ let(:provider) { build(factory) }
+ let(:operation_id) { 'operation-xxx' }
+
+ it 'calls #assign_operation_id on the provider' do
+ expect(provider).to receive(:assign_operation_id).with(operation_id).and_call_original
+
+ provider.make_creating(operation_id)
+ end
+ end
+
+ context 'when any => [:errored]' do
+ let(:provider) { build(factory, :creating) }
+ let(:status_reason) { 'err msg' }
+
+ it 'calls #nullify_credentials on the provider' do
+ expect(provider).to receive(:nullify_credentials).and_call_original
+
+ provider.make_errored(status_reason)
+ end
+
+ it 'sets a status reason' do
+ provider.make_errored(status_reason)
+
+ expect(provider.status_reason).to eq('err msg')
+ end
+
+ context 'when status_reason is nil' do
+ let(:provider) { build(factory, :errored) }
+
+ it 'does not set status_reason' do
+ provider.make_errored(nil)
+
+ expect(provider.status_reason).not_to be_nil
+ end
+ end
+ end
+ end
+
+ describe '#on_creation?' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { provider.on_creation? }
+
+ where(:status, :result) do
+ :scheduled | true
+ :creating | true
+ :created | false
+ :errored | false
+ end
+
+ with_them do
+ let(:provider) { build(factory, status) }
+
+ it { is_expected.to eq result }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
index 9604555c57d..4ebb5e35e0e 100644
--- a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'matches_cross_reference_regex? fails fast' do
it 'fails fast for long strings' do
# took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172823
@@ -6,3 +8,59 @@ shared_examples_for 'matches_cross_reference_regex? fails fast' do
end.not_to raise_error
end
end
+
+shared_examples_for 'validates description length with custom validation' do
+ let(:issuable) { build(:issue, description: 'x' * (::Issuable::DESCRIPTION_LENGTH_MAX + 1)) }
+ let(:context) { :update }
+
+ subject { issuable.validate(context) }
+
+ context 'when Issuable is a new record' do
+ it 'validates the maximum description length' do
+ subject
+ expect(issuable.errors[:description]).to eq(["is too long (maximum is #{::Issuable::DESCRIPTION_LENGTH_MAX} characters)"])
+ end
+
+ context 'on create' do
+ let(:context) { :create }
+
+ it 'does not validate the maximum description length' do
+ allow(issuable).to receive(:description_max_length_for_new_records_is_valid).and_call_original
+
+ subject
+
+ expect(issuable).not_to have_received(:description_max_length_for_new_records_is_valid)
+ end
+ end
+ end
+
+ context 'when Issuable is an existing record' do
+ before do
+ allow(issuable).to receive(:expire_etag_cache) # to skip the expire_etag_cache callback
+
+ issuable.save!(validate: false)
+ end
+
+ it 'does not validate the maximum description length' do
+ subject
+ expect(issuable.errors).not_to have_key(:description)
+ end
+ end
+end
+
+shared_examples_for 'truncates the description to its allowed maximum length on import' do
+ before do
+ allow(issuable).to receive(:importing?).and_return(true)
+ end
+
+ let(:issuable) { build(:issue, description: 'x' * (::Issuable::DESCRIPTION_LENGTH_MAX + 1)) }
+
+ subject { issuable.validate(:create) }
+
+ it 'truncates the description to its allowed maximum length' do
+ subject
+
+ expect(issuable.description).to eq('x' * ::Issuable::DESCRIPTION_LENGTH_MAX)
+ expect(issuable.errors[:description]).to be_empty
+ end
+end
diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
index eb1ade03017..822836c771e 100644
--- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb
+++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
@@ -41,7 +41,8 @@ shared_examples_for 'model with uploads' do |supports_fileuploads|
end
it 'deletes remote files' do
- expect_any_instance_of(Uploads::Fog).to receive(:delete_keys).with(uploads.map(&:path))
+ expected_array = array_including(*uploads.map(&:path))
+ expect_any_instance_of(Uploads::Fog).to receive(:delete_keys).with(expected_array)
model_object.destroy
end
diff --git a/spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb
index cb5460bde23..b4a8e3fca4d 100644
--- a/spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/zoom_quick_actions_shared_examples.rb
@@ -45,14 +45,6 @@ shared_examples 'zoom quick actions' do
expect(page).to have_content('Failed to add a Zoom meeting')
expect(page).not_to have_content(zoom_link)
end
-
- context 'when feature flag disabled' do
- before do
- stub_feature_flags(issue_zoom_integration: false)
- end
-
- include_examples 'skip silently'
- end
end
context 'with Zoom link not at the end of the issue description' do
@@ -92,14 +84,6 @@ shared_examples 'zoom quick actions' do
expect(page).to have_content('Zoom meeting removed')
expect(issue.reload.description).to eq("Text with #{zoom_link}")
end
-
- context 'when feature flag disabled' do
- before do
- stub_feature_flags(issue_zoom_integration: false)
- end
-
- include_examples 'skip silently'
- end
end
context 'with a Zoom link not at the end of the description' do
diff --git a/spec/support/shared_examples/relative_positioning_shared_examples.rb b/spec/support/shared_examples/relative_positioning_shared_examples.rb
index b7382cea93c..99e62ebf422 100644
--- a/spec/support/shared_examples/relative_positioning_shared_examples.rb
+++ b/spec/support/shared_examples/relative_positioning_shared_examples.rb
@@ -84,6 +84,22 @@ RSpec.shared_examples 'a class that supports relative positioning' do
expect(item1.relative_position).to be < item2.relative_position
end
+
+ context 'when there is no space' do
+ let(:item3) { create(factory, default_params) }
+
+ before do
+ item1.update(relative_position: 1000)
+ item2.update(relative_position: 1001)
+ item3.update(relative_position: 1002)
+ end
+
+ it 'moves items correctly' do
+ item3.move_before(item2)
+
+ expect(item3.relative_position).to be_between(item1.reload.relative_position, item2.reload.relative_position).exclusive
+ end
+ end
end
describe '#move_after' do
@@ -94,6 +110,22 @@ RSpec.shared_examples 'a class that supports relative positioning' do
expect(item1.relative_position).to be > item2.relative_position
end
+
+ context 'when there is no space' do
+ let(:item3) { create(factory, default_params) }
+
+ before do
+ item1.update(relative_position: 1000)
+ item2.update(relative_position: 1001)
+ item3.update(relative_position: 1002)
+ end
+
+ it 'moves items correctly' do
+ item1.move_after(item2)
+
+ expect(item1.relative_position).to be_between(item2.reload.relative_position, item3.reload.relative_position).exclusive
+ end
+ end
end
describe '#move_to_end' do
@@ -196,7 +228,7 @@ RSpec.shared_examples 'a class that supports relative positioning' do
new_item.move_between(item1, item2)
- expect(new_item.relative_position).to be_between(item1.relative_position, item2.relative_position)
+ expect(new_item.relative_position).to be_between(item1.relative_position, item2.relative_position).exclusive
end
it 'uses rebalancing if there is no place' do
@@ -208,7 +240,7 @@ RSpec.shared_examples 'a class that supports relative positioning' do
new_item.move_between(item2, item3)
new_item.save!
- expect(new_item.relative_position).to be_between(item2.relative_position, item3.relative_position)
+ expect(new_item.relative_position).to be_between(item2.relative_position, item3.relative_position).exclusive
expect(item1.reload.relative_position).not_to eq(100)
end
diff --git a/spec/support/shared_examples/repo_type_shared_examples.rb b/spec/support/shared_examples/repo_type_shared_examples.rb
new file mode 100644
index 00000000000..dc9e3a73346
--- /dev/null
+++ b/spec/support/shared_examples/repo_type_shared_examples.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+shared_examples 'a repo type' do
+ describe "#identifier_for_subject" do
+ subject { described_class.identifier_for_subject(project) }
+
+ it { is_expected.to eq(expected_identifier) }
+ end
+
+ describe "#fetch_id" do
+ it "finds an id match in the identifier" do
+ expect(described_class.fetch_id(expected_identifier)).to eq(expected_id)
+ end
+
+ it 'does not break on other identifiers' do
+ expect(described_class.fetch_id("wiki-noid")).to eq(nil)
+ end
+ end
+
+ describe "#path_suffix" do
+ subject { described_class.path_suffix }
+
+ it { is_expected.to eq(expected_suffix) }
+ end
+
+ describe "#repository_for" do
+ it "finds the repository for the repo type" do
+ expect(described_class.repository_for(project)).to eq(expected_repository)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
index afc6f59b773..a2e38cfc60b 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -8,6 +8,14 @@
# * period_in_seconds
# * period
shared_examples_for 'rate-limited token-authenticated requests' do
+ let(:throttle_types) do
+ {
+ "throttle_protected_paths" => "throttle_authenticated_protected_paths_api",
+ "throttle_authenticated_api" => "throttle_authenticated_api",
+ "throttle_authenticated_web" => "throttle_authenticated_web"
+ }
+ end
+
before do
# Set low limits
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
@@ -84,7 +92,8 @@ shared_examples_for 'rate-limited token-authenticated requests' do
request_method: 'GET',
path: get_args.first,
user_id: user.id,
- username: user.username
+ username: user.username,
+ throttle_type: throttle_types[throttle_setting_prefix]
}
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
@@ -116,6 +125,13 @@ end
# * period_in_seconds
# * period
shared_examples_for 'rate-limited web authenticated requests' do
+ let(:throttle_types) do
+ {
+ "throttle_protected_paths" => "throttle_authenticated_protected_paths_web",
+ "throttle_authenticated_web" => "throttle_authenticated_web"
+ }
+ end
+
before do
login_as(user)
@@ -196,7 +212,8 @@ shared_examples_for 'rate-limited web authenticated requests' do
request_method: 'GET',
path: '/dashboard/snippets',
user_id: user.id,
- username: user.username
+ username: user.username,
+ throttle_type: throttle_types[throttle_setting_prefix]
}
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
diff --git a/spec/support/shared_examples/services/boards/boards_create_service.rb b/spec/support/shared_examples/services/boards/boards_create_service.rb
index 19818a6091b..7fd69354c2d 100644
--- a/spec/support/shared_examples/services/boards/boards_create_service.rb
+++ b/spec/support/shared_examples/services/boards/boards_create_service.rb
@@ -17,7 +17,7 @@ shared_examples 'boards create service' do
context 'when parent has a board' do
before do
- create(:board, parent: parent)
+ create(:board, resource_parent: parent)
end
it 'does not create a new board' do
diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb
index 566e5050f8e..25dc2e04942 100644
--- a/spec/support/shared_examples/services/boards/boards_list_service.rb
+++ b/spec/support/shared_examples/services/boards/boards_list_service.rb
@@ -15,7 +15,7 @@ shared_examples 'boards list service' do
context 'when parent has a board' do
before do
- create(:board, parent: parent)
+ create(:board, resource_parent: parent)
end
it 'does not create a new board' do
@@ -24,7 +24,7 @@ shared_examples 'boards list service' do
end
it 'returns parent boards' do
- board = create(:board, parent: parent)
+ board = create(:board, resource_parent: parent)
expect(service.execute).to eq [board]
end
diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
index 455f7b117b2..f15128d3e13 100644
--- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f }
-
RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' }
@@ -371,6 +369,48 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
end
end
+ context 'on a protected branch with protected branches defined using wildcards' do
+ before do
+ create(:protected_branch, project: project, name: '*-stable')
+ end
+
+ let(:data) do
+ Gitlab::DataBuilder::Push.build(
+ project: project,
+ user: user,
+ ref: '1-stable'
+ )
+ end
+
+ context 'pushing tags' do
+ let(:data) do
+ Gitlab::DataBuilder::Push.build(
+ project: project,
+ user: user,
+ ref: "#{Gitlab::Git::TAG_REF_PREFIX}test"
+ )
+ end
+
+ it_behaves_like "triggered #{service_name} service", event_type: "push"
+ end
+
+ context 'notification enabled only for default branch' do
+ it_behaves_like "untriggered #{service_name} service", event_type: "push", branches_to_be_notified: "default"
+ end
+
+ context 'notification enabled only for protected branches' do
+ it_behaves_like "triggered #{service_name} service", event_type: "push", branches_to_be_notified: "protected"
+ end
+
+ context 'notification enabled only for default and protected branches' do
+ it_behaves_like "triggered #{service_name} service", event_type: "push", branches_to_be_notified: "default_and_protected"
+ end
+
+ context 'notification enabled for all branches' do
+ it_behaves_like "triggered #{service_name} service", event_type: "push", branches_to_be_notified: "all"
+ end
+ end
+
context 'on a neither protected nor default branch' do
let(:data) do
Gitlab::DataBuilder::Push.build(
@@ -572,6 +612,36 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
end
end
+ context 'on a protected branch with protected branches defined usin wildcards' do
+ before do
+ create(:protected_branch, project: project, name: '*-stable')
+ end
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project, status: :failed,
+ sha: project.commit.sha, ref: '1-stable')
+ end
+
+ let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
+
+ context 'notification enabled only for default branch' do
+ it_behaves_like "untriggered #{service_name} service", event_type: "pipeline", branches_to_be_notified: "default"
+ end
+
+ context 'notification enabled only for protected branches' do
+ it_behaves_like "triggered #{service_name} service", event_type: "pipeline", branches_to_be_notified: "protected"
+ end
+
+ context 'notification enabled only for default and protected branches' do
+ it_behaves_like "triggered #{service_name} service", event_type: "pipeline", branches_to_be_notified: "default_and_protected"
+ end
+
+ context 'notification enabled for all branches' do
+ it_behaves_like "triggered #{service_name} service", event_type: "pipeline", branches_to_be_notified: "all"
+ end
+ end
+
context 'on a neither protected nor default branch' do
let(:pipeline) do
create(:ci_pipeline,
diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
index b5321c6db34..e2089ee623a 100644
--- a/spec/support/shared_examples/snippet_visibility_shared_examples.rb
+++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
@@ -11,13 +11,21 @@ RSpec.shared_examples 'snippet visibility' do
set(:author) { create(:user) }
set(:member) { create(:user) }
set(:external) { create(:user, :external) }
+ set(:non_member) { create(:user) }
+
+ set(:project) do
+ create(:project).tap do |project|
+ project.add_developer(author)
+ project.add_developer(member)
+ end
+ end
context "For project snippets" do
let!(:users) do
{
unauthenticated: nil,
external: external,
- non_member: create(:user),
+ non_member: non_member,
member: member,
author: author
}
@@ -211,14 +219,18 @@ RSpec.shared_examples 'snippet visibility' do
end
with_them do
- let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel.level_value(project_type.to_s)) }
+ let!(:project_visibility) { project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(project_type.to_s)) }
let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, feature_visibility) }
let!(:user) { users[user_type] }
let!(:snippet) { create(:project_snippet, visibility_level: snippet_type, project: project, author: author) }
- let!(:members) do
- project.add_developer(author)
- project.add_developer(member)
- project.add_developer(external) if project.private?
+ let!(:external_member) do
+ member = project.project_member(external)
+
+ if project.private?
+ project.add_developer(external) unless member
+ else
+ member.delete if member
+ end
end
context "For #{params[:project_type]} project and #{params[:user_type]} users" do
@@ -256,7 +268,7 @@ RSpec.shared_examples 'snippet visibility' do
{
unauthenticated: nil,
external: external,
- non_member: create(:user),
+ non_member: non_member,
author: author
}
end
diff --git a/spec/support/shared_examples/trackable_shared_examples.rb b/spec/support/shared_examples/trackable_shared_examples.rb
new file mode 100644
index 00000000000..6ad75a14d6b
--- /dev/null
+++ b/spec/support/shared_examples/trackable_shared_examples.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+shared_examples 'a Trackable Controller' do
+ describe '#track_event' do
+ before do
+ sign_in user
+ end
+
+ context 'with no params' do
+ controller(described_class) do
+ def index
+ track_event
+ head :ok
+ end
+ end
+
+ it 'tracks the action name' do
+ expect(Gitlab::Tracking).to receive(:event).with('AnonymousController', 'index', {})
+ get :index
+ end
+ end
+
+ context 'with params' do
+ controller(described_class) do
+ def index
+ track_event('some_event', category: 'SomeCategory', label: 'errorlabel')
+ head :ok
+ end
+ end
+
+ it 'tracks with the specified param' do
+ expect(Gitlab::Tracking).to receive(:event).with('SomeCategory', 'some_event', label: 'errorlabel')
+ get :index
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/updating_mentions_shared_examples.rb b/spec/support/shared_examples/updating_mentions_shared_examples.rb
index ef385f94cc2..9a8f8012762 100644
--- a/spec/support/shared_examples/updating_mentions_shared_examples.rb
+++ b/spec/support/shared_examples/updating_mentions_shared_examples.rb
@@ -1,11 +1,16 @@
# frozen_string_literal: true
RSpec.shared_examples 'updating mentions' do |service_class|
- let(:mentioned_user) { create(:user) }
- let(:service_class) { service_class }
+ let(:service_class) { service_class }
+ let(:mentioned_user) { create(:user) }
+ let(:group_member1) { create(:user) }
+ let(:group_member2) { create(:user) }
+ let(:external_group) { create(:group, :private) }
before do
project.add_developer(mentioned_user)
+ group.add_developer(group_member1)
+ group.add_developer(group_member2)
end
def update_mentionable(opts)
@@ -16,23 +21,74 @@ RSpec.shared_examples 'updating mentions' do |service_class|
mentionable.reload
end
- context 'in title' do
- before do
- update_mentionable(title: mentioned_user.to_reference)
+ context 'when mentioning a different user' do
+ context 'in title' do
+ before do
+ update_mentionable(title: "For #{mentioned_user.to_reference}")
+ end
+
+ it 'emails only the newly-mentioned user' do
+ should_only_email(mentioned_user)
+ end
+ end
+
+ context 'in description' do
+ before do
+ update_mentionable(description: "For #{mentioned_user.to_reference}")
+ end
+
+ it 'emails only the newly-mentioned user' do
+ should_only_email(mentioned_user)
+ end
+ end
+ end
+
+ context 'when mentioning a user and a group with access to' do
+ shared_examples 'updating attribute with allowed mentions' do |attribute|
+ before do
+ update_mentionable(
+ { attribute => "For #{group.to_reference}, cc: #{mentioned_user.to_reference}" }
+ )
+ end
+
+ it 'emails group members' do
+ should_email(mentioned_user)
+ should_email(group_member1)
+ should_email(group_member2)
+ end
+ end
+
+ context 'when group is public' do
+ it_behaves_like 'updating attribute with allowed mentions', :title
+ it_behaves_like 'updating attribute with allowed mentions', :description
end
- it 'emails only the newly-mentioned user' do
- should_only_email(mentioned_user)
+ context 'when the group is private' do
+ before do
+ group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'updating attribute with allowed mentions', :title
+ it_behaves_like 'updating attribute with allowed mentions', :description
end
end
- context 'in description' do
- before do
- update_mentionable(description: mentioned_user.to_reference)
+ context 'when mentioning a user and a group without access to' do
+ shared_examples 'updating attribute with not allowed mentions' do |attribute|
+ before do
+ update_mentionable(
+ { attribute => "For #{external_group.to_reference}, cc: #{mentioned_user.to_reference}" }
+ )
+ end
+
+ it 'emails mentioned user' do
+ should_only_email(mentioned_user)
+ end
end
- it 'emails only the newly-mentioned user' do
- should_only_email(mentioned_user)
+ context 'when the group is private' do
+ it_behaves_like 'updating attribute with not allowed mentions', :title
+ it_behaves_like 'updating attribute with not allowed mentions', :description
end
end
end
diff --git a/spec/support/shared_examples/versioned_description_shared_examples.rb b/spec/support/shared_examples/versioned_description_shared_examples.rb
new file mode 100644
index 00000000000..59124af19ec
--- /dev/null
+++ b/spec/support/shared_examples/versioned_description_shared_examples.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'versioned description' do
+ describe 'associations' do
+ it { is_expected.to have_many(:description_versions) }
+ end
+
+ describe 'save_description_version' do
+ let(:factory_name) { described_class.name.underscore.to_sym }
+ let!(:model) { create(factory_name, description: 'Original description') }
+
+ context 'when feature is enabled' do
+ before do
+ stub_feature_flags(save_description_versions: true)
+ end
+
+ context 'when description was changed' do
+ before do
+ model.update!(description: 'New description')
+ end
+
+ it 'saves the old and new description for the first update' do
+ expect(model.description_versions.first.description).to eq('Original description')
+ expect(model.description_versions.last.description).to eq('New description')
+ end
+
+ it 'only saves the new description for subsequent updates' do
+ expect { model.update!(description: 'Another description') }.to change { model.description_versions.count }.by(1)
+
+ expect(model.description_versions.last.description).to eq('Another description')
+ end
+
+ it 'sets the new description version to `saved_description_version`' do
+ expect(model.saved_description_version).to eq(model.description_versions.last)
+ end
+
+ it 'clears `saved_description_version` after another save that does not change description' do
+ model.save!
+
+ expect(model.saved_description_version).to be_nil
+ end
+ end
+
+ context 'when description was not changed' do
+ it 'does not save any description version' do
+ expect { model.save! }.not_to change { model.description_versions.count }
+
+ expect(model.saved_description_version).to be_nil
+ end
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(save_description_versions: false)
+ end
+
+ it 'does not save any description version' do
+ expect { model.update!(description: 'New description') }.not_to change { model.description_versions.count }
+
+ expect(model.saved_description_version).to be_nil
+ end
+ end
+ end
+end