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-07-19 17:16:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-19 17:16:28 +0300
commite4384360a16dd9a19d4d2d25d0ef1f2b862ed2a6 (patch)
tree2fcdfa7dcdb9db8f5208b2562f4b4e803d671243 /spec/services
parentffda4e7bcac36987f936b4ba515995a6698698f0 (diff)
Add latest changes from gitlab-org/gitlab@16-2-stable-eev16.2.0-rc42
Diffstat (limited to 'spec/services')
-rw-r--r--spec/services/admin/plan_limits/update_service_spec.rb24
-rw-r--r--spec/services/alert_management/alerts/todo/create_service_spec.rb2
-rw-r--r--spec/services/application_settings/update_service_spec.rb9
-rw-r--r--spec/services/auth/dependency_proxy_authentication_service_spec.rb2
-rw-r--r--spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb171
-rw-r--r--spec/services/auto_merge_service_spec.rb21
-rw-r--r--spec/services/award_emojis/add_service_spec.rb6
-rw-r--r--spec/services/award_emojis/base_service_spec.rb37
-rw-r--r--spec/services/award_emojis/destroy_service_spec.rb6
-rw-r--r--spec/services/boards/issues/list_service_spec.rb10
-rw-r--r--spec/services/boards/lists/list_service_spec.rb6
-rw-r--r--spec/services/ci/create_pipeline_service/rules_spec.rb36
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb4
-rw-r--r--spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb28
-rw-r--r--spec/services/ci/pipeline_processing/test_cases/dag_build_fails_test_skips_rollback_on_failure.yml47
-rw-r--r--spec/services/ci/pipeline_schedules/create_service_spec.rb86
-rw-r--r--spec/services/ci/pipeline_schedules/update_service_spec.rb55
-rw-r--r--spec/services/ci/pipeline_trigger_service_spec.rb4
-rw-r--r--spec/services/ci/process_sync_events_service_spec.rb4
-rw-r--r--spec/services/ci/register_job_service_spec.rb4
-rw-r--r--spec/services/ci/runners/register_runner_service_spec.rb4
-rw-r--r--spec/services/clusters/agent_tokens/create_service_spec.rb15
-rw-r--r--spec/services/clusters/agents/authorize_proxy_user_service_spec.rb16
-rw-r--r--spec/services/clusters/integrations/prometheus_health_check_service_spec.rb115
-rw-r--r--spec/services/design_management/generate_image_versions_service_spec.rb2
-rw-r--r--spec/services/draft_notes/create_service_spec.rb14
-rw-r--r--spec/services/environments/create_service_spec.rb3
-rw-r--r--spec/services/environments/update_service_spec.rb15
-rw-r--r--spec/services/error_tracking/issue_details_service_spec.rb33
-rw-r--r--spec/services/error_tracking/issue_latest_event_service_spec.rb35
-rw-r--r--spec/services/error_tracking/issue_update_service_spec.rb40
-rw-r--r--spec/services/error_tracking/list_issues_service_spec.rb88
-rw-r--r--spec/services/git/base_hooks_service_spec.rb17
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb1
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb1
-rw-r--r--spec/services/groups/participants_service_spec.rb73
-rw-r--r--spec/services/groups/update_service_spec.rb14
-rw-r--r--spec/services/groups/update_shared_runners_service_spec.rb183
-rw-r--r--spec/services/import/github_service_spec.rb44
-rw-r--r--spec/services/import_csv/preprocess_milestones_service_spec.rb83
-rw-r--r--spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb2
-rw-r--r--spec/services/integrations/group_mention_service_spec.rb143
-rw-r--r--spec/services/issuable/discussions_list_service_spec.rb2
-rw-r--r--spec/services/issuable/import_csv/base_service_spec.rb92
-rw-r--r--spec/services/issuable/process_assignees_spec.rb14
-rw-r--r--spec/services/issues/build_service_spec.rb1
-rw-r--r--spec/services/issues/create_service_spec.rb29
-rw-r--r--spec/services/issues/relative_position_rebalancing_service_spec.rb93
-rw-r--r--spec/services/issues/reopen_service_spec.rb6
-rw-r--r--spec/services/members/invite_service_spec.rb12
-rw-r--r--spec/services/merge_requests/after_create_service_spec.rb6
-rw-r--r--spec/services/merge_requests/cleanup_refs_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_orchestration_service_spec.rb18
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb12
-rw-r--r--spec/services/merge_requests/mergeability_check_batch_service_spec.rb46
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb45
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb4
-rw-r--r--spec/services/merge_requests/update_service_spec.rb29
-rw-r--r--spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb2
-rw-r--r--spec/services/milestones/create_service_spec.rb68
-rw-r--r--spec/services/milestones/update_service_spec.rb90
-rw-r--r--spec/services/namespace_settings/update_service_spec.rb16
-rw-r--r--spec/services/notes/post_process_service_spec.rb3
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb4
-rw-r--r--spec/services/notification_service_spec.rb4
-rw-r--r--spec/services/packages/cleanup/execute_policy_service_spec.rb4
-rw-r--r--spec/services/packages/debian/find_or_create_package_service_spec.rb83
-rw-r--r--spec/services/packages/debian/process_changes_service_spec.rb140
-rw-r--r--spec/services/packages/npm/create_metadata_cache_service_spec.rb2
-rw-r--r--spec/services/packages/npm/create_package_service_spec.rb14
-rw-r--r--spec/services/packages/npm/generate_metadata_service_spec.rb6
-rw-r--r--spec/services/packages/nuget/extract_metadata_content_service_spec.rb64
-rw-r--r--spec/services/packages/nuget/extract_metadata_file_service_spec.rb100
-rw-r--r--spec/services/packages/nuget/metadata_extraction_service_spec.rb138
-rw-r--r--spec/services/packages/nuget/update_package_from_metadata_service_spec.rb2
-rw-r--r--spec/services/personal_access_tokens/last_used_service_spec.rb44
-rw-r--r--spec/services/personal_access_tokens/revoke_token_family_service_spec.rb18
-rw-r--r--spec/services/personal_access_tokens/rotate_service_spec.rb7
-rw-r--r--spec/services/projects/create_service_spec.rb30
-rw-r--r--spec/services/projects/destroy_service_spec.rb61
-rw-r--r--spec/services/projects/download_service_spec.rb4
-rw-r--r--spec/services/projects/participants_service_spec.rb16
-rw-r--r--spec/services/projects/update_service_spec.rb2
-rw-r--r--spec/services/prometheus/proxy_variable_substitution_service_spec.rb2
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb25
-rw-r--r--spec/services/resource_access_tokens/create_service_spec.rb16
-rw-r--r--spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb2
-rw-r--r--spec/services/service_desk/custom_emails/create_service_spec.rb185
-rw-r--r--spec/services/service_desk/custom_emails/destroy_service_spec.rb95
-rw-r--r--spec/services/service_desk_settings/update_service_spec.rb14
-rw-r--r--spec/services/service_response_spec.rb13
-rw-r--r--spec/services/snippets/update_service_spec.rb2
-rw-r--r--spec/services/spam/spam_action_service_spec.rb3
-rw-r--r--spec/services/spam/spam_verdict_service_spec.rb61
-rw-r--r--spec/services/system_notes/time_tracking_service_spec.rb12
-rw-r--r--spec/services/test_hooks/project_service_spec.rb21
-rw-r--r--spec/services/todo_service_spec.rb51
-rw-r--r--spec/services/user_project_access_changed_service_spec.rb4
-rw-r--r--spec/services/users/allow_possible_spam_service_spec.rb24
-rw-r--r--spec/services/users/ban_service_spec.rb7
-rw-r--r--spec/services/users/disallow_possible_spam_service_spec.rb34
-rw-r--r--spec/services/web_hook_service_spec.rb11
-rw-r--r--spec/services/webauthn/authenticate_service_spec.rb6
-rw-r--r--spec/services/webauthn/register_service_spec.rb4
-rw-r--r--spec/services/work_items/export_csv_service_spec.rb2
-rw-r--r--spec/services/work_items/update_service_spec.rb36
-rw-r--r--spec/services/work_items/widgets/current_user_todos_service/update_service_spec.rb2
107 files changed, 2424 insertions, 1044 deletions
diff --git a/spec/services/admin/plan_limits/update_service_spec.rb b/spec/services/admin/plan_limits/update_service_spec.rb
index 4a384b98299..718367fadc2 100644
--- a/spec/services/admin/plan_limits/update_service_spec.rb
+++ b/spec/services/admin/plan_limits/update_service_spec.rb
@@ -33,12 +33,10 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do
subject(:update_plan_limits) { described_class.new(params, current_user: user, plan: plan).execute }
context 'when current_user is an admin', :enable_admin_mode do
- context 'when the update is successful' do
- it 'updates all attributes' do
- expect_next_instance_of(described_class) do |instance|
- expect(instance).to receive(:parsed_params).and_call_original
- end
+ context 'when the update is successful', :freeze_time do
+ let(:current_timestamp) { Time.current.utc.to_i }
+ it 'updates all attributes' do
update_plan_limits
params.each do |key, value|
@@ -46,6 +44,22 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do
end
end
+ it 'logs the allowed attributes only' do
+ update_plan_limits
+
+ expect(limits.limits_history).to eq(
+ { "enforcement_limit" =>
+ [{ "user_id" => user.id, "username" => user.username,
+ "timestamp" => current_timestamp, "value" => 15 }],
+ "notification_limit" =>
+ [{ "user_id" => user.id, "username" => user.username,
+ "timestamp" => current_timestamp, "value" => 30 }],
+ "storage_size_limit" =>
+ [{ "user_id" => user.id, "username" => user.username,
+ "timestamp" => current_timestamp, "value" => 90 }] }
+ )
+ end
+
it 'returns success' do
response = update_plan_limits
diff --git a/spec/services/alert_management/alerts/todo/create_service_spec.rb b/spec/services/alert_management/alerts/todo/create_service_spec.rb
index fd81c0893ed..c883466cf25 100644
--- a/spec/services/alert_management/alerts/todo/create_service_spec.rb
+++ b/spec/services/alert_management/alerts/todo/create_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe AlertManagement::Alerts::Todo::CreateService, feature_category: :
let(:current_user) { user }
describe '#execute' do
- subject(:result) { AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute }
+ subject(:result) { described_class.new(alert, current_user).execute }
shared_examples 'permissions error' do
it 'returns an error', :aggregate_failures do
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index 79d4fc67538..a05219a0a49 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -314,6 +314,15 @@ RSpec.describe ApplicationSettings::UpdateService do
end
end
+ context 'when default_branch_protection is updated' do
+ let(:expected) { ::Gitlab::Access::BranchProtection.protected_against_developer_pushes.stringify_keys }
+ let(:params) { { default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE } }
+
+ it "updates default_branch_protection_defaults from the default_branch_protection param" do
+ expect { subject.execute }.to change { application_settings.default_branch_protection_defaults }.from({}).to(expected)
+ end
+ end
+
context 'when protected path settings are passed' do
let(:params) do
{
diff --git a/spec/services/auth/dependency_proxy_authentication_service_spec.rb b/spec/services/auth/dependency_proxy_authentication_service_spec.rb
index 8f92fbe272c..3ef9c8fc96e 100644
--- a/spec/services/auth/dependency_proxy_authentication_service_spec.rb
+++ b/spec/services/auth/dependency_proxy_authentication_service_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :dependency_proxy do
let_it_be(:user) { create(:user) }
- let(:service) { Auth::DependencyProxyAuthenticationService.new(nil, user) }
+ let(:service) { described_class.new(nil, user) }
before do
stub_config(dependency_proxy: { enabled: true })
diff --git a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
index a0b22267960..79c931990bb 100644
--- a/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb
@@ -3,30 +3,7 @@
require 'spec_helper'
RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService, feature_category: :code_review_workflow do
- let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project, :repository) }
-
- let(:mr_merge_if_green_enabled) do
- create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user,
- source_branch: "master", target_branch: 'feature',
- source_project: project, target_project: project, state: "opened")
- end
-
- let(:pipeline) do
- create(:ci_pipeline, ref: mr_merge_if_green_enabled.source_branch, project: project)
- end
-
- let(:service) do
- described_class.new(project, user, commit_message: 'Awesome message')
- end
-
- before_all do
- project.add_maintainer(user)
- end
-
- before do
- allow(MergeWorker).to receive(:with_status).and_return(MergeWorker)
- end
+ include_context 'for auto_merge strategy context'
describe "#available_for?" do
subject { service.available_for?(mr_merge_if_green_enabled) }
@@ -64,152 +41,24 @@ RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService, feature_category: :c
end
describe "#execute" do
- let(:merge_request) do
- create(:merge_request, target_project: project, source_project: project,
- source_branch: "feature", target_branch: 'master')
- end
-
- context 'first time enabling' do
- before do
- allow(merge_request)
- .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
- expect(MailScheduler::NotificationServiceWorker).to receive(:perform_async).with('merge_when_pipeline_succeeds', merge_request, user).once
-
- service.execute(merge_request)
- end
-
- it 'sets the params, merge_user, and flag' do
- expect(merge_request).to be_valid
- expect(merge_request.merge_when_pipeline_succeeds).to be_truthy
- expect(merge_request.merge_params).to include 'commit_message' => 'Awesome message'
- expect(merge_request.merge_user).to be user
- expect(merge_request.auto_merge_strategy).to eq AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
- end
-
- it 'creates a system note' do
- pipeline = build(:ci_pipeline)
- allow(merge_request).to receive(:actual_head_pipeline) { pipeline }
-
- note = merge_request.notes.last
- expect(note.note).to match "enabled an automatic merge when the pipeline for #{pipeline.sha}"
- end
- end
-
- context 'already approved' do
- let(:service) { described_class.new(project, user, should_remove_source_branch: true) }
- let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
-
- before do
- allow(mr_merge_if_green_enabled)
- .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
-
- allow(mr_merge_if_green_enabled).to receive(:mergeable?)
- .and_return(true)
-
- allow(pipeline).to receive(:success?).and_return(true)
- end
-
- it 'updates the merge params' do
- expect(SystemNoteService).not_to receive(:merge_when_pipeline_succeeds)
- expect(MailScheduler::NotificationServiceWorker).not_to receive(:perform_async).with('merge_when_pipeline_succeeds', any_args)
-
- service.execute(mr_merge_if_green_enabled)
- expect(mr_merge_if_green_enabled.merge_params).to have_key('should_remove_source_branch')
+ it_behaves_like 'auto_merge service #execute', 'merge_when_pipeline_succeeds' do
+ let(:auto_merge_strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
+ let(:expected_note) do
+ "enabled an automatic merge when the pipeline for #{pipeline.sha}"
end
end
end
describe "#process" do
- let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch }
- let(:merge_request_head) do
- project.commit(mr_merge_if_green_enabled.source_branch).id
- end
-
- context 'when triggered by pipeline with valid ref and sha' do
- let(:triggering_pipeline) do
- create(:ci_pipeline, project: project, ref: merge_request_ref,
- sha: merge_request_head, status: 'success',
- head_pipeline_of: mr_merge_if_green_enabled)
- end
-
- it "merges all merge requests with merge when the pipeline succeeds enabled" do
- allow(mr_merge_if_green_enabled)
- .to receive_messages(head_pipeline: triggering_pipeline, actual_head_pipeline: triggering_pipeline)
-
- expect(MergeWorker).to receive(:perform_async)
- service.process(mr_merge_if_green_enabled)
- end
- end
-
- context 'when triggered by an old pipeline' do
- let(:old_pipeline) do
- create(:ci_pipeline, project: project, ref: merge_request_ref,
- sha: '1234abcdef', status: 'success')
- end
-
- it 'does not merge request' do
- expect(MergeWorker).not_to receive(:perform_async)
- service.process(mr_merge_if_green_enabled)
- end
- end
-
- context 'when triggered by pipeline from a different branch' do
- let(:unrelated_pipeline) do
- create(:ci_pipeline, project: project, ref: 'feature',
- sha: merge_request_head, status: 'success')
- end
-
- it 'does not merge request' do
- expect(MergeWorker).not_to receive(:perform_async)
- service.process(mr_merge_if_green_enabled)
- end
- end
-
- context 'when pipeline is merge request pipeline' do
- let(:pipeline) do
- create(:ci_pipeline, :success,
- source: :merge_request_event,
- ref: mr_merge_if_green_enabled.merge_ref_path,
- merge_request: mr_merge_if_green_enabled,
- merge_requests_as_head_pipeline: [mr_merge_if_green_enabled])
- end
-
- it 'merges the associated merge request' do
- allow(mr_merge_if_green_enabled)
- .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
-
- expect(MergeWorker).to receive(:perform_async)
- service.process(mr_merge_if_green_enabled)
- end
- end
+ it_behaves_like 'auto_merge service #process'
end
- describe "#cancel" do
- before do
- service.cancel(mr_merge_if_green_enabled)
- end
-
- it "resets all the pipeline succeeds params" do
- expect(mr_merge_if_green_enabled.merge_when_pipeline_succeeds).to be_falsey
- expect(mr_merge_if_green_enabled.merge_params).to eq({})
- expect(mr_merge_if_green_enabled.merge_user).to be nil
- end
-
- it 'posts a system note' do
- note = mr_merge_if_green_enabled.notes.last
- expect(note.note).to include 'canceled the automatic merge'
- end
+ describe '#cancel' do
+ it_behaves_like 'auto_merge service #cancel'
end
- describe "#abort" do
- before do
- service.abort(mr_merge_if_green_enabled, 'an error')
- end
-
- it 'posts a system note' do
- note = mr_merge_if_green_enabled.notes.last
- expect(note.note).to include 'aborted the automatic merge'
- end
+ describe '#abort' do
+ it_behaves_like 'auto_merge service #abort'
end
describe 'pipeline integration' do
diff --git a/spec/services/auto_merge_service_spec.rb b/spec/services/auto_merge_service_spec.rb
index 94f4b414dca..64473884b13 100644
--- a/spec/services/auto_merge_service_spec.rb
+++ b/spec/services/auto_merge_service_spec.rb
@@ -17,10 +17,11 @@ RSpec.describe AutoMergeService, feature_category: :code_review_workflow do
it 'returns all strategies in preference order' do
if Gitlab.ee?
- is_expected.to eq(
- [AutoMergeService::STRATEGY_MERGE_TRAIN,
+ is_expected.to contain_exactly(
+ AutoMergeService::STRATEGY_MERGE_TRAIN,
AutoMergeService::STRATEGY_ADD_TO_MERGE_TRAIN_WHEN_PIPELINE_SUCCEEDS,
- AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS])
+ AutoMergeService::STRATEGY_MERGE_WHEN_CHECKS_PASS,
+ AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else
is_expected.to eq([AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS])
end
@@ -74,11 +75,15 @@ RSpec.describe AutoMergeService, feature_category: :code_review_workflow do
merge_request.update_head_pipeline
end
- it 'returns preferred strategy' do
+ it 'returns preferred strategy', if: Gitlab.ee? do
+ is_expected.to eq('merge_when_checks_pass')
+ end
+
+ it 'returns preferred strategy', unless: Gitlab.ee? do
is_expected.to eq('merge_when_pipeline_succeeds')
end
- context 'when the head piipeline succeeded' do
+ context 'when the head pipeline succeeded' do
let(:pipeline_status) { :success }
it 'returns available strategies' do
@@ -142,7 +147,11 @@ RSpec.describe AutoMergeService, feature_category: :code_review_workflow do
context 'when strategy is not specified' do
let(:strategy) {}
- it 'chooses the most preferred strategy' do
+ it 'chooses the most preferred strategy', if: Gitlab.ee? do
+ is_expected.to eq(:merge_when_checks_pass)
+ end
+
+ it 'chooses the most preferred strategy', unless: Gitlab.ee? do
is_expected.to eq(:merge_when_pipeline_succeeds)
end
end
diff --git a/spec/services/award_emojis/add_service_spec.rb b/spec/services/award_emojis/add_service_spec.rb
index 99dbe6dc606..e90ea284f29 100644
--- a/spec/services/award_emojis/add_service_spec.rb
+++ b/spec/services/award_emojis/add_service_spec.rb
@@ -53,6 +53,12 @@ RSpec.describe AwardEmojis::AddService, feature_category: :team_planning do
expect(award.user).to eq(user)
end
+ it 'executes hooks' do
+ expect(service).to receive(:execute_hooks).with(kind_of(AwardEmoji), 'award')
+
+ service.execute
+ end
+
describe 'marking Todos as done' do
subject { service.execute }
diff --git a/spec/services/award_emojis/base_service_spec.rb b/spec/services/award_emojis/base_service_spec.rb
index f1ee4d1cfb8..0f67c619a48 100644
--- a/spec/services/award_emojis/base_service_spec.rb
+++ b/spec/services/award_emojis/base_service_spec.rb
@@ -3,15 +3,17 @@
require 'spec_helper'
RSpec.describe AwardEmojis::BaseService, feature_category: :team_planning do
- let(:awardable) { build(:note) }
- let(:current_user) { build(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be_with_reload(:awardable) { create(:note, project: project) }
+
+ let(:emoji_name) { 'horse' }
describe '.initialize' do
subject { described_class }
it 'uses same emoji name if not an alias' do
- emoji_name = 'horse'
-
expect(subject.new(awardable, emoji_name, current_user).name).to eq(emoji_name)
end
@@ -22,4 +24,31 @@ RSpec.describe AwardEmojis::BaseService, feature_category: :team_planning do
expect(subject.new(awardable, emoji_alias, current_user).name).to eq(emoji_name)
end
end
+
+ describe '.execute_hooks' do
+ let(:award_emoji) { create(:award_emoji, awardable: awardable) }
+ let(:action) { 'award' }
+
+ subject { described_class.new(awardable, emoji_name, current_user) }
+
+ context 'with no emoji hooks configured' do
+ it 'does not build hook_data' do
+ expect(Gitlab::DataBuilder::Emoji).not_to receive(:build)
+ expect(award_emoji.awardable.project).not_to receive(:execute_hooks)
+
+ subject.execute_hooks(award_emoji, action)
+ end
+ end
+
+ context 'with emoji hooks configured' do
+ it 'builds hook_data and calls execute_hooks for project' do
+ hook_data = {}
+ create(:project_hook, project: project, emoji_events: true)
+ expect(Gitlab::DataBuilder::Emoji).to receive(:build).and_return(hook_data)
+ expect(award_emoji.awardable.project).to receive(:execute_hooks).with(hook_data, :emoji_hooks)
+
+ subject.execute_hooks(award_emoji, action)
+ end
+ end
+ end
end
diff --git a/spec/services/award_emojis/destroy_service_spec.rb b/spec/services/award_emojis/destroy_service_spec.rb
index 109bdbfa986..fbadee87f45 100644
--- a/spec/services/award_emojis/destroy_service_spec.rb
+++ b/spec/services/award_emojis/destroy_service_spec.rb
@@ -85,6 +85,12 @@ RSpec.describe AwardEmojis::DestroyService, feature_category: :team_planning do
expect(result[:award]).to eq(award_from_user)
expect(result[:award]).to be_destroyed
end
+
+ it 'executes hooks' do
+ expect(service).to receive(:execute_hooks).with(award_from_user, 'revoke')
+
+ service.execute
+ end
end
end
end
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index 4089e9e6da0..c9d00f61a90 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -20,10 +20,10 @@ RSpec.describe Boards::Issues::ListService, feature_category: :team_planning do
let_it_be(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
let_it_be(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
- let_it_be(:backlog) { create(:backlog_list, board: board) }
+ let_it_be(:backlog) { board.lists.backlog.first }
let_it_be(:list1) { create(:list, board: board, label: development, position: 0) }
let_it_be(:list2) { create(:list, board: board, label: testing, position: 1) }
- let_it_be(:closed) { create(:closed_list, board: board) }
+ let_it_be(:closed) { board.lists.closed.first }
let_it_be(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) }
let_it_be(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2]) }
@@ -109,10 +109,10 @@ RSpec.describe Boards::Issues::ListService, feature_category: :team_planning do
let(:p2_project1) { create(:label, title: 'P2_project1', project: project1, priority: 2) }
let(:p3_project1) { create(:label, title: 'P3_project1', project: project1, priority: 3) }
- let!(:backlog) { create(:backlog_list, board: board) }
+ let!(:backlog) { board.lists.backlog.first }
let!(:list1) { create(:list, board: board, label: development, position: 0) }
let!(:list2) { create(:list, board: board, label: testing, position: 1) }
- let!(:closed) { create(:closed_list, board: board) }
+ let!(:closed) { board.lists.closed.first }
let!(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) }
let!(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2, p2_project]) }
@@ -145,7 +145,6 @@ RSpec.describe Boards::Issues::ListService, feature_category: :team_planning do
context 'when the group is an ancestor' do
let(:parent) { create(:group) }
let(:group) { create(:group, parent: parent) }
- let!(:backlog) { create(:backlog_list, board: board) }
let(:board) { create(:board, group: parent) }
before do
@@ -161,7 +160,6 @@ RSpec.describe Boards::Issues::ListService, feature_category: :team_planning do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:board) { create(:board, project: project) }
- let_it_be(:backlog) { create(:backlog_list, board: board) }
let(:issue) { create(:issue, project: project, relative_position: nil) }
diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb
index 90b705e05c3..97bd5e13454 100644
--- a/spec/services/boards/lists/list_service_spec.rb
+++ b/spec/services/boards/lists/list_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Boards::Lists::ListService, feature_category: :team_planning do
RSpec.shared_examples 'FOSS lists only' do
context 'when board contains a non FOSS list' do
# This scenario may happen when there used to be an EE license and user downgraded
- let!(:backlog_list) { create_backlog_list(board) }
+ let_it_be(:backlog_list) { board.lists.backlog.first }
let_it_be(:milestone) { create(:milestone, group: group) }
let_it_be(:assignee_list) do
list = build(:list, board: board, user_id: user.id, list_type: List.list_types[:assignee], position: 0)
@@ -59,9 +59,5 @@ RSpec.describe Boards::Lists::ListService, feature_category: :team_planning do
it_behaves_like 'lists list service'
it_behaves_like 'FOSS lists only'
end
-
- def create_backlog_list(board)
- create(:backlog_list, board: board)
- end
end
end
diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb
index 87112137675..a81d1487fab 100644
--- a/spec/services/ci/create_pipeline_service/rules_spec.rb
+++ b/spec/services/ci/create_pipeline_service/rules_spec.rb
@@ -452,42 +452,6 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes
expect(job4.needs).to contain_exactly(an_object_having_attributes(name: 'job3'))
end
end
-
- context 'when the FF introduce_rules_with_needs is disabled' do
- before do
- stub_feature_flags(introduce_rules_with_needs: false)
- end
-
- context 'when the `$var` rule matches' do
- it 'creates a pipeline without overridden needs' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('job1', 'job2', 'job3', 'job4')
-
- expect(job1.needs).to be_empty
- expect(job2.needs).to be_empty
- expect(job3.needs).to be_empty
- expect(job4.needs).to contain_exactly(an_object_having_attributes(name: 'job1'))
- end
- end
-
- context 'when the `$var` rule does not match' do
- let(:initialization_params) { base_initialization_params.merge(variables_attributes: variables_attributes) }
-
- let(:variables_attributes) do
- [{ key: 'var', secret_value: 'SOME_VAR' }]
- end
-
- it 'creates a pipeline without overridden needs' do
- expect(pipeline).to be_persisted
- expect(build_names).to contain_exactly('job1', 'job2', 'job3', 'job4')
-
- expect(job1.needs).to be_empty
- expect(job2.needs).to be_empty
- expect(job3.needs).to be_empty
- expect(job4.needs).to contain_exactly(an_object_having_attributes(name: 'job1'))
- end
- end
- end
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index f75c95c66f9..a28ede89cee 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -2035,7 +2035,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes
expect(pipeline).to be_persisted
expect(pipeline.yaml_errors)
- .to include 'content does not have a valid YAML syntax'
+ .to include 'mapping values are not allowed'
end
end
end
@@ -2172,7 +2172,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes
expect(pipeline).to be_persisted
expect(pipeline.yaml_errors)
- .to include 'content does not have a valid YAML syntax'
+ .to include 'mapping values are not allowed'
end
end
end
diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
index c43f1e5264e..15f2cc0990c 100644
--- a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
+++ b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb
@@ -1094,34 +1094,6 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService, feature_category
process_pipeline
end
end
-
- context 'when FF `ci_reset_skipped_jobs_in_atomic_processing` is disabled' do
- before do
- stub_feature_flags(ci_reset_skipped_jobs_in_atomic_processing: false)
-
- process_pipeline # First pipeline processing
-
- # Change the manual jobs from stopped to alive status
- manual1.enqueue!
- manual2.enqueue!
-
- mock_play_jobs_during_processing([manual1, manual2])
- end
-
- it 'does not run ResetSkippedJobsService' do
- expect(Ci::ResetSkippedJobsService).not_to receive(:new)
-
- process_pipeline
-
- expect(all_builds_names_and_statuses).to eq(statuses_2)
- end
-
- it 'does not log event' do
- expect(Gitlab::AppJsonLogger).not_to receive(:info)
-
- process_pipeline
- end
- end
end
context 'when a bridge job has parallel:matrix config', :sidekiq_inline do
diff --git a/spec/services/ci/pipeline_processing/test_cases/dag_build_fails_test_skips_rollback_on_failure.yml b/spec/services/ci/pipeline_processing/test_cases/dag_build_fails_test_skips_rollback_on_failure.yml
new file mode 100644
index 00000000000..591e66304eb
--- /dev/null
+++ b/spec/services/ci/pipeline_processing/test_cases/dag_build_fails_test_skips_rollback_on_failure.yml
@@ -0,0 +1,47 @@
+config:
+ build:
+ stage: build
+ script: exit 1
+
+ test:
+ stage: test
+ script: exit 0
+
+ deploy:
+ stage: deploy
+ script: exit 0
+ needs: [build, test]
+
+ rollback:
+ stage: deploy
+ script: exit 0
+ when: on_failure
+ needs: [build, test]
+
+init:
+ expect:
+ pipeline: pending
+ stages:
+ build: pending
+ test: created
+ deploy: created
+ jobs:
+ build: pending
+ test: created
+ deploy: created
+ rollback: created
+
+transitions:
+ - event: drop
+ jobs: [build]
+ expect:
+ pipeline: failed
+ stages:
+ build: failed
+ test: skipped
+ deploy: skipped
+ jobs:
+ build: failed
+ test: skipped
+ deploy: skipped
+ rollback: skipped
diff --git a/spec/services/ci/pipeline_schedules/create_service_spec.rb b/spec/services/ci/pipeline_schedules/create_service_spec.rb
new file mode 100644
index 00000000000..a01c71432c3
--- /dev/null
+++ b/spec/services/ci/pipeline_schedules/create_service_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineSchedules::CreateService, feature_category: :continuous_integration do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :repository) }
+
+ before_all do
+ project.add_maintainer(user)
+ project.add_reporter(reporter)
+ end
+
+ describe "execute" do
+ context 'when user does not have permission' do
+ subject(:service) { described_class.new(project, reporter, {}) }
+
+ it 'returns ServiceResponse.error' do
+ result = service.execute
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+
+ error_message = _('The current user is not authorized to create the pipeline schedule')
+ expect(result.message).to match_array([error_message])
+ expect(result.payload.errors).to match_array([error_message])
+ end
+ end
+
+ context 'when user has permission' do
+ let(:params) do
+ {
+ description: 'desc',
+ ref: 'patch-x',
+ active: false,
+ cron: '*/1 * * * *',
+ cron_timezone: 'UTC'
+ }
+ end
+
+ subject(:service) { described_class.new(project, user, params) }
+
+ it 'saves values with passed params' do
+ result = service.execute
+
+ expect(result.payload).to have_attributes(
+ description: 'desc',
+ ref: 'patch-x',
+ active: false,
+ cron: '*/1 * * * *',
+ cron_timezone: 'UTC'
+ )
+ end
+
+ it 'returns ServiceResponse.success' do
+ result = service.execute
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.success?).to be(true)
+ end
+
+ context 'when schedule save fails' do
+ subject(:service) { described_class.new(project, user, {}) }
+
+ before do
+ errors = ActiveModel::Errors.new(project)
+ errors.add(:base, 'An error occurred')
+
+ allow_next_instance_of(Ci::PipelineSchedule) do |instance|
+ allow(instance).to receive(:save).and_return(false)
+ allow(instance).to receive(:errors).and_return(errors)
+ end
+ end
+
+ it 'returns ServiceResponse.error' do
+ result = service.execute
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ expect(result.message).to match_array(['An error occurred'])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/pipeline_schedules/update_service_spec.rb b/spec/services/ci/pipeline_schedules/update_service_spec.rb
index 838f49f6dea..c31a652ed93 100644
--- a/spec/services/ci/pipeline_schedules/update_service_spec.rb
+++ b/spec/services/ci/pipeline_schedules/update_service_spec.rb
@@ -8,9 +8,16 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
+ let_it_be(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable,
+ key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule)
+ end
+
before_all do
project.add_maintainer(user)
project.add_reporter(reporter)
+
+ pipeline_schedule.reload
end
describe "execute" do
@@ -22,7 +29,10 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo
expect(result).to be_a(ServiceResponse)
expect(result.error?).to be(true)
- expect(result.message).to eq(_('The current user is not authorized to update the pipeline schedule'))
+
+ error_message = _('The current user is not authorized to update the pipeline schedule')
+ expect(result.message).to match_array([error_message])
+ expect(pipeline_schedule.errors).to match_array([error_message])
end
end
@@ -32,7 +42,10 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo
description: 'updated_desc',
ref: 'patch-x',
active: false,
- cron: '*/1 * * * *'
+ cron: '*/1 * * * *',
+ variables_attributes: [
+ { id: pipeline_schedule_variable.id, key: 'bar', secret_value: 'barvalue' }
+ ]
}
end
@@ -44,6 +57,42 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo
.and change { pipeline_schedule.ref }.from('master').to('patch-x')
.and change { pipeline_schedule.active }.from(true).to(false)
.and change { pipeline_schedule.cron }.from('0 1 * * *').to('*/1 * * * *')
+ .and change { pipeline_schedule.variables.last.key }.from('foo').to('bar')
+ .and change { pipeline_schedule.variables.last.value }.from('foovalue').to('barvalue')
+ end
+
+ context 'when creating a variable' do
+ let(:params) do
+ {
+ variables_attributes: [
+ { key: 'ABC', secret_value: 'ABC123' }
+ ]
+ }
+ end
+
+ it 'creates the new variable' do
+ expect { service.execute }.to change { Ci::PipelineScheduleVariable.count }.by(1)
+
+ expect(pipeline_schedule.variables.last.key).to eq('ABC')
+ expect(pipeline_schedule.variables.last.value).to eq('ABC123')
+ end
+ end
+
+ context 'when deleting a variable' do
+ let(:params) do
+ {
+ variables_attributes: [
+ {
+ id: pipeline_schedule_variable.id,
+ _destroy: true
+ }
+ ]
+ }
+ end
+
+ it 'deletes the existing variable' do
+ expect { service.execute }.to change { Ci::PipelineScheduleVariable.count }.by(-1)
+ end
end
it 'returns ServiceResponse.success' do
@@ -58,7 +107,7 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo
subject(:service) { described_class.new(pipeline_schedule, user, {}) }
before do
- allow(pipeline_schedule).to receive(:update).and_return(false)
+ allow(pipeline_schedule).to receive(:save).and_return(false)
errors = ActiveModel::Errors.new(pipeline_schedule)
errors.add(:base, 'An error occurred')
diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb
index b6e07e82bb5..fbd1a765351 100644
--- a/spec/services/ci/pipeline_trigger_service_spec.rb
+++ b/spec/services/ci/pipeline_trigger_service_spec.rb
@@ -23,8 +23,8 @@ RSpec.describe Ci::PipelineTriggerService, feature_category: :continuous_integra
shared_examples 'detecting an unprocessable pipeline trigger' do
context 'when the pipeline was not created successfully' do
let(:fail_pipeline) do
- receive(:execute).and_wrap_original do |original, *args|
- response = original.call(*args)
+ receive(:execute).and_wrap_original do |original, *args, **kwargs|
+ response = original.call(*args, **kwargs)
pipeline = response.payload
pipeline.update!(failure_reason: 'unknown_failure')
diff --git a/spec/services/ci/process_sync_events_service_spec.rb b/spec/services/ci/process_sync_events_service_spec.rb
index 84b6d7d96f6..ff9bcd0f8e9 100644
--- a/spec/services/ci/process_sync_events_service_spec.rb
+++ b/spec/services/ci/process_sync_events_service_spec.rb
@@ -145,9 +145,9 @@ RSpec.describe Ci::ProcessSyncEventsService, feature_category: :continuous_integ
end
end
- context 'when the FFs use_traversal_ids and use_traversal_ids_for_ancestors are disabled' do
+ context 'when the use_traversal_ids FF is disabled' do
before do
- stub_feature_flags(use_traversal_ids: false, use_traversal_ids_for_ancestors: false)
+ stub_feature_flags(use_traversal_ids: false)
end
it_behaves_like 'event consuming'
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 6fb61bb3ec5..61fec82c688 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -417,8 +417,8 @@ module Ci
context 'when first build is stalled' do
before do
- allow_any_instance_of(Ci::RegisterJobService).to receive(:assign_runner!).and_call_original
- allow_any_instance_of(Ci::RegisterJobService).to receive(:assign_runner!)
+ allow_any_instance_of(described_class).to receive(:assign_runner!).and_call_original
+ allow_any_instance_of(described_class).to receive(:assign_runner!)
.with(pending_job, anything).and_raise(ActiveRecord::StaleObjectError)
end
diff --git a/spec/services/ci/runners/register_runner_service_spec.rb b/spec/services/ci/runners/register_runner_service_spec.rb
index b5921773364..7252763c13e 100644
--- a/spec/services/ci/runners/register_runner_service_spec.rb
+++ b/spec/services/ci/runners/register_runner_service_spec.rb
@@ -173,7 +173,7 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute', feature_categor
expect(runner).to be_an_instance_of(::Ci::Runner)
expect(runner.persisted?).to be_falsey
expect(runner.errors.messages).to eq(
- 'runner_projects.base': ['Maximum number of ci registered project runners (1) exceeded']
+ runner_projects: ['Maximum number of ci registered project runners (1) exceeded']
)
expect(project.runners.reload.size).to eq(1)
end
@@ -252,7 +252,7 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute', feature_categor
expect(runner).to be_an_instance_of(::Ci::Runner)
expect(runner.persisted?).to be_falsey
expect(runner.errors.messages).to eq(
- 'runner_namespaces.base': ['Maximum number of ci registered group runners (1) exceeded']
+ runner_namespaces: ['Maximum number of ci registered group runners (1) exceeded']
)
expect(group.runners.reload.size).to eq(1)
end
diff --git a/spec/services/clusters/agent_tokens/create_service_spec.rb b/spec/services/clusters/agent_tokens/create_service_spec.rb
index 431d7ce2079..fde39b1099e 100644
--- a/spec/services/clusters/agent_tokens/create_service_spec.rb
+++ b/spec/services/clusters/agent_tokens/create_service_spec.rb
@@ -89,21 +89,6 @@ RSpec.describe Clusters::AgentTokens::CreateService, feature_category: :deployme
expect(subject.status).to eq(:error)
expect(subject.message).to eq('An agent can have only two active tokens at a time')
end
-
- context 'when cluster_agents_limit_tokens_created feature flag is disabled' do
- before do
- stub_feature_flags(cluster_agents_limit_tokens_created: false)
- end
-
- it 'creates a new token' do
- expect { subject }.to change { ::Clusters::AgentToken.count }.by(1)
- end
-
- it 'returns success status', :aggregate_failures do
- expect(subject.status).to eq(:success)
- expect(subject.message).to be_nil
- end
- end
end
end
end
diff --git a/spec/services/clusters/agents/authorize_proxy_user_service_spec.rb b/spec/services/clusters/agents/authorize_proxy_user_service_spec.rb
index 2d6c79c5cb3..b1e28c903f4 100644
--- a/spec/services/clusters/agents/authorize_proxy_user_service_spec.rb
+++ b/spec/services/clusters/agents/authorize_proxy_user_service_spec.rb
@@ -31,6 +31,8 @@ RSpec.describe Clusters::Agents::AuthorizeProxyUserService, feature_category: :d
it 'returns forbidden when user has no access to any project', :aggregate_failures do
expect(service_response).to be_error
expect(service_response.reason).to eq :forbidden
+ expect(service_response.message)
+ .to eq 'You must be a member of `projects` or `groups` under the `user_access` keyword.'
end
context 'when user is member of an authorized group' do
@@ -45,6 +47,8 @@ RSpec.describe Clusters::Agents::AuthorizeProxyUserService, feature_category: :d
deployment_group.add_member(user, :reporter)
expect(service_response).to be_error
expect(service_response.reason).to eq :forbidden
+ expect(service_response.message)
+ .to eq 'You must be a member of `projects` or `groups` under the `user_access` keyword.'
end
end
@@ -60,6 +64,18 @@ RSpec.describe Clusters::Agents::AuthorizeProxyUserService, feature_category: :d
deployment_project.add_member(user, :reporter)
expect(service_response).to be_error
expect(service_response.reason).to eq :forbidden
+ expect(service_response.message)
+ .to eq 'You must be a member of `projects` or `groups` under the `user_access` keyword.'
+ end
+ end
+
+ context 'when config is empty' do
+ let(:user_access_config) { {} }
+
+ it 'returns an error', :aggregate_failures do
+ expect(service_response).to be_error
+ expect(service_response.reason).to eq :forbidden
+ expect(service_response.message).to eq '`user_access` keyword is not found in agent config file.'
end
end
end
diff --git a/spec/services/clusters/integrations/prometheus_health_check_service_spec.rb b/spec/services/clusters/integrations/prometheus_health_check_service_spec.rb
deleted file mode 100644
index 9390d4b368b..00000000000
--- a/spec/services/clusters/integrations/prometheus_health_check_service_spec.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Clusters::Integrations::PrometheusHealthCheckService, '#execute', feature_category: :deployment_management do
- let(:service) { described_class.new(cluster) }
-
- subject { service.execute }
-
- RSpec.shared_examples 'no alert' do
- it 'does not send alert' do
- expect(Projects::Alerting::NotifyService).not_to receive(:new)
-
- subject
- end
- end
-
- RSpec.shared_examples 'sends alert' do
- it 'sends an alert' do
- expect_next_instance_of(Projects::Alerting::NotifyService) do |notify_service|
- expect(notify_service).to receive(:execute).with(integration.token, integration)
- end
-
- subject
- end
- end
-
- RSpec.shared_examples 'correct health stored' do
- it 'stores the correct health of prometheus' do
- subject
-
- expect(prometheus.healthy?).to eq(client_healthy)
- end
- end
-
- context 'when cluster is not project_type' do
- let(:cluster) { create(:cluster, :instance) }
-
- it { expect { subject }.to raise_error(RuntimeError, 'Invalid cluster type. Only project types are allowed.') }
- end
-
- context 'when cluster is project_type' do
- let_it_be(:project) { create(:project) }
- let_it_be(:integration) { create(:alert_management_http_integration, project: project) }
-
- let(:previous_health_status) { :healthy }
- let(:prometheus) { create(:clusters_integrations_prometheus, enabled: prometheus_enabled, health_status: previous_health_status) }
- let(:cluster) { create(:cluster, :project, integration_prometheus: prometheus, projects: [project]) }
-
- context 'when prometheus not enabled' do
- let(:prometheus_enabled) { false }
-
- it { expect(subject).to eq(nil) }
-
- include_examples 'no alert'
- end
-
- context 'when prometheus enabled' do
- let(:prometheus_enabled) { true }
-
- before do
- client = instance_double('Gitlab::PrometheusClient', healthy?: client_healthy)
- expect(prometheus).to receive(:prometheus_client).and_return(client)
- end
-
- context 'when newly unhealthy' do
- let(:previous_health_status) { :healthy }
- let(:client_healthy) { false }
-
- include_examples 'sends alert'
- include_examples 'correct health stored'
- end
-
- context 'when newly healthy' do
- let(:previous_health_status) { :unhealthy }
- let(:client_healthy) { true }
-
- include_examples 'no alert'
- include_examples 'correct health stored'
- end
-
- context 'when continuously unhealthy' do
- let(:previous_health_status) { :unhealthy }
- let(:client_healthy) { false }
-
- include_examples 'no alert'
- include_examples 'correct health stored'
- end
-
- context 'when continuously healthy' do
- let(:previous_health_status) { :healthy }
- let(:client_healthy) { true }
-
- include_examples 'no alert'
- include_examples 'correct health stored'
- end
-
- context 'when first health check and healthy' do
- let(:previous_health_status) { :unknown }
- let(:client_healthy) { true }
-
- include_examples 'no alert'
- include_examples 'correct health stored'
- end
-
- context 'when first health check and not healthy' do
- let(:previous_health_status) { :unknown }
- let(:client_healthy) { false }
-
- include_examples 'sends alert'
- include_examples 'correct health stored'
- end
- end
- end
-end
diff --git a/spec/services/design_management/generate_image_versions_service_spec.rb b/spec/services/design_management/generate_image_versions_service_spec.rb
index 08442f221fa..d02d472f897 100644
--- a/spec/services/design_management/generate_image_versions_service_spec.rb
+++ b/spec/services/design_management/generate_image_versions_service_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe DesignManagement::GenerateImageVersionsService, feature_category:
.from(nil).to(CarrierWave::SanitizedFile)
end
- it 'skips generating image versions if the mime type is not whitelisted' do
+ it 'skips generating image versions if the mime type is not allowlisted' do
stub_const('DesignManagement::DesignV432x230Uploader::MIME_TYPE_ALLOWLIST', [])
described_class.new(version).execute
diff --git a/spec/services/draft_notes/create_service_spec.rb b/spec/services/draft_notes/create_service_spec.rb
index 93731a80dcc..6288579025f 100644
--- a/spec/services/draft_notes/create_service_spec.rb
+++ b/spec/services/draft_notes/create_service_spec.rb
@@ -108,4 +108,18 @@ RSpec.describe DraftNotes::CreateService, feature_category: :code_review_workflo
end
end
end
+
+ context 'when the draft note is invalid' do
+ before do
+ allow_next_instance_of(DraftNote) do |draft|
+ allow(draft).to receive(:valid?).and_return(false)
+ end
+ end
+
+ it 'does not create the note' do
+ draft_note = create_draft(note: 'invalid note')
+
+ expect(draft_note).not_to be_persisted
+ end
+ end
end
diff --git a/spec/services/environments/create_service_spec.rb b/spec/services/environments/create_service_spec.rb
index d7fdfd2a38e..c7d32f9111a 100644
--- a/spec/services/environments/create_service_spec.rb
+++ b/spec/services/environments/create_service_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Environments::CreateService, feature_category: :environment_manag
describe '#execute' do
subject { service.execute }
- let(:params) { { name: 'production', external_url: 'https://gitlab.com', tier: :production } }
+ let(:params) { { name: 'production', external_url: 'https://gitlab.com', tier: :production, kubernetes_namespace: 'default' } }
it 'creates an environment' do
expect { subject }.to change { ::Environment.count }.by(1)
@@ -27,6 +27,7 @@ RSpec.describe Environments::CreateService, feature_category: :environment_manag
expect(response.payload[:environment].name).to eq('production')
expect(response.payload[:environment].external_url).to eq('https://gitlab.com')
expect(response.payload[:environment].tier).to eq('production')
+ expect(response.payload[:environment].kubernetes_namespace).to eq('default')
end
context 'with a cluster agent' do
diff --git a/spec/services/environments/update_service_spec.rb b/spec/services/environments/update_service_spec.rb
index 84220c0930b..808d6340314 100644
--- a/spec/services/environments/update_service_spec.rb
+++ b/spec/services/environments/update_service_spec.rb
@@ -28,6 +28,21 @@ RSpec.describe Environments::UpdateService, feature_category: :environment_manag
expect(response.payload[:environment]).to eq(environment)
end
+ context 'when setting a kubernetes namespace to the environment' do
+ let(:params) { { kubernetes_namespace: 'default' } }
+
+ it 'updates the kubernetes namespace' do
+ expect { subject }.to change { environment.reload.kubernetes_namespace }.to('default')
+ end
+
+ it 'returns successful response' do
+ response = subject
+
+ expect(response).to be_success
+ expect(response.payload[:environment]).to eq(environment)
+ end
+ end
+
context 'when setting a cluster agent to the environment' do
let_it_be(:agent_management_project) { create(:project) }
let_it_be(:cluster_agent) { create(:cluster_agent, project: agent_management_project) }
diff --git a/spec/services/error_tracking/issue_details_service_spec.rb b/spec/services/error_tracking/issue_details_service_spec.rb
index 7ac41ffead6..b1c963e2487 100644
--- a/spec/services/error_tracking/issue_details_service_spec.rb
+++ b/spec/services/error_tracking/issue_details_service_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe ErrorTracking::IssueDetailsService, feature_category: :error_tracking do
include_context 'sentry error tracking context'
- subject { described_class.new(project, user, params) }
+ subject(:service) { described_class.new(project, user, params) }
describe '#execute' do
context 'with authorized user' do
@@ -41,26 +41,41 @@ RSpec.describe ErrorTracking::IssueDetailsService, feature_category: :error_trac
include_examples 'error tracking service http status handling', :issue_details
context 'with integrated error tracking' do
- let_it_be(:error) { create(:error_tracking_error, project: project) }
-
- let(:params) { { issue_id: error.id } }
+ let(:error_repository) { instance_double(Gitlab::ErrorTracking::ErrorRepository) }
+ let(:params) { { issue_id: issue_id } }
before do
error_tracking_setting.update!(integrated: true)
+
+ allow(service).to receive(:error_repository).and_return(error_repository)
end
- it 'returns the error in detailed format' do
- expect(result[:status]).to eq(:success)
- expect(result[:issue].to_json).to eq(error.to_sentry_detailed_error.to_json)
+ context 'when error is found' do
+ let(:error) { build_stubbed(:error_tracking_open_api_error, project_id: project.id) }
+ let(:issue_id) { error.fingerprint }
+
+ before do
+ allow(error_repository).to receive(:find_error).with(issue_id).and_return(error)
+ end
+
+ it 'returns the error in detailed format' do
+ expect(result[:status]).to eq(:success)
+ expect(result[:issue]).to eq(error)
+ end
end
context 'when error does not exist' do
- let(:params) { { issue_id: non_existing_record_id } }
+ let(:issue_id) { non_existing_record_id }
+
+ before do
+ allow(error_repository).to receive(:find_error).with(issue_id)
+ .and_raise(Gitlab::ErrorTracking::ErrorRepository::DatabaseError.new('Error not found'))
+ end
it 'returns the error in detailed format' do
expect(result).to match(
status: :error,
- message: /Couldn't find ErrorTracking::Error/,
+ message: /Error not found/,
http_status: :bad_request
)
end
diff --git a/spec/services/error_tracking/issue_latest_event_service_spec.rb b/spec/services/error_tracking/issue_latest_event_service_spec.rb
index bfde14c7ef1..7e0e8dd56a0 100644
--- a/spec/services/error_tracking/issue_latest_event_service_spec.rb
+++ b/spec/services/error_tracking/issue_latest_event_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe ErrorTracking::IssueLatestEventService, feature_category: :error_
let(:params) { {} }
- subject { described_class.new(project, user, params) }
+ subject(:service) { described_class.new(project, user, params) }
describe '#execute' do
context 'with authorized user' do
@@ -29,27 +29,42 @@ RSpec.describe ErrorTracking::IssueLatestEventService, feature_category: :error_
include_examples 'error tracking service http status handling', :issue_latest_event
context 'with integrated error tracking' do
- let_it_be(:error) { create(:error_tracking_error, project: project) }
- let_it_be(:event) { create(:error_tracking_error_event, error: error) }
-
- let(:params) { { issue_id: error.id } }
+ let(:error_repository) { instance_double(Gitlab::ErrorTracking::ErrorRepository) }
+ let(:params) { { issue_id: issue_id } }
before do
error_tracking_setting.update!(integrated: true)
+
+ allow(service).to receive(:error_repository).and_return(error_repository)
end
- it 'returns the latest event in expected format' do
- expect(result[:status]).to eq(:success)
- expect(result[:latest_event].to_json).to eq(event.to_sentry_error_event.to_json)
+ context 'when error is found' do
+ let(:error) { build_stubbed(:error_tracking_open_api_error, project_id: project.id) }
+ let(:event) { build_stubbed(:error_tracking_open_api_error_event, fingerprint: error.fingerprint) }
+ let(:issue_id) { error.fingerprint }
+
+ before do
+ allow(error_repository).to receive(:last_event_for).with(issue_id).and_return(event)
+ end
+
+ it 'returns the latest event in expected format' do
+ expect(result[:status]).to eq(:success)
+ expect(result[:latest_event]).to eq(event)
+ end
end
context 'when error does not exist' do
- let(:params) { { issue_id: non_existing_record_id } }
+ let(:issue_id) { non_existing_record_id }
+
+ before do
+ allow(error_repository).to receive(:last_event_for).with(issue_id)
+ .and_raise(Gitlab::ErrorTracking::ErrorRepository::DatabaseError.new('Error not found'))
+ end
it 'returns the error in detailed format' do
expect(result).to match(
status: :error,
- message: /Couldn't find ErrorTracking::Error/,
+ message: /Error not found/,
http_status: :bad_request
)
end
diff --git a/spec/services/error_tracking/issue_update_service_spec.rb b/spec/services/error_tracking/issue_update_service_spec.rb
index 4dae6cc2fa0..989ebc86abe 100644
--- a/spec/services/error_tracking/issue_update_service_spec.rb
+++ b/spec/services/error_tracking/issue_update_service_spec.rb
@@ -113,17 +113,45 @@ RSpec.describe ErrorTracking::IssueUpdateService, feature_category: :error_track
include_examples 'error tracking service sentry error handling', :update_issue
context 'with integrated error tracking' do
- let(:error) { create(:error_tracking_error, project: project) }
- let(:arguments) { { issue_id: error.id, status: 'resolved' } }
- let(:update_issue_response) { { updated: true, status: :success, closed_issue_iid: nil } }
+ let(:error_repository) { instance_double(Gitlab::ErrorTracking::ErrorRepository) }
+ let(:error) { build_stubbed(:error_tracking_open_api_error, project_id: project.id) }
+ let(:issue_id) { error.fingerprint }
+ let(:arguments) { { issue_id: issue_id, status: 'resolved' } }
before do
error_tracking_setting.update!(integrated: true)
+
+ allow(update_service).to receive(:error_repository).and_return(error_repository)
+ allow(error_repository).to receive(:update_error)
+ .with(issue_id, status: 'resolved').and_return(updated)
end
- it 'resolves the error and responds with expected format' do
- expect(update_service.execute).to eq(update_issue_response)
- expect(error.reload.status).to eq('resolved')
+ context 'when update succeeded' do
+ let(:updated) { true }
+
+ it 'returns success with updated true' do
+ expect(project.error_tracking_setting).to receive(:expire_issues_cache)
+
+ expect(update_service.execute).to eq(
+ status: :success,
+ updated: true,
+ closed_issue_iid: nil
+ )
+ end
+ end
+
+ context 'when update failed' do
+ let(:updated) { false }
+
+ it 'returns success with updated false' do
+ expect(project.error_tracking_setting).to receive(:expire_issues_cache)
+
+ expect(update_service.execute).to eq(
+ status: :success,
+ updated: false,
+ closed_issue_iid: nil
+ )
+ end
end
end
end
diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb
index 2c35c2b8acd..5a6e9b56f6c 100644
--- a/spec/services/error_tracking/list_issues_service_spec.rb
+++ b/spec/services/error_tracking/list_issues_service_spec.rb
@@ -7,10 +7,10 @@ RSpec.describe ErrorTracking::ListIssuesService, feature_category: :error_tracki
let(:params) { {} }
- subject { described_class.new(project, user, params) }
+ subject(:service) { described_class.new(project, user, params) }
describe '#execute' do
- context 'Sentry backend' do
+ context 'with Sentry backend' do
let(:params) { { search_term: 'something', sort: 'last_seen', cursor: 'some-cursor' } }
let(:list_sentry_issues_args) do
@@ -42,7 +42,7 @@ RSpec.describe ErrorTracking::ListIssuesService, feature_category: :error_tracki
expect(result).to eq(status: :success, pagination: {}, issues: issues)
end
- it 'returns bad request for an issue_status not on the whitelist' do
+ it 'returns bad request with invalid issue_status' do
params[:issue_status] = 'assigned'
expect(error_tracking_setting).not_to receive(:list_sentry_issues)
@@ -65,22 +65,84 @@ RSpec.describe ErrorTracking::ListIssuesService, feature_category: :error_tracki
end
end
- context 'GitLab backend' do
- let_it_be(:error1) { create(:error_tracking_error, name: 'foo', project: project) }
- let_it_be(:error2) { create(:error_tracking_error, name: 'bar', project: project) }
+ context 'with integrated error tracking' do
+ let(:error_repository) { instance_double(Gitlab::ErrorTracking::ErrorRepository) }
+ let(:errors) { [] }
+ let(:pagination) { Gitlab::ErrorTracking::ErrorRepository::Pagination.new(nil, nil) }
+ let(:opts) { default_opts }
- let(:params) { { limit: '1' } }
+ let(:default_opts) do
+ {
+ filters: { status: described_class::DEFAULT_ISSUE_STATUS },
+ query: nil,
+ sort: described_class::DEFAULT_SORT,
+ limit: described_class::DEFAULT_LIMIT,
+ cursor: nil
+ }
+ end
+
+ let(:params) { {} }
before do
error_tracking_setting.update!(integrated: true)
+
+ allow(service).to receive(:error_repository).and_return(error_repository)
end
- it 'returns the error in expected format' do
- expect(result[:status]).to eq(:success)
- expect(result[:issues].size).to eq(1)
- expect(result[:issues].first.to_json).to eq(error2.to_sentry_error.to_json)
- expect(result[:pagination][:next][:cursor]).to be_present
- expect(result[:pagination][:previous]).to be_nil
+ context 'when errors are found' do
+ let(:error) { build_stubbed(:error_tracking_open_api_error, project_id: project.id) }
+ let(:errors) { [error] }
+
+ before do
+ allow(error_repository).to receive(:list_errors)
+ .with(**opts)
+ .and_return([errors, pagination])
+ end
+
+ context 'without params' do
+ it 'returns the errors without pagination' do
+ expect(result[:status]).to eq(:success)
+ expect(result[:issues]).to eq(errors)
+ expect(result[:pagination]).to eq({})
+ expect(error_repository).to have_received(:list_errors).with(**opts)
+ end
+ end
+
+ context 'with pagination' do
+ context 'with next page' do
+ before do
+ pagination.next = 'next cursor'
+ end
+
+ it 'has next cursor' do
+ expect(result[:pagination]).to eq(next: { cursor: 'next cursor' })
+ end
+ end
+
+ context 'with prev page' do
+ before do
+ pagination.prev = 'prev cursor'
+ end
+
+ it 'has prev cursor' do
+ expect(result[:pagination]).to eq(previous: { cursor: 'prev cursor' })
+ end
+ end
+
+ context 'with next and prev page' do
+ before do
+ pagination.next = 'next cursor'
+ pagination.prev = 'prev cursor'
+ end
+
+ it 'has both cursors' do
+ expect(result[:pagination]).to eq(
+ next: { cursor: 'next cursor' },
+ previous: { cursor: 'prev cursor' }
+ )
+ end
+ end
+ end
end
end
end
diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb
index 8a686a19c4c..60883db0cd5 100644
--- a/spec/services/git/base_hooks_service_spec.rb
+++ b/spec/services/git/base_hooks_service_spec.rb
@@ -102,10 +102,25 @@ RSpec.describe Git::BaseHooksService, feature_category: :source_code_management
it 'executes the services' do
expect(subject).to receive(:push_data).at_least(:once).and_call_original
- expect(project).to receive(:execute_integrations)
+ expect(project).to receive(:execute_integrations).with(kind_of(Hash), subject.hook_name, skip_ci: false)
subject.execute
end
+
+ context 'with integrations.skip_ci push option' do
+ before do
+ params[:push_options] = {
+ integrations: { skip_ci: true }
+ }
+ end
+
+ it 'executes the services' do
+ expect(subject).to receive(:push_data).at_least(:once).and_call_original
+ expect(project).to receive(:execute_integrations).with(kind_of(Hash), subject.hook_name, skip_ci: true)
+
+ subject.execute
+ end
+ end
end
context 'with inactive integrations' do
diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb
index f567624068a..3050d6c5eca 100644
--- a/spec/services/git/branch_hooks_service_spec.rb
+++ b/spec/services/git/branch_hooks_service_spec.rb
@@ -29,6 +29,7 @@ RSpec.describe Git::BranchHooksService, :clean_gitlab_redis_shared_state, featur
before: oldrev,
after: newrev,
ref: ref,
+ ref_protected: project.protected_for?(ref),
user_id: user.id,
user_name: user.name,
project_id: project.id
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
index 73f6eff36ba..3e06443126b 100644
--- a/spec/services/git/tag_hooks_service_spec.rb
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -63,6 +63,7 @@ RSpec.describe Git::TagHooksService, :service, feature_category: :source_code_ma
is_expected.to match a_hash_including(
object_kind: 'tag_push',
ref: ref,
+ ref_protected: project.protected_for?(ref),
before: oldrev,
after: newrev,
message: tag.message,
diff --git a/spec/services/groups/participants_service_spec.rb b/spec/services/groups/participants_service_spec.rb
index eee9cfce1b1..0b370ca9fd8 100644
--- a/spec/services/groups/participants_service_spec.rb
+++ b/spec/services/groups/participants_service_spec.rb
@@ -3,25 +3,78 @@
require 'spec_helper'
RSpec.describe Groups::ParticipantsService, feature_category: :groups_and_projects do
- describe '#group_members' do
- let(:user) { create(:user) }
- let(:parent_group) { create(:group) }
- let(:group) { create(:group, parent: parent_group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:subproject) { create(:project, group: subgroup) }
+ describe '#execute' do
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:parent_group) { create(:group) }
+ let_it_be(:group) { create(:group, parent: parent_group) }
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let_it_be(:subproject) { create(:project, group: subgroup) }
+
+ let(:service) { described_class.new(group, developer) }
+
+ subject(:service_result) { service.execute(nil) }
+
+ before_all do
+ parent_group.add_developer(developer)
+ end
+
+ before do
+ stub_feature_flags(disable_all_mention: false)
+ end
+
+ it 'includes `All Group Members`' do
+ group.add_developer(create(:user))
+
+ # These should not be included in the count for the @all entry
+ subgroup.add_developer(create(:user))
+ subproject.add_developer(create(:user))
+
+ expect(service_result).to include(a_hash_including({ username: "all", name: "All Group Members", count: 1 }))
+ end
+
+ context 'when `disable_all_mention` FF is enabled' do
+ before do
+ stub_feature_flags(disable_all_mention: true)
+ end
+
+ it 'does not include `All Group Members`' do
+ expect(service_result).not_to include(a_hash_including({ username: "all", name: "All Group Members" }))
+ end
+ end
it 'returns all members in parent groups, sub-groups, and sub-projects' do
parent_group.add_developer(create(:user))
subgroup.add_developer(create(:user))
subproject.add_developer(create(:user))
- result = described_class.new(group, user).execute(nil)
-
expected_users = (group.self_and_hierarchy.flat_map(&:users) + subproject.users)
.map { |user| user_to_autocompletable(user) }
- expect(expected_users.count).to eq(3)
- expect(result).to include(*expected_users)
+ expect(expected_users.count).to eq(4)
+ expect(service_result).to include(*expected_users)
+ end
+
+ context 'when shared with a private group' do
+ let_it_be(:private_group_member) { create(:user) }
+ let_it_be(:private_group) { create(:group, :private, :nested) }
+
+ before_all do
+ private_group.add_owner(private_group_member)
+
+ create(:group_group_link, shared_group: parent_group, shared_with_group: private_group)
+ end
+
+ subject(:usernames) { service_result.pluck(:username) }
+
+ context 'when current_user is not a member' do
+ let(:service) { described_class.new(group, create(:user)) }
+
+ it { is_expected.not_to include(private_group_member.username) }
+ end
+
+ context 'when current_user is a member' do
+ it { is_expected.to include(private_group_member.username) }
+ end
end
end
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index 2842097199f..3819bcee36d 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -333,17 +333,27 @@ RSpec.describe Groups::UpdateService, feature_category: :groups_and_projects do
described_class.new(internal_group, user, default_branch_protection: Gitlab::Access::PROTECTION_NONE)
end
+ let(:settings) { internal_group.namespace_settings }
+ let(:expected_settings) { Gitlab::Access::BranchProtection.protection_none.stringify_keys }
+
context 'for users who have the ability to update default_branch_protection' do
- it 'updates the attribute' do
+ it 'updates default_branch_protection attribute' do
+ internal_group.add_owner(user)
+
+ expect { service.execute }.to change { internal_group.default_branch_protection }.from(Gitlab::Access::PROTECTION_FULL).to(Gitlab::Access::PROTECTION_NONE)
+ end
+
+ it 'updates default_branch_protection_defaults to match default_branch_protection' do
internal_group.add_owner(user)
- expect { service.execute }.to change { internal_group.default_branch_protection }.to(Gitlab::Access::PROTECTION_NONE)
+ expect { service.execute }.to change { settings.default_branch_protection_defaults }.from({}).to(expected_settings)
end
end
context 'for users who do not have the ability to update default_branch_protection' do
it 'does not update the attribute' do
expect { service.execute }.not_to change { internal_group.default_branch_protection }
+ expect { service.execute }.not_to change { internal_group.namespace_settings.default_branch_protection_defaults }
end
end
end
diff --git a/spec/services/groups/update_shared_runners_service_spec.rb b/spec/services/groups/update_shared_runners_service_spec.rb
index 0acf1ec3d35..00eabb5c875 100644
--- a/spec/services/groups/update_shared_runners_service_spec.rb
+++ b/spec/services/groups/update_shared_runners_service_spec.rb
@@ -3,15 +3,17 @@
require 'spec_helper'
RSpec.describe Groups::UpdateSharedRunnersService, feature_category: :groups_and_projects do
+ include ReloadHelpers
+
let(:user) { create(:user) }
- let(:group) { create(:group) }
let(:params) { {} }
+ let(:service) { described_class.new(group, user, params) }
describe '#execute' do
- subject { described_class.new(group, user, params).execute }
+ subject { service.execute }
context 'when current_user is not the group owner' do
- let_it_be(:group) { create(:group) }
+ let(:group) { create(:group) }
let(:params) { { shared_runners_setting: 'enabled' } }
@@ -19,9 +21,7 @@ RSpec.describe Groups::UpdateSharedRunnersService, feature_category: :groups_and
group.add_maintainer(user)
end
- it 'results error and does not call any method' do
- expect(group).not_to receive(:update_shared_runners_setting!)
-
+ it 'returns error' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to eq('Operation not allowed')
expect(subject[:http_status]).to eq(403)
@@ -36,23 +36,36 @@ RSpec.describe Groups::UpdateSharedRunnersService, feature_category: :groups_and
context 'enable shared Runners' do
let(:params) { { shared_runners_setting: 'enabled' } }
- context 'group that its ancestors have shared runners disabled' do
- let_it_be(:parent) { create(:group, :shared_runners_disabled) }
- let_it_be(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+ context 'when ancestor disable shared runners' do
+ let(:parent) { create(:group, :shared_runners_disabled) }
+ let(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+ let!(:project) { create(:project, shared_runners_enabled: false, group: group) }
- it 'results error' do
- expect(subject[:status]).to eq(:error)
- expect(subject[:message]).to eq('Validation failed: Shared runners enabled cannot be enabled because parent group has shared Runners disabled')
+ it 'returns an error and does not enable shared runners' do
+ expect do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Validation failed: Shared runners enabled cannot be enabled because parent group has shared Runners disabled')
+
+ reload_models(parent, group, project)
+ end.to not_change { parent.shared_runners_enabled }
+ .and not_change { group.shared_runners_enabled }
+ .and not_change { project.shared_runners_enabled }
end
end
- context 'root group with shared runners disabled' do
- let_it_be(:group) { create(:group, :shared_runners_disabled) }
+ context 'when updating root group' do
+ let(:group) { create(:group, :shared_runners_disabled) }
+ let(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let!(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
- it 'receives correct method and succeeds' do
- expect(group).to receive(:update_shared_runners_setting!).with('enabled')
+ it 'enables shared Runners for itself and descendants' do
+ expect do
+ expect(subject[:status]).to eq(:success)
- expect(subject[:status]).to eq(:success)
+ reload_models(group, sub_group, project)
+ end.to change { group.shared_runners_enabled }.from(false).to(true)
+ .and change { sub_group.shared_runners_enabled }.from(false).to(true)
+ .and change { project.shared_runners_enabled }.from(false).to(true)
end
end
@@ -75,7 +88,7 @@ RSpec.describe Groups::UpdateSharedRunnersService, feature_category: :groups_and
let(:params) { { shared_runners_setting: 'invalid_enabled' } }
it 'does not update pending builds for the group' do
- expect(::Ci::UpdatePendingBuildService).not_to receive(:new).and_call_original
+ expect(::Ci::UpdatePendingBuildService).not_to receive(:new)
subject
@@ -87,20 +100,46 @@ RSpec.describe Groups::UpdateSharedRunnersService, feature_category: :groups_and
end
context 'disable shared Runners' do
- let_it_be(:group) { create(:group) }
+ let!(:group) { create(:group) }
+ let!(:sub_group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners, parent: group) }
+ let!(:sub_group2) { create(:group, parent: group) }
+ let!(:project) { create(:project, group: group, shared_runners_enabled: true) }
+ let!(:project2) { create(:project, group: sub_group2, shared_runners_enabled: true) }
let(:params) { { shared_runners_setting: Namespace::SR_DISABLED_AND_UNOVERRIDABLE } }
- it 'receives correct method and succeeds' do
- expect(group).to receive(:update_shared_runners_setting!).with(Namespace::SR_DISABLED_AND_UNOVERRIDABLE)
+ it 'disables shared Runners for all descendant groups and projects' do
+ expect do
+ expect(subject[:status]).to eq(:success)
+
+ reload_models(group, sub_group, sub_group2, project, project2)
+ end.to change { group.shared_runners_enabled }.from(true).to(false)
+ .and not_change { group.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.shared_runners_enabled }
+ .and change { sub_group.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ .and change { sub_group2.shared_runners_enabled }.from(true).to(false)
+ .and not_change { sub_group2.allow_descendants_override_disabled_shared_runners }
+ .and change { project.shared_runners_enabled }.from(true).to(false)
+ .and change { project2.shared_runners_enabled }.from(true).to(false)
+ end
+
+ context 'with override on self' do
+ let(:group) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners) }
+
+ it 'disables it' do
+ expect do
+ expect(subject[:status]).to eq(:success)
- expect(subject[:status]).to eq(:success)
+ group.reload
+ end
+ .to not_change { group.shared_runners_enabled }
+ .and change { group.allow_descendants_override_disabled_shared_runners }.from(true).to(false)
+ end
end
context 'when group has pending builds' do
- let_it_be(:project) { create(:project, namespace: group) }
- let_it_be(:pending_build_1) { create(:ci_pending_build, project: project, instance_runners_enabled: true) }
- let_it_be(:pending_build_2) { create(:ci_pending_build, project: project, instance_runners_enabled: true) }
+ let!(:pending_build_1) { create(:ci_pending_build, project: project, instance_runners_enabled: true) }
+ let!(:pending_build_2) { create(:ci_pending_build, project: project, instance_runners_enabled: true) }
it 'updates pending builds for the group' do
expect(::Ci::UpdatePendingBuildService).to receive(:new).and_call_original
@@ -113,53 +152,91 @@ RSpec.describe Groups::UpdateSharedRunnersService, feature_category: :groups_and
end
end
- context 'allow descendants to override' do
- let(:params) { { shared_runners_setting: Namespace::SR_DISABLED_AND_OVERRIDABLE } }
-
+ shared_examples 'allow descendants to override' do
context 'top level group' do
- let_it_be(:group) { create(:group, :shared_runners_disabled) }
+ let!(:group) { create(:group, :shared_runners_disabled) }
+ let!(:sub_group) { create(:group, :shared_runners_disabled, parent: group) }
+ let!(:project) { create(:project, shared_runners_enabled: false, group: sub_group) }
- it 'receives correct method and succeeds' do
- expect(group).to receive(:update_shared_runners_setting!).with(Namespace::SR_DISABLED_AND_OVERRIDABLE)
+ it 'enables allow descendants to override only for itself' do
+ expect do
+ expect(subject[:status]).to eq(:success)
- expect(subject[:status]).to eq(:success)
+ reload_models(group, sub_group, project)
+ end.to change { group.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
+ .and not_change { group.shared_runners_enabled }
+ .and not_change { sub_group.allow_descendants_override_disabled_shared_runners }
+ .and not_change { sub_group.shared_runners_enabled }
+ .and not_change { project.shared_runners_enabled }
end
end
- context 'when parent does not allow' do
- let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false) }
- let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
+ context 'when ancestor disables shared Runners but allows to override' do
+ let!(:parent) { create(:group, :shared_runners_disabled, :allow_descendants_override_disabled_shared_runners) }
+ let!(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+ let!(:project) { create(:project, shared_runners_enabled: false, group: group) }
- it 'results error' do
- expect(subject[:status]).to eq(:error)
- expect(subject[:message]).to eq('Validation failed: Allow descendants override disabled shared runners cannot be enabled because parent group does not allow it')
+ it 'enables allow descendants to override' do
+ expect do
+ expect(subject[:status]).to eq(:success)
+
+ reload_models(parent, group, project)
+ end
+ .to not_change { parent.allow_descendants_override_disabled_shared_runners }
+ .and not_change { parent.shared_runners_enabled }
+ .and change { group.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
+ .and not_change { group.shared_runners_enabled }
+ .and not_change { project.shared_runners_enabled }
end
end
- context 'when using DISABLED_WITH_OVERRIDE (deprecated)' do
- let(:params) { { shared_runners_setting: Namespace::SR_DISABLED_WITH_OVERRIDE } }
+ context 'when ancestor disables shared runners' do
+ let(:parent) { create(:group, :shared_runners_disabled) }
+ let(:group) { create(:group, :shared_runners_disabled, parent: parent) }
+ let!(:project) { create(:project, shared_runners_enabled: false, group: group) }
- context 'top level group' do
- let_it_be(:group) { create(:group, :shared_runners_disabled) }
-
- it 'receives correct method and succeeds' do
- expect(group).to receive(:update_shared_runners_setting!).with(Namespace::SR_DISABLED_WITH_OVERRIDE)
+ it 'returns an error and does not enable shared runners' do
+ expect do
+ expect(subject[:status]).to eq(:error)
+ expect(subject[:message]).to eq('Validation failed: Allow descendants override disabled shared runners cannot be enabled because parent group does not allow it')
- expect(subject[:status]).to eq(:success)
- end
+ reload_models(parent, group, project)
+ end.to not_change { parent.shared_runners_enabled }
+ .and not_change { group.shared_runners_enabled }
+ .and not_change { project.shared_runners_enabled }
end
+ end
- context 'when parent does not allow' do
- let_it_be(:parent) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false) }
- let_it_be(:group) { create(:group, :shared_runners_disabled, allow_descendants_override_disabled_shared_runners: false, parent: parent) }
+ context 'top level group that has shared Runners enabled' do
+ let!(:group) { create(:group, shared_runners_enabled: true) }
+ let!(:sub_group) { create(:group, shared_runners_enabled: true, parent: group) }
+ let!(:project) { create(:project, shared_runners_enabled: true, group: sub_group) }
- it 'results error' do
- expect(subject[:status]).to eq(:error)
- expect(subject[:message]).to eq('Validation failed: Allow descendants override disabled shared runners cannot be enabled because parent group does not allow it')
+ it 'enables allow descendants to override & disables shared runners everywhere' do
+ expect do
+ expect(subject[:status]).to eq(:success)
+
+ reload_models(group, sub_group, project)
end
+ .to change { group.shared_runners_enabled }.from(true).to(false)
+ .and change { group.allow_descendants_override_disabled_shared_runners }.from(false).to(true)
+ .and change { sub_group.shared_runners_enabled }.from(true).to(false)
+ .and change { project.shared_runners_enabled }.from(true).to(false)
end
end
end
+
+ context "when using SR_DISABLED_AND_OVERRIDABLE" do
+ let(:params) { { shared_runners_setting: Namespace::SR_DISABLED_AND_OVERRIDABLE } }
+
+ include_examples 'allow descendants to override'
+ end
+
+ context "when using SR_DISABLED_WITH_OVERRIDE" do
+ let(:params) { { shared_runners_setting: Namespace::SR_DISABLED_WITH_OVERRIDE } }
+
+ include_examples 'allow descendants to override'
+ end
end
end
end
diff --git a/spec/services/import/github_service_spec.rb b/spec/services/import/github_service_spec.rb
index 21dc24e28f6..982b8b11383 100644
--- a/spec/services/import/github_service_spec.rb
+++ b/spec/services/import/github_service_spec.rb
@@ -5,7 +5,13 @@ require 'spec_helper'
RSpec.describe Import::GithubService, feature_category: :importers do
let_it_be(:user) { create(:user) }
let_it_be(:token) { 'complex-token' }
- let_it_be(:access_params) { { github_access_token: 'github-complex-token' } }
+ let_it_be(:access_params) do
+ {
+ github_access_token: 'github-complex-token',
+ additional_access_tokens: %w[foo bar]
+ }
+ end
+
let(:settings) { instance_double(Gitlab::GithubImport::Settings) }
let(:user_namespace_path) { user.namespace_path }
let(:optional_stages) { nil }
@@ -26,7 +32,12 @@ RSpec.describe Import::GithubService, feature_category: :importers do
before do
allow(Gitlab::GithubImport::Settings).to receive(:new).with(project_double).and_return(settings)
- allow(settings).to receive(:write).with(optional_stages)
+ allow(settings)
+ .to receive(:write)
+ .with(
+ optional_stages: optional_stages,
+ additional_access_tokens: access_params[:additional_access_tokens]
+ )
end
context 'do not raise an exception on input error' do
@@ -82,7 +93,9 @@ RSpec.describe Import::GithubService, feature_category: :importers do
context 'when there is no repository size limit defined' do
it 'skips the check, succeeds, and tracks an access level' do
expect(subject.execute(access_params, :github)).to include(status: :success)
- expect(settings).to have_received(:write).with(nil)
+ expect(settings)
+ .to have_received(:write)
+ .with(optional_stages: nil, additional_access_tokens: access_params[:additional_access_tokens])
expect_snowplow_event(
category: 'Import::GithubService',
action: 'create',
@@ -102,7 +115,9 @@ RSpec.describe Import::GithubService, feature_category: :importers do
it 'succeeds when the repository is smaller than the limit' do
expect(subject.execute(access_params, :github)).to include(status: :success)
- expect(settings).to have_received(:write).with(nil)
+ expect(settings)
+ .to have_received(:write)
+ .with(optional_stages: nil, additional_access_tokens: access_params[:additional_access_tokens])
expect_snowplow_event(
category: 'Import::GithubService',
action: 'create',
@@ -129,7 +144,9 @@ RSpec.describe Import::GithubService, feature_category: :importers do
context 'when application size limit is defined' do
it 'succeeds when the repository is smaller than the limit' do
expect(subject.execute(access_params, :github)).to include(status: :success)
- expect(settings).to have_received(:write).with(nil)
+ expect(settings)
+ .to have_received(:write)
+ .with(optional_stages: nil, additional_access_tokens: access_params[:additional_access_tokens])
expect_snowplow_event(
category: 'Import::GithubService',
action: 'create',
@@ -160,7 +177,22 @@ RSpec.describe Import::GithubService, feature_category: :importers do
it 'saves optional stages choice to import_data' do
subject.execute(access_params, :github)
- expect(settings).to have_received(:write).with(optional_stages)
+ expect(settings)
+ .to have_received(:write)
+ .with(
+ optional_stages: optional_stages,
+ additional_access_tokens: access_params[:additional_access_tokens]
+ )
+ end
+ end
+
+ context 'when additional access tokens are present' do
+ it 'saves additional access tokens to import_data' do
+ subject.execute(access_params, :github)
+
+ expect(settings)
+ .to have_received(:write)
+ .with(optional_stages: optional_stages, additional_access_tokens: %w[foo bar])
end
end
end
diff --git a/spec/services/import_csv/preprocess_milestones_service_spec.rb b/spec/services/import_csv/preprocess_milestones_service_spec.rb
new file mode 100644
index 00000000000..d21be52c9b9
--- /dev/null
+++ b/spec/services/import_csv/preprocess_milestones_service_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ImportCsv::PreprocessMilestonesService, feature_category: :importers do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:provided_titles) { %w[15.10 10.1] }
+
+ let(:service) { described_class.new(user, project, provided_titles) }
+
+ subject { service.execute }
+
+ describe '#execute' do
+ let(:project_milestones) { ::MilestonesFinder.new({ project_ids: [project.id] }).execute }
+
+ shared_examples 'csv import' do |is_success:, milestone_errors:|
+ it 'does not create milestones' do
+ expect { subject }.not_to change { project_milestones.count }
+ end
+
+ it 'reports any missing milestones' do
+ result = subject
+
+ if is_success
+ expect(result).to be_success
+ else
+ expect(result[:status]).to eq(:error)
+ expect(result.payload).to match(milestone_errors)
+ end
+ end
+ end
+
+ context 'with csv that has missing or unavailable milestones' do
+ it_behaves_like 'csv import',
+ { is_success: false, milestone_errors: { missing: { header: 'Milestone', titles: %w[15.10 10.1] } } }
+ end
+
+ context 'with csv that includes project milestones' do
+ let!(:project_milestone) { create(:milestone, project: project, title: '15.10') }
+
+ it_behaves_like 'csv import',
+ { is_success: false, milestone_errors: { missing: { header: 'Milestone', titles: ["10.1"] } } }
+ end
+
+ context 'with csv that includes milestones column' do
+ let!(:project_milestone) { create(:milestone, project: project, title: '15.10') }
+
+ context 'when milestones exist in the importing projects group' do
+ let!(:group_milestone) { create(:milestone, group: group, title: '10.1') }
+
+ it_behaves_like 'csv import', { is_success: true, milestone_errors: nil }
+ end
+
+ context 'when milestones exist in a subgroup of the importing projects group' do
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let!(:group_milestone) { create(:milestone, group: subgroup, title: '10.1') }
+
+ it_behaves_like 'csv import',
+ { is_success: false, milestone_errors: { missing: { header: 'Milestone', titles: ["10.1"] } } }
+ end
+
+ context 'when milestones exist in a different project from the importing project' do
+ let_it_be(:second_project) { create(:project, group: group) }
+ let!(:second_project_milestone) { create(:milestone, project: second_project, title: '10.1') }
+
+ it_behaves_like 'csv import',
+ { is_success: false, milestone_errors: { missing: { header: 'Milestone', titles: ["10.1"] } } }
+ end
+
+ context 'when duplicate milestones exist in the projects group and parent group' do
+ let_it_be(:sub_group) { create(:group, parent: group) }
+ let_it_be(:project) { create(:project, group: sub_group) }
+ let!(:ancestor_group_milestone) { create(:milestone, group: group, title: '15.10') }
+ let!(:ancestor_group_milestone_two) { create(:milestone, group: group, title: '10.1') }
+ let!(:group_milestone) { create(:milestone, group: sub_group, title: '10.1') }
+
+ it_behaves_like 'csv import', { is_success: true, milestone_errors: nil }
+ end
+ end
+ end
+end
diff --git a/spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb
index 9b1994af1bb..528921a80ee 100644
--- a/spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb
+++ b/spec/services/incident_management/issuable_escalation_statuses/after_update_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::AfterUpdateServic
let_it_be(:issue, reload: true) { escalation_status.issue }
let_it_be(:project) { issue.project }
- let(:service) { IncidentManagement::IssuableEscalationStatuses::AfterUpdateService.new(issue, current_user) }
+ let(:service) { described_class.new(issue, current_user) }
subject(:result) do
issue.update!(incident_management_issuable_escalation_status_attributes: update_params)
diff --git a/spec/services/integrations/group_mention_service_spec.rb b/spec/services/integrations/group_mention_service_spec.rb
new file mode 100644
index 00000000000..72d53ce6d06
--- /dev/null
+++ b/spec/services/integrations/group_mention_service_spec.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Integrations::GroupMentionService, feature_category: :integrations do
+ subject(:execute) { described_class.new(mentionable, hook_data: hook_data, is_confidential: is_confidential).execute }
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+
+ before do
+ allow(mentionable).to receive(:referenced_groups).with(user).and_return([group])
+ end
+
+ shared_examples 'group_mention_hooks' do
+ specify do
+ expect(group).to receive(:execute_integrations).with(anything, :group_mention_hooks)
+ expect(execute).to be_success
+ end
+ end
+
+ shared_examples 'group_confidential_mention_hooks' do
+ specify do
+ expect(group).to receive(:execute_integrations).with(anything, :group_confidential_mention_hooks)
+ expect(execute).to be_success
+ end
+ end
+
+ context 'for issue descriptions' do
+ let(:hook_data) { mentionable.to_hook_data(user) }
+ let(:is_confidential) { mentionable.confidential? }
+
+ context 'in public projects' do
+ let_it_be(:project) { create(:project, :public) }
+
+ context 'in public issues' do
+ let(:mentionable) do
+ create(:issue, confidential: false, project: project, author: user, description: "@#{group.full_path}")
+ end
+
+ it_behaves_like 'group_mention_hooks'
+ end
+
+ context 'in confidential issues' do
+ let(:mentionable) do
+ create(:issue, confidential: true, project: project, author: user, description: "@#{group.full_path}")
+ end
+
+ it_behaves_like 'group_confidential_mention_hooks'
+ end
+ end
+
+ context 'in private projects' do
+ let_it_be(:project) { create(:project, :private) }
+
+ context 'in public issues' do
+ let(:mentionable) do
+ create(:issue, confidential: false, project: project, author: user, description: "@#{group.full_path}")
+ end
+
+ it_behaves_like 'group_confidential_mention_hooks'
+ end
+
+ context 'in confidential issues' do
+ let(:mentionable) do
+ create(:issue, confidential: true, project: project, author: user, description: "@#{group.full_path}")
+ end
+
+ it_behaves_like 'group_confidential_mention_hooks'
+ end
+ end
+ end
+
+ context 'for merge request descriptions' do
+ let(:hook_data) { mentionable.to_hook_data(user) }
+ let(:is_confidential) { false }
+ let(:mentionable) do
+ create(:merge_request, source_project: project, target_project: project, author: user,
+ description: "@#{group.full_path}")
+ end
+
+ context 'in public projects' do
+ let_it_be(:project) { create(:project, :public) }
+
+ it_behaves_like 'group_mention_hooks'
+ end
+
+ context 'in private projects' do
+ let_it_be(:project) { create(:project, :private) }
+
+ it_behaves_like 'group_confidential_mention_hooks'
+ end
+ end
+
+ context 'for issue notes' do
+ let(:hook_data) { Gitlab::DataBuilder::Note.build(mentionable, mentionable.author) }
+ let(:is_confidential) { mentionable.confidential?(include_noteable: true) }
+
+ context 'in public projects' do
+ let_it_be(:project) { create(:project, :public) }
+
+ context 'in public issues' do
+ let(:issue) do
+ create(:issue, confidential: false, project: project, author: user, description: "@#{group.full_path}")
+ end
+
+ context 'for public notes' do
+ let(:mentionable) { create(:note_on_issue, noteable: issue, project: project, author: user) }
+
+ it_behaves_like 'group_mention_hooks'
+ end
+
+ context 'for internal notes' do
+ let(:mentionable) { create(:note_on_issue, :confidential, noteable: issue, project: project, author: user) }
+
+ it_behaves_like 'group_confidential_mention_hooks'
+ end
+ end
+ end
+
+ context 'in private projects' do
+ let_it_be(:project) { create(:project, :private) }
+
+ context 'in public issues' do
+ let(:issue) do
+ create(:issue, confidential: false, project: project, author: user, description: "@#{group.full_path}")
+ end
+
+ context 'for public notes' do
+ let(:mentionable) { create(:note_on_issue, noteable: issue, project: project, author: user) }
+
+ it_behaves_like 'group_confidential_mention_hooks'
+ end
+
+ context 'for internal notes' do
+ let(:mentionable) { create(:note_on_issue, :confidential, noteable: issue, project: project, author: user) }
+
+ it_behaves_like 'group_confidential_mention_hooks'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/issuable/discussions_list_service_spec.rb b/spec/services/issuable/discussions_list_service_spec.rb
index 03b6a1b4556..446cc286e28 100644
--- a/spec/services/issuable/discussions_list_service_spec.rb
+++ b/spec/services/issuable/discussions_list_service_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Issuable::DiscussionsListService, feature_category: :team_plannin
it_behaves_like 'listing issuable discussions', :guest, 1, 7
context 'without notes widget' do
- let_it_be(:issuable) { create(:work_item, :issue, project: project) }
+ let_it_be(:issuable) { create(:work_item, project: project) }
before do
WorkItems::Type.default_by_type(:issue).widget_definitions.find_by_widget_type(:notes).update!(disabled: true)
diff --git a/spec/services/issuable/import_csv/base_service_spec.rb b/spec/services/issuable/import_csv/base_service_spec.rb
new file mode 100644
index 00000000000..b07c4556694
--- /dev/null
+++ b/spec/services/issuable/import_csv/base_service_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issuable::ImportCsv::BaseService, feature_category: :importers do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:csv_io) { double }
+
+ let(:importer_klass) do
+ Class.new(described_class) do
+ def email_results_to_user
+ # no-op
+ end
+ end
+ end
+
+ let(:service) do
+ uploader = FileUploader.new(project)
+ uploader.store!(file)
+
+ importer_klass.new(user, project, uploader)
+ end
+
+ subject { service.execute }
+
+ describe '#preprocess_milestones' do
+ let(:utility_class) { ImportCsv::PreprocessMilestonesService }
+ let(:file) { fixture_file_upload('spec/fixtures/csv_missing_milestones.csv') }
+ let(:mocked_object) { double }
+
+ before do
+ allow(service).to receive(:create_object).and_return(mocked_object)
+ allow(mocked_object).to receive(:persisted?).and_return(true)
+ end
+
+ context 'with csv that has milestone heading' do
+ before do
+ allow(utility_class).to receive(:new).and_return(utility_class)
+ allow(utility_class).to receive(:execute).and_return(ServiceResponse.success)
+ end
+
+ it 'calls PreprocessMilestonesService' do
+ subject
+ expect(utility_class).to have_received(:new)
+ end
+
+ it 'calls PreprocessMilestonesService with unique milestone titles' do
+ subject
+ expect(utility_class).to have_received(:new).with(user, project, %w[15.10 10.1])
+ expect(utility_class).to have_received(:execute)
+ end
+ end
+
+ context 'with csv that does not have milestone heading' do
+ let(:file) { fixture_file_upload('spec/fixtures/work_items_valid_types.csv') }
+
+ before do
+ allow(utility_class).to receive(:new).and_return(utility_class)
+ end
+
+ it 'does not call PreprocessMilestonesService' do
+ subject
+ expect(utility_class).not_to have_received(:new)
+ end
+ end
+
+ context 'when one or more milestones do not exist' do
+ it 'returns the expected error in results payload' do
+ results = subject
+
+ expect(results[:success]).to eq(0)
+ expect(results[:preprocess_errors]).to match({
+ milestone_errors: { missing: { header: 'Milestone', titles: %w[15.10 10.1] } }
+ })
+ end
+ end
+
+ context 'when all milestones exist' do
+ let!(:group_milestone) { create(:milestone, group: group, title: '10.1') }
+ let!(:project_milestone) { create(:milestone, project: project, title: '15.10') }
+
+ it 'returns a successful response' do
+ results = subject
+
+ expect(results[:preprocess_errors]).to be_empty
+ expect(results[:success]).to eq(4)
+ end
+ end
+ end
+end
diff --git a/spec/services/issuable/process_assignees_spec.rb b/spec/services/issuable/process_assignees_spec.rb
index 2c8d4c5e11d..2751267c08b 100644
--- a/spec/services/issuable/process_assignees_spec.rb
+++ b/spec/services/issuable/process_assignees_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Issuable::ProcessAssignees, feature_category: :team_planning do
describe '#execute' do
it 'returns assignee_ids when add_assignee_ids and remove_assignee_ids are not specified' do
- process = Issuable::ProcessAssignees.new(assignee_ids: %w(5 7 9),
+ process = described_class.new(assignee_ids: %w(5 7 9),
add_assignee_ids: nil,
remove_assignee_ids: nil,
existing_assignee_ids: %w(1 3 9),
@@ -16,7 +16,7 @@ RSpec.describe Issuable::ProcessAssignees, feature_category: :team_planning do
end
it 'combines other ids when assignee_ids is nil' do
- process = Issuable::ProcessAssignees.new(assignee_ids: nil,
+ process = described_class.new(assignee_ids: nil,
add_assignee_ids: nil,
remove_assignee_ids: nil,
existing_assignee_ids: %w(1 3 11),
@@ -27,7 +27,7 @@ RSpec.describe Issuable::ProcessAssignees, feature_category: :team_planning do
end
it 'combines other ids when both add_assignee_ids and remove_assignee_ids are not empty' do
- process = Issuable::ProcessAssignees.new(assignee_ids: %w(5 7 9),
+ process = described_class.new(assignee_ids: %w(5 7 9),
add_assignee_ids: %w(2 4 6),
remove_assignee_ids: %w(4 7 11),
existing_assignee_ids: %w(1 3 11),
@@ -38,7 +38,7 @@ RSpec.describe Issuable::ProcessAssignees, feature_category: :team_planning do
end
it 'combines other ids when remove_assignee_ids is not empty' do
- process = Issuable::ProcessAssignees.new(assignee_ids: %w(5 7 9),
+ process = described_class.new(assignee_ids: %w(5 7 9),
add_assignee_ids: nil,
remove_assignee_ids: %w(4 7 11),
existing_assignee_ids: %w(1 3 11),
@@ -49,7 +49,7 @@ RSpec.describe Issuable::ProcessAssignees, feature_category: :team_planning do
end
it 'combines other ids when add_assignee_ids is not empty' do
- process = Issuable::ProcessAssignees.new(assignee_ids: %w(5 7 9),
+ process = described_class.new(assignee_ids: %w(5 7 9),
add_assignee_ids: %w(2 4 6),
remove_assignee_ids: nil,
existing_assignee_ids: %w(1 3 11),
@@ -60,7 +60,7 @@ RSpec.describe Issuable::ProcessAssignees, feature_category: :team_planning do
end
it 'combines ids when existing_assignee_ids and extra_assignee_ids are omitted' do
- process = Issuable::ProcessAssignees.new(assignee_ids: %w(5 7 9),
+ process = described_class.new(assignee_ids: %w(5 7 9),
add_assignee_ids: %w(2 4 6),
remove_assignee_ids: %w(4 7 11))
result = process.execute
@@ -69,7 +69,7 @@ RSpec.describe Issuable::ProcessAssignees, feature_category: :team_planning do
end
it 'handles mixed string and integer arrays' do
- process = Issuable::ProcessAssignees.new(assignee_ids: %w(5 7 9),
+ process = described_class.new(assignee_ids: %w(5 7 9),
add_assignee_ids: [2, 4, 6],
remove_assignee_ids: %w(4 7 11),
existing_assignee_ids: [1, 3, 11],
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 8368a34caf0..c51371ca0f2 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -205,7 +205,6 @@ RSpec.describe Issues::BuildService, feature_category: :team_planning do
issue = build_issue(**issue_params)
expect(issue.work_item_type_id).to eq(work_item_type_id)
- expect(issue.attributes['issue_type']).to eq(resulting_issue_type)
end
end
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 3dfc9571c9c..2daba8e359d 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -149,6 +149,12 @@ RSpec.describe Issues::CreateService, feature_category: :team_planning do
issue
end
+ it 'calls GroupMentionWorker' do
+ expect(Integrations::GroupMentionWorker).to receive(:perform_async)
+
+ issue
+ end
+
context 'when a build_service is provided' do
let(:result) { described_class.new(container: project, current_user: user, params: opts, build_service: build_service).execute }
@@ -162,6 +168,29 @@ RSpec.describe Issues::CreateService, feature_category: :team_planning do
end
end
+ context 'when issue template is provided' do
+ let_it_be(:files) { { '.gitlab/issue_templates/Default.md' => 'Default template contents' } }
+ let_it_be_with_reload(:project) { create(:project, :custom_repo, group: group, files: files).tap { |project| project.add_guest(user) } }
+
+ context 'when description is blank' do
+ it 'sets template contents as description when description is blank' do
+ opts[:description] = ''
+
+ expect(result).to be_success
+ expect(issue).to be_persisted
+ expect(issue).to have_attributes(description: 'Default template contents')
+ end
+ end
+
+ context 'when description is not blank' do
+ it 'does not apply template when description is not blank' do
+ expect(result).to be_success
+ expect(issue).to be_persisted
+ expect(issue).to have_attributes(description: 'please fix')
+ end
+ end
+ end
+
context 'when skip_system_notes is true' do
let(:issue) { described_class.new(container: project, current_user: user, params: opts).execute(skip_system_notes: true) }
diff --git a/spec/services/issues/relative_position_rebalancing_service_spec.rb b/spec/services/issues/relative_position_rebalancing_service_spec.rb
index 68f1af49b5f..512990b5b3d 100644
--- a/spec/services/issues/relative_position_rebalancing_service_spec.rb
+++ b/spec/services/issues/relative_position_rebalancing_service_spec.rb
@@ -156,5 +156,98 @@ RSpec.describe Issues::RelativePositionRebalancingService, :clean_gitlab_redis_s
subject.execute
end
end
+
+ shared_examples 'no-op on the retried job' do
+ it 'does not update positions in the 2nd .execute' do
+ original_order = issues_in_position_order.map(&:id)
+
+ # preloads issue ids on both runs
+ expect(service).to receive(:preload_issue_ids).twice.and_call_original
+
+ # 1st run performs rebalancing
+ expect(service).to receive(:update_positions_with_retry).exactly(9).times.and_call_original
+ expect { service.execute }.to raise_error(StandardError)
+
+ # 2nd run is a no-op
+ expect(service).not_to receive(:update_positions_with_retry)
+ expect { service.execute }.to raise_error(StandardError)
+
+ # order is preserved
+ expect(original_order).to match_array(issues_in_position_order.map(&:id))
+ end
+ end
+
+ context 'when error is raised in cache cleanup step' do
+ let_it_be(:root_namespace_id) { project.root_namespace.id }
+
+ context 'when srem fails' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ allow(redis).to receive(:srem?).and_raise(StandardError)
+ end
+ end
+
+ it_behaves_like 'no-op on the retried job'
+ end
+
+ context 'when delete issues ids sorted set fails' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ allow(redis).to receive(:del).and_call_original
+ allow(redis).to receive(:del)
+ .with("#{Gitlab::Issues::Rebalancing::State::REDIS_KEY_PREFIX}:#{root_namespace_id}")
+ .and_raise(StandardError)
+ end
+ end
+
+ it_behaves_like 'no-op on the retried job'
+ end
+
+ context 'when delete current_index_key fails' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ allow(redis).to receive(:del).and_call_original
+ allow(redis).to receive(:del)
+ .with("#{Gitlab::Issues::Rebalancing::State::REDIS_KEY_PREFIX}:#{root_namespace_id}:current_index")
+ .and_raise(StandardError)
+ end
+ end
+
+ it_behaves_like 'no-op on the retried job'
+ end
+
+ context 'when setting recently finished key fails' do
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ allow(redis).to receive(:set).and_call_original
+ allow(redis).to receive(:set)
+ .with(
+ "#{Gitlab::Issues::Rebalancing::State::RECENTLY_FINISHED_REBALANCE_PREFIX}:2:#{project.id}",
+ anything,
+ anything
+ )
+ .and_raise(StandardError)
+ end
+ end
+
+ it 'reruns the next job in full' do
+ original_order = issues_in_position_order.map(&:id)
+
+ # preloads issue ids on both runs
+ expect(service).to receive(:preload_issue_ids).twice.and_call_original
+
+ # 1st run performs rebalancing
+ expect(service).to receive(:update_positions_with_retry).exactly(9).times.and_call_original
+ expect { service.execute }.to raise_error(StandardError)
+
+ # 2nd run performs rebalancing in full
+ expect(service).to receive(:update_positions_with_retry).exactly(9).times.and_call_original
+ expect { service.execute }.to raise_error(StandardError)
+
+ # order is preserved
+ expect(original_order).to match_array(issues_in_position_order.map(&:id))
+ end
+ end
+ end
end
end
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index bb1151dfac7..377efdb3f9f 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -68,6 +68,12 @@ RSpec.describe Issues::ReopenService, feature_category: :team_planning do
expect { execute }.not_to change { issue.incident_management_timeline_events.count }
end
+ it 'does not call GroupMentionWorker' do
+ expect(Integrations::GroupMentionWorker).not_to receive(:perform_async)
+
+ issue
+ end
+
context 'issue is incident type' do
let(:issue) { create(:incident, :closed, project: project) }
let(:current_user) { user }
diff --git a/spec/services/members/invite_service_spec.rb b/spec/services/members/invite_service_spec.rb
index 1c0466980f4..76cd5d6c89e 100644
--- a/spec/services/members/invite_service_spec.rb
+++ b/spec/services/members/invite_service_spec.rb
@@ -219,6 +219,18 @@ RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_
expect(result[:message][params[:email]]).to eq("Invite email is invalid")
end
end
+
+ context 'with email that has trailing spaces' do
+ let(:params) { { email: ' foo@bar.com ' } }
+
+ it 'returns an error' do
+ expect_not_to_create_members
+ expect(result[:status]).to eq(:error)
+ expect(result[:message][params[:email]]).to eq("Invite email is invalid")
+ end
+
+ it_behaves_like 'does not record an onboarding progress action'
+ end
end
context 'with duplicate invites' do
diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb
index 7255d19ef8a..1126539b25a 100644
--- a/spec/services/merge_requests/after_create_service_spec.rb
+++ b/spec/services/merge_requests/after_create_service_spec.rb
@@ -75,6 +75,12 @@ RSpec.describe MergeRequests::AfterCreateService, feature_category: :code_review
execute_service
end
+ it 'calls GroupMentionWorker' do
+ expect(Integrations::GroupMentionWorker).to receive(:perform_async)
+
+ execute_service
+ end
+
it_behaves_like 'records an onboarding progress action', :merge_request_created do
let(:namespace) { merge_request.target_project.namespace }
end
diff --git a/spec/services/merge_requests/cleanup_refs_service_spec.rb b/spec/services/merge_requests/cleanup_refs_service_spec.rb
index 960b8101c36..efb6265e3d8 100644
--- a/spec/services/merge_requests/cleanup_refs_service_spec.rb
+++ b/spec/services/merge_requests/cleanup_refs_service_spec.rb
@@ -18,6 +18,8 @@ RSpec.describe MergeRequests::CleanupRefsService, feature_category: :code_review
describe '#execute' do
before do
+ stub_feature_flags(merge_request_cleanup_ref_worker_async: false)
+
# Need to re-enable this as it's being stubbed in spec_helper for
# performance reasons but is needed to run for this test.
allow(Gitlab::Git::KeepAround).to receive(:execute).and_call_original
diff --git a/spec/services/merge_requests/merge_orchestration_service_spec.rb b/spec/services/merge_requests/merge_orchestration_service_spec.rb
index 389956bf258..b9bf936eddd 100644
--- a/spec/services/merge_requests/merge_orchestration_service_spec.rb
+++ b/spec/services/merge_requests/merge_orchestration_service_spec.rb
@@ -45,7 +45,17 @@ RSpec.describe MergeRequests::MergeOrchestrationService, feature_category: :code
subject
expect(merge_request).to be_auto_merge_enabled
- expect(merge_request.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+
+ if Gitlab.ee?
+ expect(merge_request.auto_merge_strategy).to(
+ eq(AutoMergeService::STRATEGY_MERGE_WHEN_CHECKS_PASS)
+ )
+ else
+ expect(merge_request.auto_merge_strategy).to(
+ eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
+ )
+ end
+
expect(merge_request).not_to be_merged
end
end
@@ -108,7 +118,11 @@ RSpec.describe MergeRequests::MergeOrchestrationService, feature_category: :code
merge_request.update_head_pipeline
end
- it 'fetches perferred auto merge strategy' do
+ it 'fetches preferred auto merge strategy', if: Gitlab.ee? do
+ is_expected.to eq(AutoMergeService::STRATEGY_MERGE_WHEN_CHECKS_PASS)
+ end
+
+ it 'fetches preferred auto merge strategy', unless: Gitlab.ee? do
is_expected.to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
end
end
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
index 8200f60b072..4e951f1bc85 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -288,17 +288,5 @@ RSpec.describe MergeRequests::MergeToRefService, feature_category: :code_review_
end
end
end
-
- context 'allow conflicts to be merged in diff' do
- let(:params) { { allow_conflicts: true } }
-
- it 'calls merge_to_ref with allow_conflicts param' do
- expect(project.repository).to receive(:merge_to_ref) do |user, **kwargs|
- expect(kwargs[:allow_conflicts]).to eq(true)
- end.and_call_original
-
- service.execute(merge_request)
- end
- end
end
end
diff --git a/spec/services/merge_requests/mergeability_check_batch_service_spec.rb b/spec/services/merge_requests/mergeability_check_batch_service_spec.rb
new file mode 100644
index 00000000000..099b8039f3e
--- /dev/null
+++ b/spec/services/merge_requests/mergeability_check_batch_service_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe MergeRequests::MergeabilityCheckBatchService, feature_category: :code_review_workflow do
+ describe '#execute' do
+ subject { described_class.new(merge_requests, user).execute }
+
+ let(:merge_requests) { [] }
+ let_it_be(:user) { create(:user) }
+
+ context 'when merge_requests are not empty' do
+ let_it_be(:merge_request_1) { create(:merge_request) }
+ let_it_be(:merge_request_2) { create(:merge_request) }
+ let_it_be(:merge_requests) { [merge_request_1, merge_request_2] }
+
+ it 'triggers batch mergeability checks' do
+ expect(MergeRequests::MergeabilityCheckBatchWorker).to receive(:perform_async)
+ .with([merge_request_1.id, merge_request_2.id], user.id)
+
+ subject
+ end
+
+ context 'when user is nil' do
+ let(:user) { nil }
+
+ it 'trigger mergeability checks with nil user_id' do
+ expect(MergeRequests::MergeabilityCheckBatchWorker).to receive(:perform_async)
+ .with([merge_request_1.id, merge_request_2.id], nil)
+
+ subject
+ end
+ end
+ end
+
+ context 'when merge_requests is empty' do
+ let(:merge_requests) { MergeRequest.none }
+
+ it 'does not trigger mergeability checks' do
+ expect(MergeRequests::MergeabilityCheckBatchWorker).not_to receive(:perform_async)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 4d533b67690..06932af26dc 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -1024,4 +1024,49 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor
end
end
end
+
+ describe '#abort_auto_merges' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:author) { user }
+
+ let_it_be(:merge_request, refind: true) do
+ create(
+ :merge_request,
+ source_project: project,
+ target_project: project,
+ merge_user: user,
+ auto_merge_enabled: true,
+ auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
+ )
+ end
+
+ let(:service) { described_class.new(project: project, current_user: user) }
+ let(:oldrev) { merge_request.diff_refs.base_sha }
+ let(:newrev) { merge_request.diff_refs.head_sha }
+ let(:merge_sha) { oldrev }
+
+ before do
+ merge_request.merge_params[:sha] = merge_sha
+ merge_request.save!
+
+ service.execute(oldrev, newrev, "refs/heads/#{merge_request.source_branch}")
+
+ merge_request.reload
+ end
+
+ it 'aborts MWPS for merge requests' do
+ expect(merge_request.auto_merge_enabled?).to be_falsey
+ expect(merge_request.merge_user).to be_nil
+ end
+
+ context 'when merge params contains up-to-date sha' do
+ let(:merge_sha) { newrev }
+
+ it 'maintains MWPS for merge requests' do
+ expect(merge_request.auto_merge_enabled?).to be_truthy
+ expect(merge_request.merge_user).to eq(user)
+ end
+ end
+ end
end
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index 7399b29d06e..e173cd382f2 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -40,6 +40,10 @@ RSpec.describe MergeRequests::ReopenService, feature_category: :code_review_work
.with(merge_request, 'reopen')
end
+ it 'does not call GroupMentionWorker' do
+ expect(Integrations::GroupMentionWorker).not_to receive(:perform_async)
+ end
+
it 'sends email to user2 about reopen of merge_request', :sidekiq_might_not_need_inline do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 52999b5a1ea..79f608a4614 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -108,7 +108,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_description_edit_action).once.with(user: user)
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request2)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request2)
end
it 'tracks Draft marking' do
@@ -117,7 +117,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:title] = "Draft: #{opts[:title]}"
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request2)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request2)
end
it 'tracks Draft un-marking' do
@@ -126,7 +126,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:title] = "Non-draft/wip title string"
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(draft_merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(draft_merge_request)
end
context 'when MR is locked' do
@@ -137,7 +137,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:discussion_locked] = true
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
@@ -148,7 +148,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:discussion_locked] = false
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
end
@@ -163,7 +163,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:discussion_locked] = false
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
@@ -174,7 +174,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:discussion_locked] = true
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
end
@@ -193,7 +193,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
spent_at: Date.parse('2021-02-24')
}
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
it 'tracks milestone change' do
@@ -202,7 +202,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:milestone_id] = milestone.id
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
it 'track labels change' do
@@ -211,7 +211,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:label_ids] = [label2.id]
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
context 'reviewers' do
@@ -222,7 +222,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:reviewers] = [user2]
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
@@ -233,7 +233,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
opts[:reviewers] = merge_request.reviewers
- MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request)
+ described_class.new(project: project, current_user: user, params: opts).execute(merge_request)
end
end
end
@@ -449,7 +449,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
let(:milestone) { create(:milestone, project: project) }
let(:req_opts) { { source_branch: 'feature', target_branch: 'master' } }
- subject { MergeRequests::UpdateService.new(project: project, current_user: user, params: opts).execute(merge_request) }
+ subject { described_class.new(project: project, current_user: user, params: opts).execute(merge_request) }
context 'when mentionable attributes change' do
let(:opts) { { description: "Description with #{user.to_reference}" }.merge(req_opts) }
@@ -552,7 +552,8 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
head_pipeline_of: merge_request
)
- expect(AutoMerge::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user, { sha: merge_request.diff_head_sha })
+ strategies_count = Gitlab.ee? ? :twice : :once
+ expect(AutoMerge::MergeWhenPipelineSucceedsService).to receive(:new).exactly(strategies_count).with(project, user, { sha: merge_request.diff_head_sha })
.and_return(service_mock)
allow(service_mock).to receive(:available_for?) { true }
expect(service_mock).to receive(:execute).with(merge_request)
diff --git a/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb b/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb
index beed23a366f..53def716de3 100644
--- a/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Metrics::Dashboard::ClusterDashboardService, :use_clean_rails_mem
end
describe '#get_dashboard' do
- let(:service_params) { [project, user, { cluster: cluster, cluster_type: :project }] }
+ let(:service_params) { [project, user, { cluster: cluster, cluster_type: :admin }] }
let(:service_call) { subject.get_dashboard }
subject { described_class.new(*service_params) }
diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb
index 78cb05532eb..70010d88fbd 100644
--- a/spec/services/milestones/create_service_spec.rb
+++ b/spec/services/milestones/create_service_spec.rb
@@ -3,24 +3,70 @@
require 'spec_helper'
RSpec.describe Milestones::CreateService, feature_category: :team_planning do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:params) { { title: 'New Milestone', description: 'Description' } }
+
+ subject(:create_milestone) { described_class.new(project, user, params) }
describe '#execute' do
- context "valid params" do
+ context 'when milestone is saved successfully' do
+ it 'creates a new milestone' do
+ expect { create_milestone.execute }.to change { Milestone.count }.by(1)
+ end
+
+ it 'opens the milestone if it is a project milestone' do
+ expect_next_instance_of(EventCreateService) do |instance|
+ expect(instance).to receive(:open_milestone)
+ end
+
+ create_milestone.execute
+ end
+
+ it 'returns the created milestone' do
+ milestone = create_milestone.execute
+ expect(milestone).to be_a(Milestone)
+ expect(milestone.title).to eq('New Milestone')
+ expect(milestone.description).to eq('Description')
+ end
+ end
+
+ context 'when milestone fails to save' do
before do
- project.add_maintainer(user)
+ allow_next_instance_of(Milestone) do |instance|
+ allow(instance).to receive(:save).and_return(false)
+ end
+ end
+
+ it 'does not create a new milestone' do
+ expect { create_milestone.execute }.not_to change { Milestone.count }
+ end
- opts = {
- title: 'v2.1.9',
- description: 'Patch release to fix security issue'
- }
+ it 'does not open the milestone' do
+ expect(EventCreateService).not_to receive(:open_milestone)
+
+ create_milestone.execute
+ end
- @milestone = described_class.new(project, user, opts).execute
+ it 'returns the unsaved milestone' do
+ milestone = create_milestone.execute
+ expect(milestone).to be_a(Milestone)
+ expect(milestone.title).to eq('New Milestone')
+ expect(milestone.persisted?).to be_falsey
end
+ end
+
+ it 'calls before_create method' do
+ expect(create_milestone).to receive(:before_create)
+ create_milestone.execute
+ end
+ end
- it { expect(@milestone).to be_valid }
- it { expect(@milestone.title).to eq('v2.1.9') }
+ describe '#before_create' do
+ it 'checks for spam' do
+ milestone = build(:milestone)
+ expect(milestone).to receive(:check_for_spam).with(user: user, action: :create)
+ subject.send(:before_create, milestone)
end
end
end
diff --git a/spec/services/milestones/update_service_spec.rb b/spec/services/milestones/update_service_spec.rb
index 76110af2514..44de49960d4 100644
--- a/spec/services/milestones/update_service_spec.rb
+++ b/spec/services/milestones/update_service_spec.rb
@@ -2,40 +2,86 @@
require 'spec_helper'
RSpec.describe Milestones::UpdateService, feature_category: :team_planning do
- let(:project) { create(:project) }
- let(:user) { build(:user) }
- let(:milestone) { create(:milestone, project: project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:params) { { title: 'New Title' } }
+
+ subject(:update_milestone) { described_class.new(project, user, params) }
describe '#execute' do
- context "valid params" do
- let(:inner_service) { double(:service) }
+ context 'when state_event is "activate"' do
+ let(:params) { { state_event: 'activate' } }
- before do
- project.add_maintainer(user)
+ it 'calls Milestones::ReopenService' do
+ reopen_service = instance_double(Milestones::ReopenService)
+ expect(Milestones::ReopenService).to receive(:new).with(project, user, {}).and_return(reopen_service)
+ expect(reopen_service).to receive(:execute).with(milestone)
+
+ update_milestone.execute(milestone)
end
+ end
- subject { described_class.new(project, user, { title: 'new_title' }).execute(milestone) }
+ context 'when state_event is "close"' do
+ let(:params) { { state_event: 'close' } }
+
+ it 'calls Milestones::CloseService' do
+ close_service = instance_double(Milestones::CloseService)
+ expect(Milestones::CloseService).to receive(:new).with(project, user, {}).and_return(close_service)
+ expect(close_service).to receive(:execute).with(milestone)
+
+ update_milestone.execute(milestone)
+ end
+ end
- it { expect(subject).to be_valid }
- it { expect(subject.title).to eq('new_title') }
+ context 'when params are present' do
+ it 'assigns the params to the milestone' do
+ expect(milestone).to receive(:assign_attributes).with(params.except(:state_event))
- context 'state_event is activate' do
- it 'calls ReopenService' do
- expect(Milestones::ReopenService).to receive(:new).with(project, user, {}).and_return(inner_service)
- expect(inner_service).to receive(:execute).with(milestone)
+ update_milestone.execute(milestone)
+ end
+ end
- described_class.new(project, user, { state_event: 'activate' }).execute(milestone)
- end
+ context 'when milestone is changed' do
+ before do
+ allow(milestone).to receive(:changed?).and_return(true)
end
- context 'state_event is close' do
- it 'calls ReopenService' do
- expect(Milestones::CloseService).to receive(:new).with(project, user, {}).and_return(inner_service)
- expect(inner_service).to receive(:execute).with(milestone)
+ it 'calls before_update' do
+ expect(update_milestone).to receive(:before_update).with(milestone)
- described_class.new(project, user, { state_event: 'close' }).execute(milestone)
- end
+ update_milestone.execute(milestone)
end
end
+
+ context 'when milestone is not changed' do
+ before do
+ allow(milestone).to receive(:changed?).and_return(false)
+ end
+
+ it 'does not call before_update' do
+ expect(update_milestone).not_to receive(:before_update)
+
+ update_milestone.execute(milestone)
+ end
+ end
+
+ it 'saves the milestone' do
+ expect(milestone).to receive(:save)
+
+ update_milestone.execute(milestone)
+ end
+
+ it 'returns the milestone' do
+ expect(update_milestone.execute(milestone)).to eq(milestone)
+ end
+ end
+
+ describe '#before_update' do
+ it 'checks for spam' do
+ expect(milestone).to receive(:check_for_spam).with(user: user, action: :update)
+
+ update_milestone.send(:before_update, milestone)
+ end
end
end
diff --git a/spec/services/namespace_settings/update_service_spec.rb b/spec/services/namespace_settings/update_service_spec.rb
index daffae1dda7..37cbaf19a6e 100644
--- a/spec/services/namespace_settings/update_service_spec.rb
+++ b/spec/services/namespace_settings/update_service_spec.rb
@@ -45,6 +45,22 @@ RSpec.describe NamespaceSettings::UpdateService, feature_category: :groups_and_p
end
end
+ context 'when default_branch_protection is updated' do
+ let(:namespace_settings) { group.namespace_settings }
+ let(:expected) { ::Gitlab::Access::BranchProtection.protected_against_developer_pushes.stringify_keys }
+ let(:settings) { { default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE } }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it "updates default_branch_protection_defaults from the default_branch_protection param" do
+ expect { service.execute }
+ .to change { namespace_settings.default_branch_protection_defaults }
+ .from({}).to(expected)
+ end
+ end
+
context "updating :resource_access_token_creation_allowed" do
let(:settings) { { resource_access_token_creation_allowed: false } }
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
index 0bcfd6b63d2..35d3620e429 100644
--- a/spec/services/notes/post_process_service_spec.rb
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -22,6 +22,9 @@ RSpec.describe Notes::PostProcessService, feature_category: :team_planning do
it do
expect(project).to receive(:execute_hooks)
expect(project).to receive(:execute_integrations)
+ expect_next_instance_of(Integrations::GroupMentionService) do |group_mention_service|
+ expect(group_mention_service).to receive(:execute)
+ end
described_class.new(@note).execute
end
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index cd3a4e8a395..c51e381014d 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -182,7 +182,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :team_planning do
context 'on an incident' do
before do
- issue.update!(issue_type: :incident, work_item_type: WorkItems::Type.default_by_type(:incident))
+ issue.update!(work_item_type: WorkItems::Type.default_by_type(:incident))
end
it 'leaves the note empty' do
@@ -224,7 +224,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :team_planning do
context 'on an incident' do
before do
- issue.update!(issue_type: :incident, work_item_type: WorkItems::Type.default_by_type(:incident))
+ issue.update!(work_item_type: WorkItems::Type.default_by_type(:incident))
end
it 'leaves the note empty' do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 99f3134f06f..1d1dd045a09 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -532,7 +532,7 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
allow(::Gitlab::Email::IncomingEmail).to receive(:supports_wildcard?) { true }
end
- let(:subject) { NotificationService.new }
+ let(:subject) { described_class.new }
let(:mailer) { double(deliver_later: true) }
let(:issue) { create(:issue, author: User.support_bot) }
let(:project) { issue.project }
@@ -3889,7 +3889,7 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
let(:note) { create(:note, noteable: issue, project: project) }
let(:member) { create(:user) }
- subject { NotificationService.new }
+ subject { described_class.new }
before do
project.add_maintainer(member)
diff --git a/spec/services/packages/cleanup/execute_policy_service_spec.rb b/spec/services/packages/cleanup/execute_policy_service_spec.rb
index a083dc0d4ea..249fd50588f 100644
--- a/spec/services/packages/cleanup/execute_policy_service_spec.rb
+++ b/spec/services/packages/cleanup/execute_policy_service_spec.rb
@@ -122,13 +122,13 @@ RSpec.describe Packages::Cleanup::ExecutePolicyService, feature_category: :packa
def mock_service_timeout(on_iteration:)
execute_call_count = 1
expect_next_instances_of(::Packages::MarkPackageFilesForDestructionService, 3) do |service|
- expect(service).to receive(:execute).and_wrap_original do |m, *args|
+ expect(service).to receive(:execute).and_wrap_original do |m, *args, **kwargs|
# timeout if we are on the right iteration
if execute_call_count == on_iteration
service_timeout_response
else
execute_call_count += 1
- m.call(*args)
+ m.call(*args, **kwargs)
end
end
end
diff --git a/spec/services/packages/debian/find_or_create_package_service_spec.rb b/spec/services/packages/debian/find_or_create_package_service_spec.rb
deleted file mode 100644
index c2ae3d56864..00000000000
--- a/spec/services/packages/debian/find_or_create_package_service_spec.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Packages::Debian::FindOrCreatePackageService, feature_category: :package_registry do
- let_it_be(:distribution) { create(:debian_project_distribution, :with_suite) }
- let_it_be(:distribution2) { create(:debian_project_distribution, :with_suite) }
-
- let_it_be(:project) { distribution.project }
- let_it_be(:user) { create(:user) }
-
- let(:service) { described_class.new(project, user, params) }
- let(:params2) { params }
- let(:service2) { described_class.new(project, user, params2) }
-
- let(:package) { subject.payload[:package] }
- let(:package2) { service2.execute.payload[:package] }
-
- shared_examples 'find or create Debian package' do
- it 'returns the same object' do
- expect { subject }.to change { ::Packages::Package.count }.by(1)
- expect(subject).to be_success
- expect(package).to be_valid
- expect(package.project_id).to eq(project.id)
- expect(package.creator_id).to eq(user.id)
- expect(package.name).to eq('foo')
- expect(package.version).to eq('1.0+debian')
- expect(package).to be_debian
- expect(package.debian_publication.distribution).to eq(distribution)
-
- expect { package2 }.not_to change { ::Packages::Package.count }
- expect(package2.id).to eq(package.id)
- end
-
- context 'with package marked as pending_destruction' do
- it 'creates a new package' do
- expect { subject }.to change { ::Packages::Package.count }.by(1)
-
- package.pending_destruction!
-
- expect { package2 }.to change { ::Packages::Package.count }.by(1)
- expect(package2.id).not_to eq(package.id)
- end
- end
- end
-
- describe '#execute' do
- subject { service.execute }
-
- context 'with a codename as distribution name' do
- let(:params) { { name: 'foo', version: '1.0+debian', distribution_name: distribution.codename } }
-
- it_behaves_like 'find or create Debian package'
- end
-
- context 'with a suite as distribution name' do
- let(:params) { { name: 'foo', version: '1.0+debian', distribution_name: distribution.suite } }
-
- it_behaves_like 'find or create Debian package'
- end
-
- context 'with existing package in another distribution' do
- let(:params) { { name: 'foo', version: '1.0+debian', distribution_name: distribution.codename } }
- let(:params2) { { name: 'foo', version: '1.0+debian', distribution_name: distribution2.codename } }
-
- it 'raises ArgumentError' do
- expect { subject }.to change { ::Packages::Package.count }.by(1)
-
- expect { package2 }.to raise_error(ArgumentError, "Debian package #{package.name} #{package.version} exists " \
- "in distribution #{distribution.codename}")
- end
- end
-
- context 'with non-existing distribution' do
- let(:params) { { name: 'foo', version: '1.0+debian', distribution_name: 'not-existing' } }
-
- it 'raises ActiveRecord::RecordNotFound' do
- expect { package }.to raise_error(ActiveRecord::RecordNotFound,
- /^Couldn't find Packages::Debian::ProjectDistribution/)
- end
- end
- end
-end
diff --git a/spec/services/packages/debian/process_changes_service_spec.rb b/spec/services/packages/debian/process_changes_service_spec.rb
deleted file mode 100644
index 39b917cf1bc..00000000000
--- a/spec/services/packages/debian/process_changes_service_spec.rb
+++ /dev/null
@@ -1,140 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Packages::Debian::ProcessChangesService, feature_category: :package_registry do
- describe '#execute' do
- let_it_be(:user) { create(:user) }
- let_it_be_with_reload(:distribution) { create(:debian_project_distribution, :with_file, suite: 'unstable') }
-
- let!(:incoming) { create(:debian_incoming, project: distribution.project, with_changes_file: true) }
-
- let(:package_file) { incoming.package_files.with_file_name('sample_1.2.3~alpha2_amd64.changes').first }
-
- subject { described_class.new(package_file, user) }
-
- context 'with valid package file' do
- it 'updates package and package file', :aggregate_failures do
- expect(::Packages::Debian::GenerateDistributionWorker).to receive(:perform_async).with(:project, distribution.id)
- expect { subject.execute }
- .to change { Packages::Package.count }.from(1).to(2)
- .and not_change { Packages::PackageFile.count }
- .and change { incoming.package_files.count }.from(8).to(0)
- .and change { package_file.debian_file_metadatum&.reload&.file_type }.from('unknown').to('changes')
-
- created_package = Packages::Package.last
- expect(created_package.name).to eq 'sample'
- expect(created_package.version).to eq '1.2.3~alpha2'
- expect(created_package.creator).to eq user
- end
-
- context 'with non-matching distribution' do
- before do
- distribution.update! suite: FFaker::Lorem.word
- end
-
- it { expect { subject.execute }.to raise_error(ActiveRecord::RecordNotFound) }
- end
-
- context 'with missing field in .changes file' do
- shared_examples 'raises error with missing field' do |missing_field|
- before do
- allow_next_instance_of(::Packages::Debian::ExtractChangesMetadataService) do |extract_changes_metadata_service|
- expect(extract_changes_metadata_service).to receive(:execute).once.and_wrap_original do |m, *args|
- metadata = m.call(*args)
- metadata[:fields].delete(missing_field)
- metadata
- end
- end
- end
-
- it { expect { subject.execute }.to raise_error(ArgumentError, "missing #{missing_field} field") }
- end
-
- it_behaves_like 'raises error with missing field', 'Source'
- it_behaves_like 'raises error with missing field', 'Version'
- it_behaves_like 'raises error with missing field', 'Distribution'
- end
-
- context 'with existing package in the same distribution' do
- let_it_be_with_reload(:existing_package) do
- create(:debian_package, name: 'sample', version: '1.2.3~alpha2', project: distribution.project, published_in: distribution)
- end
-
- it 'does not create a package and assigns the package_file to the existing package' do
- expect { subject.execute }
- .to not_change { Packages::Package.count }
- .and not_change { Packages::PackageFile.count }
- .and change { package_file.package }.to(existing_package)
- end
-
- context 'and marked as pending_destruction' do
- it 'does not re-use the existing package' do
- existing_package.pending_destruction!
-
- expect { subject.execute }
- .to change { Packages::Package.count }.by(1)
- .and not_change { Packages::PackageFile.count }
- end
- end
- end
-
- context 'with existing package in another distribution' do
- let_it_be_with_reload(:existing_package) do
- create(:debian_package, name: 'sample', version: '1.2.3~alpha2', project: distribution.project)
- end
-
- it 'raise ExtractionError' do
- expect(::Packages::Debian::GenerateDistributionWorker).not_to receive(:perform_async)
- expect { subject.execute }
- .to not_change { Packages::Package.count }
- .and not_change { Packages::PackageFile.count }
- .and not_change { incoming.package_files.count }
- .and raise_error(ArgumentError,
- "Debian package #{existing_package.name} #{existing_package.version} exists " \
- "in distribution #{existing_package.debian_distribution.codename}")
- end
-
- context 'and marked as pending_destruction' do
- it 'does not re-use the existing package' do
- existing_package.pending_destruction!
-
- expect { subject.execute }
- .to change { Packages::Package.count }.by(1)
- .and not_change { Packages::PackageFile.count }
- end
- end
- end
- end
-
- context 'with invalid package file' do
- let(:package_file) { incoming.package_files.first }
-
- it 'raise ExtractionError', :aggregate_failures do
- expect(::Packages::Debian::GenerateDistributionWorker).not_to receive(:perform_async)
- expect { subject.execute }
- .to not_change { Packages::Package.count }
- .and not_change { Packages::PackageFile.count }
- .and not_change { incoming.package_files.count }
- .and raise_error(Packages::Debian::ExtractChangesMetadataService::ExtractionError, 'is not a changes file')
- end
- end
-
- context 'when creating package fails' do
- before do
- allow_next_instance_of(::Packages::Debian::FindOrCreatePackageService) do |find_or_create_package_service|
- expect(find_or_create_package_service).to receive(:execute).and_raise(ActiveRecord::ConnectionTimeoutError, 'connect timeout')
- end
- end
-
- it 're-raise error', :aggregate_failures do
- expect(::Packages::Debian::GenerateDistributionWorker).not_to receive(:perform_async)
- expect { subject.execute }
- .to not_change { Packages::Package.count }
- .and not_change { Packages::PackageFile.count }
- .and not_change { incoming.package_files.count }
- .and raise_error(ActiveRecord::ConnectionTimeoutError, 'connect timeout')
- end
- end
- end
-end
diff --git a/spec/services/packages/npm/create_metadata_cache_service_spec.rb b/spec/services/packages/npm/create_metadata_cache_service_spec.rb
index 02f29dd94df..f4010a7d548 100644
--- a/spec/services/packages/npm/create_metadata_cache_service_spec.rb
+++ b/spec/services/packages/npm/create_metadata_cache_service_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe Packages::Npm::CreateMetadataCacheService, :clean_gitlab_redis_sh
new_metadata = Gitlab::Json.parse(npm_metadata_cache.file.read)
expect(new_metadata).not_to eq(metadata)
- expect(new_metadata['dist_tags'].keys).to include(tag_name)
+ expect(new_metadata['dist-tags'].keys).to include(tag_name)
expect(npm_metadata_cache.reload.size).not_to eq(metadata_size)
end
end
diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb
index a12d86412d8..8b94bce6650 100644
--- a/spec/services/packages/npm/create_package_service_spec.rb
+++ b/spec/services/packages/npm/create_package_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
let(:namespace) { create(:namespace) }
let(:project) { create(:project, namespace: namespace) }
- let(:user) { create(:user) }
+ let(:user) { project.owner }
let(:version) { '1.0.1' }
let(:params) do
@@ -69,7 +69,7 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
field_sizes: expected_field_sizes
)
- expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Package json structure is too large')
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid, /structure is too large/)
.and not_change { Packages::Package.count }
.and not_change { Packages::Package.npm.count }
.and not_change { Packages::Tag.count }
@@ -171,6 +171,12 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
it_behaves_like 'valid package'
end
+ context 'when user is no project member' do
+ let(:user) { create(:user) }
+
+ it_behaves_like 'valid package'
+ end
+
context 'scoped package not following the naming convention' do
let(:package_name) { '@any-scope/package' }
@@ -290,7 +296,7 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
end
with_them do
- it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid') }
+ it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Version #{Gitlab::Regex.semver_regex_message}") }
end
end
@@ -313,7 +319,7 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
end
it { expect(subject[:http_status]).to eq 400 }
- it { expect(subject[:message]).to eq 'Could not obtain package lease.' }
+ it { expect(subject[:message]).to eq 'Could not obtain package lease. Please try again.' }
end
context 'when many of the same packages are created at the same time', :delete do
diff --git a/spec/services/packages/npm/generate_metadata_service_spec.rb b/spec/services/packages/npm/generate_metadata_service_spec.rb
index 1e3b0f71972..fdd0ab0ccee 100644
--- a/spec/services/packages/npm/generate_metadata_service_spec.rb
+++ b/spec/services/packages/npm/generate_metadata_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe ::Packages::Npm::GenerateMetadataService, feature_category: :pack
let_it_be(:package2) { create(:npm_package, version: '2.0.6', project: project, name: package_name) }
let_it_be(:latest_package) { create(:npm_package, version: '2.0.11', project: project, name: package_name) }
- let(:packages) { project.packages.npm.with_name(package_name).last_of_each_version }
+ let(:packages) { project.packages.npm.with_name(package_name) }
let(:metadata) { described_class.new(package_name, packages).execute }
describe '#versions' do
@@ -157,14 +157,14 @@ RSpec.describe ::Packages::Npm::GenerateMetadataService, feature_category: :pack
end
def check_n_plus_one(only_dist_tags: false)
- pkgs = project.packages.npm.with_name(package_name).last_of_each_version.preload_files
+ pkgs = project.packages.npm.with_name(package_name).preload_files
control = ActiveRecord::QueryRecorder.new do
described_class.new(package_name, pkgs).execute(only_dist_tags: only_dist_tags)
end
yield
- pkgs = project.packages.npm.with_name(package_name).last_of_each_version.preload_files
+ pkgs = project.packages.npm.with_name(package_name).preload_files
expect do
described_class.new(package_name, pkgs).execute(only_dist_tags: only_dist_tags)
diff --git a/spec/services/packages/nuget/extract_metadata_content_service_spec.rb b/spec/services/packages/nuget/extract_metadata_content_service_spec.rb
new file mode 100644
index 00000000000..ff1b26e8b28
--- /dev/null
+++ b/spec/services/packages/nuget/extract_metadata_content_service_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::ExtractMetadataContentService, feature_category: :package_registry do
+ let(:nuspec_file_content) { fixture_file(nuspec_filepath) }
+
+ let(:service) { described_class.new(nuspec_file_content) }
+
+ describe '#execute' do
+ subject { service.execute.payload }
+
+ context 'with nuspec file content' do
+ context 'with dependencies' do
+ let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' }
+
+ it { is_expected.to have_key(:package_dependencies) }
+
+ it 'extracts dependencies' do
+ dependencies = subject[:package_dependencies]
+
+ expect(dependencies).to include(name: 'Moqi', version: '2.5.6')
+ expect(dependencies).to include(name: 'Castle.Core')
+ expect(dependencies).to include(name: 'Test.Dependency', version: '2.3.7',
+ target_framework: '.NETStandard2.0')
+ expect(dependencies).to include(name: 'Newtonsoft.Json', version: '12.0.3',
+ target_framework: '.NETStandard2.0')
+ end
+ end
+
+ context 'with package types' do
+ let(:nuspec_filepath) { 'packages/nuget/with_package_types.nuspec' }
+
+ it { is_expected.to have_key(:package_types) }
+
+ it 'extracts package types' do
+ expect(subject[:package_types]).to include('SymbolsPackage')
+ end
+ end
+
+ context 'with a nuspec file with metadata' do
+ let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
+
+ it { expect(subject[:package_tags].sort).to eq(%w[foo bar test tag1 tag2 tag3 tag4 tag5].sort) }
+ end
+ end
+
+ context 'with a nuspec file content with metadata' do
+ let_it_be(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
+
+ it 'returns the correct metadata' do
+ expected_metadata = {
+ authors: 'Author Test',
+ description: 'Description Test',
+ license_url: 'https://opensource.org/licenses/MIT',
+ project_url: 'https://gitlab.com/gitlab-org/gitlab',
+ icon_url: 'https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png'
+ }
+
+ expect(subject.slice(*expected_metadata.keys)).to eq(expected_metadata)
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/extract_metadata_file_service_spec.rb b/spec/services/packages/nuget/extract_metadata_file_service_spec.rb
new file mode 100644
index 00000000000..412c22fe8de
--- /dev/null
+++ b/spec/services/packages/nuget/extract_metadata_file_service_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::ExtractMetadataFileService, feature_category: :package_registry do
+ let_it_be(:package_file) { create(:nuget_package).package_files.first }
+
+ let(:service) { described_class.new(package_file.id) }
+
+ describe '#execute' do
+ subject { service.execute }
+
+ shared_examples 'raises an error' do |error_message|
+ it { expect { subject }.to raise_error(described_class::ExtractionError, error_message) }
+ end
+
+ context 'with valid package file id' do
+ expected_metadata = <<~XML.squish
+ <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
+ <metadata>
+ <id>DummyProject.DummyPackage</id>
+ <version>1.0.0</version>
+ <title>Dummy package</title>
+ <authors>Test</authors>
+ <owners>Test</owners>
+ <requireLicenseAcceptance>false</requireLicenseAcceptance>
+ <description>This is a dummy project</description>
+ <dependencies>
+ <group targetFramework=".NETCoreApp3.0">
+ <dependency id="Newtonsoft.Json" version="12.0.3" exclude="Build,Analyzers" />
+ </group>
+ </dependencies>
+ </metadata>
+ </package>
+ XML
+
+ it 'returns the nuspec file content' do
+ expect(subject.payload.squish).to include(expected_metadata)
+ end
+ end
+
+ context 'with invalid package file id' do
+ let(:package_file) { instance_double('Packages::PackageFile', id: 555) }
+
+ it_behaves_like 'raises an error', 'invalid package file'
+ end
+
+ context 'when linked to a non nuget package' do
+ before do
+ package_file.package.maven!
+ end
+
+ it_behaves_like 'raises an error', 'invalid package file'
+ end
+
+ context 'with a 0 byte package file id' do
+ before do
+ allow_next_instance_of(Packages::PackageFileUploader) do |instance|
+ allow(instance).to receive(:size).and_return(0)
+ end
+ end
+
+ it_behaves_like 'raises an error', 'invalid package file'
+ end
+
+ context 'without the nuspec file' do
+ before do
+ allow_next_instance_of(Zip::File) do |instance|
+ allow(instance).to receive(:glob).and_return([])
+ end
+ end
+
+ it_behaves_like 'raises an error', 'nuspec file not found'
+ end
+
+ context 'with a too big nuspec file' do
+ before do
+ allow_next_instance_of(Zip::File) do |instance|
+ allow(instance).to receive(:glob).and_return([instance_double('File', size: 6.megabytes)])
+ end
+ end
+
+ it_behaves_like 'raises an error', 'nuspec file too big'
+ end
+
+ context 'with a corrupted nupkg file with a wrong entry size' do
+ let(:nupkg_fixture_path) { expand_fixture_path('packages/nuget/corrupted_package.nupkg') }
+
+ before do
+ allow(Zip::File).to receive(:new).and_return(Zip::File.new(nupkg_fixture_path, false, false))
+ end
+
+ it_behaves_like 'raises an error',
+ <<~ERROR.squish
+ nuspec file has the wrong entry size: entry 'DummyProject.DummyPackage.nuspec' should be 255B,
+ but is larger when inflated.
+ ERROR
+ end
+ end
+end
diff --git a/spec/services/packages/nuget/metadata_extraction_service_spec.rb b/spec/services/packages/nuget/metadata_extraction_service_spec.rb
index 8954b89971e..c8c06414830 100644
--- a/spec/services/packages/nuget/metadata_extraction_service_spec.rb
+++ b/spec/services/packages/nuget/metadata_extraction_service_spec.rb
@@ -5,17 +5,33 @@ require 'spec_helper'
RSpec.describe Packages::Nuget::MetadataExtractionService, feature_category: :package_registry do
let_it_be(:package_file) { create(:nuget_package).package_files.first }
- let(:service) { described_class.new(package_file.id) }
+ subject { described_class.new(package_file.id) }
describe '#execute' do
- subject { service.execute }
-
- shared_examples 'raises an error' do |error_message|
- it { expect { subject }.to raise_error(described_class::ExtractionError, error_message) }
+ let(:nuspec_file_content) do
+ <<~XML.squish
+ <?xml version="1.0" encoding="utf-8"?>
+ <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
+ <metadata>
+ <id>DummyProject.DummyPackage</id>
+ <version>1.0.0</version>
+ <title>Dummy package</title>
+ <authors>Test</authors>
+ <owners>Test</owners>
+ <requireLicenseAcceptance>false</requireLicenseAcceptance>
+ <description>This is a dummy project</description>
+ <dependencies>
+ <group targetFramework=".NETCoreApp3.0">
+ <dependency id="Newtonsoft.Json" version="12.0.3" exclude="Build,Analyzers" />
+ </group>
+ </dependencies>
+ </metadata>
+ </package>
+ XML
end
- context 'with valid package file id' do
- expected_metadata = {
+ let(:expected_metadata) do
+ {
package_name: 'DummyProject.DummyPackage',
package_version: '1.0.0',
authors: 'Test',
@@ -30,113 +46,21 @@ RSpec.describe Packages::Nuget::MetadataExtractionService, feature_category: :pa
package_tags: [],
package_types: []
}
-
- it { is_expected.to eq(expected_metadata) }
end
- context 'with nuspec file' do
- before do
- allow(service).to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath))
- end
-
- context 'with dependencies' do
- let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' }
-
- it { is_expected.to have_key(:package_dependencies) }
-
- it 'extracts dependencies' do
- dependencies = subject[:package_dependencies]
-
- expect(dependencies).to include(name: 'Moqi', version: '2.5.6')
- expect(dependencies).to include(name: 'Castle.Core')
- expect(dependencies).to include(name: 'Test.Dependency', version: '2.3.7', target_framework: '.NETStandard2.0')
- expect(dependencies).to include(name: 'Newtonsoft.Json', version: '12.0.3', target_framework: '.NETStandard2.0')
- end
- end
-
- context 'with package types' do
- let(:nuspec_filepath) { 'packages/nuget/with_package_types.nuspec' }
-
- it { is_expected.to have_key(:package_types) }
-
- it 'extracts package types' do
- expect(subject[:package_types]).to include('SymbolsPackage')
+ it 'calls the necessary services and executes the metadata extraction' do
+ expect(::Packages::Nuget::ExtractMetadataFileService).to receive(:new).with(package_file.id) do
+ double.tap do |service|
+ expect(service).to receive(:execute).and_return(double(payload: nuspec_file_content))
end
end
- context 'with a nuspec file with metadata' do
- let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
-
- it { expect(subject[:package_tags].sort).to eq(%w(foo bar test tag1 tag2 tag3 tag4 tag5).sort) }
- end
- end
-
- context 'with a nuspec file with metadata' do
- let_it_be(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
-
- before do
- allow(service).to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath))
- end
-
- it 'returns the correct metadata' do
- expected_metadata = {
- authors: 'Author Test',
- description: 'Description Test',
- license_url: 'https://opensource.org/licenses/MIT',
- project_url: 'https://gitlab.com/gitlab-org/gitlab',
- icon_url: 'https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png'
- }
-
- expect(subject.slice(*expected_metadata.keys)).to eq(expected_metadata)
- end
- end
+ expect(::Packages::Nuget::ExtractMetadataContentService).to receive_message_chain(:new, :execute)
+ .with(nuspec_file_content).with(no_args).and_return(double(payload: expected_metadata))
- context 'with invalid package file id' do
- let(:package_file) { double('file', id: 555) }
-
- it_behaves_like 'raises an error', 'invalid package file'
- end
-
- context 'linked to a non nuget package' do
- before do
- package_file.package.maven!
- end
-
- it_behaves_like 'raises an error', 'invalid package file'
- end
-
- context 'with a 0 byte package file id' do
- before do
- allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(0)
- end
-
- it_behaves_like 'raises an error', 'invalid package file'
- end
-
- context 'without the nuspec file' do
- before do
- allow_any_instance_of(Zip::File).to receive(:glob).and_return([])
- end
-
- it_behaves_like 'raises an error', 'nuspec file not found'
- end
-
- context 'with a too big nuspec file' do
- before do
- allow_any_instance_of(Zip::File).to receive(:glob).and_return([double('file', size: 6.megabytes)])
- end
-
- it_behaves_like 'raises an error', 'nuspec file too big'
- end
-
- context 'with a corrupted nupkg file with a wrong entry size' do
- let(:nupkg_fixture_path) { expand_fixture_path('packages/nuget/corrupted_package.nupkg') }
-
- before do
- allow(Zip::File).to receive(:new).and_return(Zip::File.new(nupkg_fixture_path, false, false))
- end
+ metadata = subject.execute.payload
- it_behaves_like 'raises an error', "nuspec file has the wrong entry size: entry 'DummyProject.DummyPackage.nuspec' should be 255B, but is larger when inflated."
+ expect(metadata).to eq(expected_metadata)
end
end
end
diff --git a/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb b/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb
index fa7d994c13c..caa4e42d002 100644
--- a/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb
+++ b/spec/services/packages/nuget/update_package_from_metadata_service_spec.rb
@@ -228,7 +228,7 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
end
end
- it_behaves_like 'raising an', ::Packages::Nuget::MetadataExtractionService::ExtractionError, with_message: 'nuspec file not found'
+ it_behaves_like 'raising an', ::Packages::Nuget::ExtractMetadataFileService::ExtractionError, with_message: 'nuspec file not found'
end
context 'with a symbol package' do
diff --git a/spec/services/personal_access_tokens/last_used_service_spec.rb b/spec/services/personal_access_tokens/last_used_service_spec.rb
index 77ea5e10379..32544b5d6b4 100644
--- a/spec/services/personal_access_tokens/last_used_service_spec.rb
+++ b/spec/services/personal_access_tokens/last_used_service_spec.rb
@@ -43,49 +43,5 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_
expect(subject).to be_nil
end
end
-
- context 'when update_personal_access_token_usage_information_every_10_minutes is disabled' do
- before do
- stub_feature_flags(update_personal_access_token_usage_information_every_10_minutes: false)
- end
-
- context 'when the personal access token was used 1 day ago', :freeze_time do
- let(:personal_access_token) { create(:personal_access_token, last_used_at: 1.day.ago) }
-
- it 'updates the last_used_at timestamp' do
- expect { subject }.to change { personal_access_token.last_used_at }
- end
-
- it 'does not run on read-only GitLab instances' do
- allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
-
- expect { subject }.not_to change { personal_access_token.last_used_at }
- end
- end
-
- context 'when the personal access token was used less than 1 day ago', :freeze_time do
- let(:personal_access_token) { create(:personal_access_token, last_used_at: (1.day - 1.second).ago) }
-
- it 'does not update the last_used_at timestamp' do
- expect { subject }.not_to change { personal_access_token.last_used_at }
- end
- end
-
- context 'when the last_used_at timestamp is nil' do
- let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: nil) }
-
- it 'updates the last_used_at timestamp' do
- expect { subject }.to change { personal_access_token.last_used_at }
- end
- end
-
- context 'when not a personal access token' do
- let_it_be(:personal_access_token) { create(:oauth_access_token) }
-
- it 'does not execute' do
- expect(subject).to be_nil
- end
- end
- end
end
end
diff --git a/spec/services/personal_access_tokens/revoke_token_family_service_spec.rb b/spec/services/personal_access_tokens/revoke_token_family_service_spec.rb
new file mode 100644
index 00000000000..3e32200cc77
--- /dev/null
+++ b/spec/services/personal_access_tokens/revoke_token_family_service_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe PersonalAccessTokens::RevokeTokenFamilyService, feature_category: :system_access do
+ describe '#execute' do
+ let_it_be(:token_3) { create(:personal_access_token, :revoked) }
+ let_it_be(:token_2) { create(:personal_access_token, :revoked, previous_personal_access_token_id: token_3.id) }
+ let_it_be(:token_1) { create(:personal_access_token, previous_personal_access_token_id: token_2.id) }
+
+ subject(:response) { described_class.new(token_3).execute }
+
+ it 'revokes the latest token from the chain of rotated tokens' do
+ expect(response).to be_success
+ expect(token_1.reload).to be_revoked
+ end
+ end
+end
diff --git a/spec/services/personal_access_tokens/rotate_service_spec.rb b/spec/services/personal_access_tokens/rotate_service_spec.rb
index e026b0b6485..522506870f6 100644
--- a/spec/services/personal_access_tokens/rotate_service_spec.rb
+++ b/spec/services/personal_access_tokens/rotate_service_spec.rb
@@ -25,6 +25,13 @@ RSpec.describe PersonalAccessTokens::RotateService, feature_category: :system_ac
expect(new_token).not_to be_revoked
end
+ it 'saves the previous token as previous PAT attribute' do
+ response
+
+ new_token = response.payload[:personal_access_token]
+ expect(new_token.previous_personal_access_token).to eql(token)
+ end
+
context 'when user tries to rotate already revoked token' do
let_it_be(:token, reload: true) { create(:personal_access_token, :revoked) }
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 59db0b47a3c..8a737e4df56 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -1030,17 +1030,17 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :groups_an
end
before do
- group.update_shared_runners_setting!(shared_runners_setting)
+ group.update!(shared_runners_enabled: shared_runners_enabled,
+ allow_descendants_override_disabled_shared_runners: allow_to_override)
user.refresh_authorized_projects # Ensure cache is warm
end
context 'default value based on parent group setting' do
- where(:shared_runners_setting, :desired_config_for_new_project, :expected_result_for_project) do
- Namespace::SR_ENABLED | nil | true
- Namespace::SR_DISABLED_WITH_OVERRIDE | nil | false
- Namespace::SR_DISABLED_AND_OVERRIDABLE | nil | false
- Namespace::SR_DISABLED_AND_UNOVERRIDABLE | nil | false
+ where(:shared_runners_enabled, :allow_to_override, :desired_config_for_new_project, :expected_result_for_project) do
+ true | false | nil | true
+ false | true | nil | false
+ false | false | nil | false
end
with_them do
@@ -1057,14 +1057,12 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :groups_an
end
context 'parent group is present and allows desired config' do
- where(:shared_runners_setting, :desired_config_for_new_project, :expected_result_for_project) do
- Namespace::SR_ENABLED | true | true
- Namespace::SR_ENABLED | false | false
- Namespace::SR_DISABLED_WITH_OVERRIDE | false | false
- Namespace::SR_DISABLED_WITH_OVERRIDE | true | true
- Namespace::SR_DISABLED_AND_OVERRIDABLE | false | false
- Namespace::SR_DISABLED_AND_OVERRIDABLE | true | true
- Namespace::SR_DISABLED_AND_UNOVERRIDABLE | false | false
+ where(:shared_runners_enabled, :allow_to_override, :desired_config_for_new_project, :expected_result_for_project) do
+ true | false | true | true
+ true | false | false | false
+ false | true | false | false
+ false | true | true | true
+ false | false | false | false
end
with_them do
@@ -1080,8 +1078,8 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :groups_an
end
context 'parent group is present and disallows desired config' do
- where(:shared_runners_setting, :desired_config_for_new_project) do
- Namespace::SR_DISABLED_AND_UNOVERRIDABLE | true
+ where(:shared_runners_enabled, :allow_to_override, :desired_config_for_new_project) do
+ false | false | true
end
with_them do
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 7aa6980fb24..ccf58964c71 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -456,14 +456,63 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
end
context 'repository removal' do
- # 1. Project repository
- # 2. Wiki repository
- it 'removal of existing repos' do
- expect_next_instances_of(Repositories::DestroyService, 2) do |instance|
- expect(instance).to receive(:execute).and_return(status: :success)
+ describe '.trash_project_repositories!' do
+ let(:trash_project_repositories!) { described_class.new(project, user, {}).send(:trash_project_repositories!) }
+
+ # Destroys 3 repositories:
+ # 1. Project repository
+ # 2. Wiki repository
+ # 3. Design repository
+
+ it 'Repositories::DestroyService is called for existing repos' do
+ expect_next_instances_of(Repositories::DestroyService, 3) do |instance|
+ expect(instance).to receive(:execute).and_return(status: :success)
+ end
+
+ trash_project_repositories!
end
- described_class.new(project, user, {}).execute
+ context 'when the removal has errors' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:mock_error) { instance_double(Repositories::DestroyService, execute: { message: 'foo', status: :error }) }
+ let(:project_repository) { project.repository }
+ let(:wiki_repository) { project.wiki.repository }
+ let(:design_repository) { project.design_repository }
+
+ where(:repo, :message) do
+ ref(:project_repository) | 'Failed to remove project repository. Please try again or contact administrator.'
+ ref(:wiki_repository) | 'Failed to remove wiki repository. Please try again or contact administrator.'
+ ref(:design_repository) | 'Failed to remove design repository. Please try again or contact administrator.'
+ end
+
+ with_them do
+ before do
+ allow(Repositories::DestroyService).to receive(:new).with(anything).and_call_original
+ allow(Repositories::DestroyService).to receive(:new).with(repo).and_return(mock_error)
+ end
+
+ it 'raises correct error' do
+ expect { trash_project_repositories! }.to raise_error(Projects::DestroyService::DestroyError, message)
+ end
+ end
+ end
+ end
+
+ it 'removes project repository' do
+ expect { destroy_project(project, user, {}) }.to change { project.repository.exists? }.from(true).to(false)
+ end
+
+ it 'removes wiki repository' do
+ project.create_wiki unless project.wiki.repository.exists?
+
+ expect { destroy_project(project, user, {}) }.to change { project.wiki.repository.exists? }.from(true).to(false)
+ end
+
+ it 'removes design repository' do
+ project.design_repository.create_if_not_exists
+
+ expect { destroy_project(project, user, {}) }.to change { project.design_repository.exists? }.from(true).to(false)
end
end
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index e062ee04bf4..a0b14a36106 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Projects::DownloadService, feature_category: :groups_and_projects
@project = create(:project, creator_id: @user.id, namespace: @user.namespace)
end
- context 'for a URL that is not on whitelist' do
+ context 'for a URL that is not on allowlist' do
before do
url = 'https://code.jquery.com/jquery-2.1.4.min.js'
@link_to_file = download_file(@project, url)
@@ -18,7 +18,7 @@ RSpec.describe Projects::DownloadService, feature_category: :groups_and_projects
it { expect(@link_to_file).to eq(nil) }
end
- context 'for URLs that are on the whitelist' do
+ context 'for URLs that are on the allowlist' do
before do
# `ssrf_filter` resolves the hostname. See https://github.com/carrierwaveuploader/carrierwave/commit/91714adda998bc9e8decf5b1f5d260d808761304
stub_request(:get, %r{http://[\d.]+/rails_sample.jpg}).to_return(body: File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'))
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index 2f090577805..04c43dff2dc 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -10,12 +10,18 @@ RSpec.describe Projects::ParticipantsService, feature_category: :groups_and_proj
before_all do
project.add_developer(user)
+
+ stub_feature_flags(disable_all_mention: false)
end
def run_service
described_class.new(project, user).execute(noteable)
end
+ it 'includes `All Project and Group Members`' do
+ expect(run_service).to include(a_hash_including({ username: "all", name: "All Project and Group Members" }))
+ end
+
context 'N+1 checks' do
before do
run_service # warmup, runs table cache queries and create queries
@@ -99,6 +105,16 @@ RSpec.describe Projects::ParticipantsService, feature_category: :groups_and_proj
end
end
end
+
+ context 'when `disable_all_mention` FF is enabled' do
+ before do
+ stub_feature_flags(disable_all_mention: true)
+ end
+
+ it 'does not include `All Project and Group Members`' do
+ expect(run_service).not_to include(a_hash_including({ username: "all", name: "All Project and Group Members" }))
+ end
+ end
end
describe '#project_members' do
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index badbc8b628e..bfcd2be6ce4 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -690,7 +690,7 @@ RSpec.describe Projects::UpdateService, feature_category: :groups_and_projects d
attributes_for(
:prometheus_integration,
project: project,
- properties: { api_url: nil, manual_configuration: "1" }
+ properties: { api_url: 'invalid-url', manual_configuration: "1" }
)
end
diff --git a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb
index fbee4b9c7d7..a5395eed1b4 100644
--- a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb
+++ b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb
@@ -53,7 +53,7 @@ RSpec.describe Prometheus::ProxyVariableSubstitutionService, feature_category: :
end
it_behaves_like 'success' do
- let(:expected_query) { %Q[up{environment="#{environment.slug}"}] }
+ let(:expected_query) { %[up{environment="#{environment.slug}"}] }
end
end
end
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index bd09dae0a5a..186b532233e 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -35,6 +35,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
end
describe '#execute' do
+ let_it_be(:work_item) { create(:work_item, :task, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
shared_examples 'reopen command' do
@@ -301,7 +302,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
it 'returns due_date message: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do
_, _, message = service.execute(content, issuable)
- expect(message).to eq("Set the due date to #{expected_date.to_s(:medium)}.")
+ expect(message).to eq("Set the due date to #{expected_date.to_fs(:medium)}.")
end
end
@@ -538,7 +539,12 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
_, updates, message = service.execute(content, issuable)
expect(updates).to eq(merge: merge_request.diff_head_sha)
- expect(message).to eq('Scheduled to merge this merge request (Merge when pipeline succeeds).')
+
+ if Gitlab.ee?
+ expect(message).to eq('Scheduled to merge this merge request (Merge when checks pass).')
+ else
+ expect(message).to eq('Scheduled to merge this merge request (Merge when pipeline succeeds).')
+ end
end
end
@@ -1369,6 +1375,11 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
let(:issuable) { merge_request }
end
+ it_behaves_like 'done command' do
+ let(:content) { '/done' }
+ let(:issuable) { work_item }
+ end
+
it_behaves_like 'subscribe command' do
let(:content) { '/subscribe' }
let(:issuable) { issue }
@@ -1590,6 +1601,12 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
end
end
+ context 'if issuable is a work item' do
+ it_behaves_like 'todo command' do
+ let(:issuable) { work_item }
+ end
+ end
+
context 'if issuable is a MergeRequest' do
it_behaves_like 'todo command' do
let(:issuable) { merge_request }
@@ -2860,13 +2877,13 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
let_it_be(:project) { create(:project, :private) }
let_it_be(:work_item) { create(:work_item, :task, project: project) }
- let(:command) { '/type Issue' }
+ let(:command) { '/type issue' }
it 'has command available' do
_, explanations = service.explain(command, work_item)
expect(explanations)
- .to contain_exactly("Converts work item to Issue. Widgets not supported in new type are removed.")
+ .to contain_exactly("Converts work item to issue. Widgets not supported in new type are removed.")
end
end
diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb
index 31e4e008d4f..940d9858cdd 100644
--- a/spec/services/resource_access_tokens/create_service_spec.rb
+++ b/spec/services/resource_access_tokens/create_service_spec.rb
@@ -199,16 +199,14 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
end
end
- context 'expiry of the project bot member' do
- it 'project bot membership does not expire' do
- response = subject
- access_token = response.payload[:access_token]
- project_bot = access_token.user
+ it 'project bot membership expires when PAT expires' do
+ response = subject
+ access_token = response.payload[:access_token]
+ project_bot = access_token.user
- expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(
- max_pat_access_token_lifetime.to_date
- )
- end
+ expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(
+ max_pat_access_token_lifetime.to_date
+ )
end
end
diff --git a/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb b/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb
index 3396abaff9e..7c1c5884fd2 100644
--- a/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb
+++ b/spec/services/resource_events/synthetic_label_notes_builder_service_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe ResourceEvents::SyntheticLabelNotesBuilderService, feature_catego
let_it_be(:event3) { create(:resource_label_event, issue: issue) }
it 'returns the expected synthetic notes' do
- notes = ResourceEvents::SyntheticLabelNotesBuilderService.new(issue, user).execute
+ notes = described_class.new(issue, user).execute
expect(notes.size).to eq(3)
end
diff --git a/spec/services/service_desk/custom_emails/create_service_spec.rb b/spec/services/service_desk/custom_emails/create_service_spec.rb
new file mode 100644
index 00000000000..0d9582ba235
--- /dev/null
+++ b/spec/services/service_desk/custom_emails/create_service_spec.rb
@@ -0,0 +1,185 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ServiceDesk::CustomEmails::CreateService, feature_category: :service_desk do
+ describe '#execute' do
+ let_it_be_with_reload(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:service) { described_class.new(project: project, current_user: user, params: params) }
+ let(:error_feature_flag_disabled) { 'Feature flag service_desk_custom_email is not enabled' }
+ let(:error_user_not_authorized) { s_('ServiceDesk|User cannot manage project.') }
+ let(:error_cannot_create_custom_email) { s_('ServiceDesk|Cannot create custom email') }
+ let(:error_custom_email_exists) { s_('ServiceDesk|Custom email already exists') }
+ let(:error_params_missing) { s_('ServiceDesk|Parameters missing') }
+ let(:expected_error_message) { nil }
+ let(:params) { {} }
+ let(:message_delivery) { instance_double(ActionMailer::MessageDelivery) }
+ let(:message) { instance_double(Mail::Message) }
+
+ shared_examples 'a service that exits with error' do
+ it 'exits early' do
+ response = service.execute
+
+ expect(response).to be_error
+ expect(response.message).to eq(expected_error_message)
+ end
+ end
+
+ shared_examples 'a failing service that does not create records' do
+ it 'exits with error and does not create records' do
+ response = service.execute
+ project.reset
+
+ expect(response).to be_error
+ expect(response.message).to eq(expected_error_message)
+ expect(project.service_desk_custom_email_verification).to be nil
+ expect(project.service_desk_custom_email_credential).to be nil
+ expect(project.service_desk_setting).to have_attributes(
+ custom_email: nil,
+ custom_email_enabled: false
+ )
+ end
+ end
+
+ context 'when feature flag service_desk_custom_email is disabled' do
+ let(:expected_error_message) { error_feature_flag_disabled }
+
+ before do
+ stub_feature_flags(service_desk_custom_email: false)
+ end
+
+ it_behaves_like 'a service that exits with error'
+ end
+
+ context 'with illegitimate user' do
+ let(:expected_error_message) { error_user_not_authorized }
+
+ before do
+ stub_member_access_level(project, developer: user)
+ end
+
+ it_behaves_like 'a service that exits with error'
+ end
+
+ context 'with legitimate user' do
+ let!(:settings) { create(:service_desk_setting, project: project) }
+
+ let(:expected_error_message) { error_params_missing }
+
+ before do
+ stub_member_access_level(project, maintainer: user)
+
+ # We send verification email directly and it will fail with
+ # smtp.example.com because it expects a valid DNS record
+ allow(message).to receive(:deliver)
+ allow(Notify).to receive(:service_desk_custom_email_verification_email).and_return(message)
+ end
+
+ it_behaves_like 'a service that exits with error'
+
+ context 'with params but custom_email missing' do
+ let(:params) do
+ {
+ smtp_address: 'smtp.example.com',
+ smtp_port: '587',
+ smtp_username: 'user@example.com',
+ smtp_password: 'supersecret'
+ }
+ end
+
+ it_behaves_like 'a failing service that does not create records'
+ end
+
+ context 'with params but smtp username empty' do
+ let(:params) do
+ {
+ custom_email: 'user@example.com',
+ smtp_address: 'smtp.example.com',
+ smtp_port: '587',
+ smtp_username: nil,
+ smtp_password: 'supersecret'
+ }
+ end
+
+ it_behaves_like 'a failing service that does not create records'
+ end
+
+ context 'with params but smtp password is too short' do
+ let(:expected_error_message) { error_cannot_create_custom_email }
+ let(:params) do
+ {
+ custom_email: 'user@example.com',
+ smtp_address: 'smtp.example.com',
+ smtp_port: '587',
+ smtp_username: 'user@example.com',
+ smtp_password: '2short'
+ }
+ end
+
+ it_behaves_like 'a failing service that does not create records'
+ end
+
+ context 'with params but custom_email is invalid' do
+ let(:expected_error_message) { error_cannot_create_custom_email }
+ let(:params) do
+ {
+ custom_email: 'useratexampledotcom',
+ smtp_address: 'smtp.example.com',
+ smtp_port: '587',
+ smtp_username: 'user@example.com',
+ smtp_password: 'supersecret'
+ }
+ end
+
+ it_behaves_like 'a failing service that does not create records'
+ end
+
+ context 'with full set of params' do
+ let(:params) do
+ {
+ custom_email: 'user@example.com',
+ smtp_address: 'smtp.example.com',
+ smtp_port: '587',
+ smtp_username: 'user@example.com',
+ smtp_password: 'supersecret'
+ }
+ end
+
+ it 'creates all records returns a successful response' do
+ response = service.execute
+ project.reset
+
+ expect(response).to be_success
+
+ expect(project.service_desk_setting).to have_attributes(
+ custom_email: params[:custom_email],
+ custom_email_enabled: false
+ )
+ expect(project.service_desk_custom_email_credential).to have_attributes(
+ smtp_address: params[:smtp_address],
+ smtp_port: params[:smtp_port].to_i,
+ smtp_username: params[:smtp_username],
+ smtp_password: params[:smtp_password]
+ )
+ expect(project.service_desk_custom_email_verification).to have_attributes(
+ state: 'started',
+ triggerer: user,
+ error: nil
+ )
+ end
+
+ context 'when custom email aready exists' do
+ let!(:settings) { create(:service_desk_setting, project: project, custom_email: 'user@example.com') }
+ let!(:credential) { create(:service_desk_custom_email_credential, project: project) }
+ let!(:verification) { create(:service_desk_custom_email_verification, project: project) }
+
+ let(:expected_error_message) { error_custom_email_exists }
+
+ it_behaves_like 'a service that exits with error'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/service_desk/custom_emails/destroy_service_spec.rb b/spec/services/service_desk/custom_emails/destroy_service_spec.rb
new file mode 100644
index 00000000000..f5a22e26865
--- /dev/null
+++ b/spec/services/service_desk/custom_emails/destroy_service_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ServiceDesk::CustomEmails::DestroyService, feature_category: :service_desk do
+ describe '#execute' do
+ let_it_be_with_reload(:project) { create(:project) }
+
+ let(:user) { build_stubbed(:user) }
+ let(:service) { described_class.new(project: project, current_user: user) }
+ let(:error_feature_flag_disabled) { 'Feature flag service_desk_custom_email is not enabled' }
+ let(:error_user_not_authorized) { s_('ServiceDesk|User cannot manage project.') }
+ let(:error_does_not_exist) { s_('ServiceDesk|Custom email does not exist') }
+ let(:expected_error_message) { nil }
+
+ shared_examples 'a service that exits with error' do
+ it 'exits early' do
+ response = service.execute
+
+ expect(response).to be_error
+ expect(response.message).to eq(expected_error_message)
+ end
+ end
+
+ shared_examples 'a successful service that destroys all custom email records' do
+ it 'ensures no custom email records exist' do
+ project.reset
+
+ response = service.execute
+
+ expect(response).to be_success
+ expect(project.service_desk_custom_email_verification).to be nil
+ expect(project.service_desk_custom_email_credential).to be nil
+ expect(project.service_desk_setting).to have_attributes(
+ custom_email: nil,
+ custom_email_enabled: false
+ )
+ end
+ end
+
+ context 'when feature flag service_desk_custom_email is disabled' do
+ let(:expected_error_message) { error_feature_flag_disabled }
+
+ before do
+ stub_feature_flags(service_desk_custom_email: false)
+ end
+
+ it_behaves_like 'a service that exits with error'
+ end
+
+ context 'with illegitimate user' do
+ let(:expected_error_message) { error_user_not_authorized }
+
+ before do
+ stub_member_access_level(project, developer: user)
+ end
+
+ it_behaves_like 'a service that exits with error'
+ end
+
+ context 'with legitimate user' do
+ let(:expected_error_message) { error_does_not_exist }
+
+ before do
+ stub_member_access_level(project, maintainer: user)
+ end
+
+ it_behaves_like 'a service that exits with error'
+
+ context 'when service desk setting exists' do
+ let!(:settings) { create(:service_desk_setting, project: project) }
+
+ it_behaves_like 'a successful service that destroys all custom email records'
+
+ context 'when custom email is present' do
+ let!(:settings) { create(:service_desk_setting, project: project, custom_email: 'user@example.com') }
+
+ it_behaves_like 'a successful service that destroys all custom email records'
+
+ context 'when credential exists' do
+ let!(:credential) { create(:service_desk_custom_email_credential, project: project) }
+
+ it_behaves_like 'a successful service that destroys all custom email records'
+
+ context 'when verification exists' do
+ let!(:verification) { create(:service_desk_custom_email_verification, project: project) }
+
+ it_behaves_like 'a successful service that destroys all custom email records'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/service_desk_settings/update_service_spec.rb b/spec/services/service_desk_settings/update_service_spec.rb
index 342fb2b6b7a..ff564963677 100644
--- a/spec/services/service_desk_settings/update_service_spec.rb
+++ b/spec/services/service_desk_settings/update_service_spec.rb
@@ -10,9 +10,9 @@ RSpec.describe ServiceDeskSettings::UpdateService, feature_category: :service_de
let(:params) { { outgoing_name: 'some name', project_key: 'foo' } }
it 'updates service desk settings' do
- result = described_class.new(settings.project, user, params).execute
+ response = described_class.new(settings.project, user, params).execute
- expect(result[:status]).to eq :success
+ expect(response).to be_success
expect(settings.reload.outgoing_name).to eq 'some name'
expect(settings.reload.project_key).to eq 'foo'
end
@@ -22,9 +22,9 @@ RSpec.describe ServiceDeskSettings::UpdateService, feature_category: :service_de
let(:params) { { project_key: '' } }
it 'sets nil project_key' do
- result = described_class.new(settings.project, user, params).execute
+ response = described_class.new(settings.project, user, params).execute
- expect(result[:status]).to eq :success
+ expect(response).to be_success
expect(settings.reload.project_key).to be_nil
end
end
@@ -33,10 +33,10 @@ RSpec.describe ServiceDeskSettings::UpdateService, feature_category: :service_de
let(:params) { { outgoing_name: 'x' * 256 } }
it 'does not update service desk settings' do
- result = described_class.new(settings.project, user, params).execute
+ response = described_class.new(settings.project, user, params).execute
- expect(result[:status]).to eq :error
- expect(result[:message]).to eq 'Outgoing name is too long (maximum is 255 characters)'
+ expect(response).to be_error
+ expect(response.message).to eq 'Outgoing name is too long (maximum is 255 characters)'
expect(settings.reload.outgoing_name).to eq 'original name'
end
end
diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb
index 6171ca1a8a6..03fcc11b6bd 100644
--- a/spec/services/service_response_spec.rb
+++ b/spec/services/service_response_spec.rb
@@ -214,4 +214,17 @@ RSpec.describe ServiceResponse, feature_category: :shared do
end
end
end
+
+ describe '#deconstruct_keys' do
+ it 'supports pattern matching' do
+ status =
+ case described_class.error(message: 'Bad apple')
+ in { status: Symbol => status }
+ status
+ else
+ raise
+ end
+ expect(status).to eq(:error)
+ end
+ end
end
diff --git a/spec/services/snippets/update_service_spec.rb b/spec/services/snippets/update_service_spec.rb
index b428897ce27..f36560480e3 100644
--- a/spec/services/snippets/update_service_spec.rb
+++ b/spec/services/snippets/update_service_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Snippets::UpdateService, feature_category: :source_code_managemen
let(:options) { base_opts.merge(extra_opts) }
let(:updater) { user }
- let(:service) { Snippets::UpdateService.new(project: project, current_user: updater, params: options, perform_spam_check: true) }
+ let(:service) { described_class.new(project: project, current_user: updater, params: options, perform_spam_check: true) }
subject { service.execute(snippet) }
diff --git a/spec/services/spam/spam_action_service_spec.rb b/spec/services/spam/spam_action_service_spec.rb
index 15cb4977b61..bc73a5cbfaf 100644
--- a/spec/services/spam/spam_action_service_spec.rb
+++ b/spec/services/spam/spam_action_service_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-RSpec.describe Spam::SpamActionService, feature_category: :instance_resiliency do
+RSpec.describe Spam::SpamActionService, feature_category: :instance_resiliency,
+ quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/418757' do
include_context 'includes Spam constants'
let(:issue) { create(:issue, project: project, author: author) }
diff --git a/spec/services/spam/spam_verdict_service_spec.rb b/spec/services/spam/spam_verdict_service_spec.rb
index 6b14cf33041..70f43d82ead 100644
--- a/spec/services/spam/spam_verdict_service_spec.rb
+++ b/spec/services/spam/spam_verdict_service_spec.rb
@@ -136,6 +136,38 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
end
+ context 'if allow_possible_spam user custom attribute is set' do
+ before do
+ UserCustomAttribute.upsert_custom_attributes(
+ [{
+ user_id: user.id,
+ key: 'allow_possible_spam',
+ value: 'does not matter'
+ }]
+ )
+ end
+
+ context 'and a service returns a verdict that should be overridden' do
+ before do
+ allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
+ end
+
+ it 'overrides and renders the override verdict' do
+ is_expected.to eq OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM
+ end
+ end
+
+ context 'and a service returns a verdict that does not need to be overridden' do
+ before do
+ allow(service).to receive(:get_spamcheck_verdict).and_return(ALLOW)
+ end
+
+ it 'does not override and renders the original verdict' do
+ is_expected.to eq ALLOW
+ end
+ end
+ end
+
context 'records metrics' do
let(:histogram) { instance_double(Prometheus::Client::Histogram) }
@@ -237,6 +269,8 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
end
context 'if the endpoint is accessible' do
+ let(:user_scores) { Abuse::UserTrustScore.new(user) }
+
before do
allow(service).to receive(:spamcheck_client).and_return(spam_client)
allow(spam_client).to receive(:spam?).and_return(spam_client_result)
@@ -248,7 +282,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
it 'returns the verdict' do
is_expected.to eq(NOOP)
- expect(user.spam_score).to eq(0.0)
+ expect(user_scores.spam_score).to eq(0.0)
end
end
@@ -259,7 +293,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
context 'the result was evaluated' do
it 'returns the verdict and updates the spam score' do
is_expected.to eq(ALLOW)
- expect(user.spam_score).to be_within(0.000001).of(verdict_score)
+ expect(user_scores.spam_score).to be_within(0.000001).of(verdict_score)
end
end
@@ -268,7 +302,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
it 'returns the verdict and does not update the spam score' do
expect(subject).to eq(ALLOW)
- expect(user.spam_score).to eq(0.0)
+ expect(user_scores.spam_score).to eq(0.0)
end
end
end
@@ -290,7 +324,7 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
with_them do
it "returns expected spam constant and updates the spam score" do
is_expected.to eq(expected)
- expect(user.spam_score).to be_within(0.000001).of(verdict_score)
+ expect(user_scores.spam_score).to be_within(0.000001).of(verdict_score)
end
end
end
@@ -369,7 +403,24 @@ RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency
describe 'issue' do
let(:target) { issue }
- it_behaves_like 'execute spam verdict service'
+ context 'when issue is publicly visible' do
+ before do
+ allow(issue).to receive(:publicly_visible?).and_return(true)
+ end
+
+ it_behaves_like 'execute spam verdict service'
+ end
+
+ context 'when issue is not publicly visible' do
+ before do
+ allow(issue).to receive(:publicly_visible?).and_return(false)
+ allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER)
+ end
+
+ it 'overrides and renders the override verdict' do
+ expect(service.execute).to eq OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM
+ end
+ end
end
describe 'snippet' do
diff --git a/spec/services/system_notes/time_tracking_service_spec.rb b/spec/services/system_notes/time_tracking_service_spec.rb
index 71228050085..a3793880ff1 100644
--- a/spec/services/system_notes/time_tracking_service_spec.rb
+++ b/spec/services/system_notes/time_tracking_service_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann
context 'when both dates are added' do
it 'sets the correct note message' do
- expect(note.note).to eq("changed start date to #{start_date.to_s(:long)} and changed due date to #{due_date.to_s(:long)}")
+ expect(note.note).to eq("changed start date to #{start_date.to_fs(:long)} and changed due date to #{due_date.to_fs(:long)}")
end
end
@@ -37,7 +37,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann
end
it 'sets the correct note message' do
- expect(note.note).to eq("removed start date #{start_date.to_s(:long)} and removed due date #{due_date.to_s(:long)}")
+ expect(note.note).to eq("removed start date #{start_date.to_fs(:long)} and removed due date #{due_date.to_fs(:long)}")
end
end
@@ -45,14 +45,14 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann
let(:changed_dates) { { 'due_date' => [nil, due_date] } }
it 'sets the correct note message' do
- expect(note.note).to eq("changed due date to #{due_date.to_s(:long)}")
+ expect(note.note).to eq("changed due date to #{due_date.to_fs(:long)}")
end
context 'and start date removed' do
let(:changed_dates) { { 'due_date' => [nil, due_date], 'start_date' => [start_date, nil] } }
it 'sets the correct note message' do
- expect(note.note).to eq("removed start date #{start_date.to_s(:long)} and changed due date to #{due_date.to_s(:long)}")
+ expect(note.note).to eq("removed start date #{start_date.to_fs(:long)} and changed due date to #{due_date.to_fs(:long)}")
end
end
end
@@ -73,14 +73,14 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann
end
it 'sets the correct note message' do
- expect(note.note).to eq("changed start date to #{start_date.to_s(:long)}")
+ expect(note.note).to eq("changed start date to #{start_date.to_fs(:long)}")
end
context 'and due date removed' do
let(:changed_dates) { { 'due_date' => [due_date, nil], 'start_date' => [nil, start_date] } }
it 'sets the correct note message' do
- expect(note.note).to eq("changed start date to #{start_date.to_s(:long)} and removed due date #{due_date.to_s(:long)}")
+ expect(note.note).to eq("changed start date to #{start_date.to_fs(:long)} and removed due date #{due_date.to_fs(:long)}")
end
end
end
diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb
index 31f97edbd08..35c827d5448 100644
--- a/spec/services/test_hooks/project_service_spec.rb
+++ b/spec/services/test_hooks/project_service_spec.rb
@@ -207,5 +207,26 @@ RSpec.describe TestHooks::ProjectService, feature_category: :code_testing do
expect(service.execute).to include(success_result)
end
end
+
+ context 'emoji' do
+ let(:trigger) { 'emoji_events' }
+ let(:trigger_key) { :emoji_hooks }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to have_attributes(status: :error, message: 'Ensure the project has notes.')
+ end
+
+ it 'executes hook' do
+ note = create(:note)
+ allow(project).to receive_message_chain(:notes, :any?).and_return(true)
+ allow(project).to receive_message_chain(:notes, :last).and_return(note)
+ allow(Gitlab::DataBuilder::Emoji).to receive(:build).with(anything, current_user, 'award')
+ .and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger_key, force: true).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
end
end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 1ec6a3250fc..32e17df4d69 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -717,6 +717,57 @@ RSpec.describe TodoService, feature_category: :team_planning do
end
end
+ describe 'Work Items' do
+ let_it_be(:work_item) { create(:work_item, :task, project: project, author: author) }
+
+ describe '#mark_todo' do
+ it 'creates a todo from a work item' do
+ service.mark_todo(work_item, author)
+
+ should_create_todo(user: author, target: work_item, action: Todo::MARKED)
+ end
+ end
+
+ describe '#todo_exists?' do
+ it 'returns false when no todo exist for the given work_item' do
+ expect(service.todo_exist?(work_item, author)).to be_falsy
+ end
+
+ it 'returns true when a todo exist for the given work_item' do
+ service.mark_todo(work_item, author)
+
+ expect(service.todo_exist?(work_item, author)).to be_truthy
+ end
+ end
+
+ describe '#resolve_todos_for_target' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: work_item, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: work_item, author: author)
+
+ service.resolve_todos_for_target(work_item, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+
+ describe 'cached counts' do
+ it 'updates when todos change' do
+ create(:todo, :assigned, user: john_doe, project: project, target: work_item, author: author)
+
+ expect(john_doe.todos_done_count).to eq(0)
+ expect(john_doe.todos_pending_count).to eq(1)
+ expect(john_doe).to receive(:update_todos_count_cache).and_call_original
+
+ service.resolve_todos_for_target(work_item, john_doe)
+
+ expect(john_doe.todos_done_count).to eq(1)
+ expect(john_doe.todos_pending_count).to eq(0)
+ end
+ end
+ end
+ end
+
describe '#reassigned_assignable' do
let(:described_method) { :reassigned_assignable }
diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb
index 563af8e7e9e..a50bd3ee2f1 100644
--- a/spec/services/user_project_access_changed_service_spec.rb
+++ b/spec/services/user_project_access_changed_service_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe UserProjectAccessChangedService, feature_category: :system_access
end
context 'with load balancing enabled' do
- let(:service) { UserProjectAccessChangedService.new([1, 2]) }
+ let(:service) { described_class.new([1, 2]) }
before do
expect(AuthorizedProjectsWorker).to receive(:bulk_perform_async)
@@ -81,7 +81,7 @@ RSpec.describe UserProjectAccessChangedService, feature_category: :system_access
service.execute
end
- service = UserProjectAccessChangedService.new([1, 2, 3, 4, 5])
+ service = described_class.new([1, 2, 3, 4, 5])
allow(AuthorizedProjectsWorker).to receive(:bulk_perform_async)
.with([[1], [2], [3], [4], [5]])
diff --git a/spec/services/users/allow_possible_spam_service_spec.rb b/spec/services/users/allow_possible_spam_service_spec.rb
new file mode 100644
index 00000000000..53618f0c8e9
--- /dev/null
+++ b/spec/services/users/allow_possible_spam_service_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::AllowPossibleSpamService, feature_category: :user_management do
+ let_it_be(:current_user) { create(:admin) }
+
+ subject(:service) { described_class.new(current_user) }
+
+ describe '#execute' do
+ let(:user) { create(:user) }
+
+ subject(:operation) { service.execute(user) }
+
+ it 'updates the custom attributes', :aggregate_failures do
+ expect(user.custom_attributes).to be_empty
+
+ operation
+ user.reload
+
+ expect(user.custom_attributes.by_key(UserCustomAttribute::ALLOW_POSSIBLE_SPAM)).to be_present
+ end
+ end
+end
diff --git a/spec/services/users/ban_service_spec.rb b/spec/services/users/ban_service_spec.rb
index 5be5de82e91..7e342340f88 100644
--- a/spec/services/users/ban_service_spec.rb
+++ b/spec/services/users/ban_service_spec.rb
@@ -38,6 +38,13 @@ RSpec.describe Users::BanService, feature_category: :user_management do
ban_user
end
+
+ it 'tracks the event', :experiment do
+ expect(experiment(:phone_verification_for_low_risk_users))
+ .to track(:banned).on_next_instance.with_context(user: user)
+
+ ban_user
+ end
end
context 'when failed' do
diff --git a/spec/services/users/disallow_possible_spam_service_spec.rb b/spec/services/users/disallow_possible_spam_service_spec.rb
new file mode 100644
index 00000000000..32a47e05525
--- /dev/null
+++ b/spec/services/users/disallow_possible_spam_service_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::DisallowPossibleSpamService, feature_category: :user_management do
+ let_it_be(:current_user) { create(:admin) }
+
+ subject(:service) { described_class.new(current_user) }
+
+ describe '#execute' do
+ let(:user) { create(:user) }
+
+ subject(:operation) { service.execute(user) }
+
+ before do
+ UserCustomAttribute.upsert_custom_attributes(
+ [{
+ user_id: user.id,
+ key: :allow_possible_spam,
+ value: 'not important'
+ }]
+ )
+ end
+
+ it 'updates the custom attributes', :aggregate_failures do
+ expect(user.custom_attributes.by_key(UserCustomAttribute::ALLOW_POSSIBLE_SPAM)).to be_present
+
+ operation
+ user.reload
+
+ expect(user.custom_attributes).to be_empty
+ end
+ end
+end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 2aa62f932ed..fb7d487b29b 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -69,20 +69,23 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state,
end
describe '#execute' do
- let!(:uuid) { SecureRandom.uuid }
+ let(:uuid) { SecureRandom.uuid }
+ let!(:recursion_uuid) { SecureRandom.uuid }
let(:headers) do
{
'Content-Type' => 'application/json',
'User-Agent' => "GitLab/#{Gitlab::VERSION}",
+ 'X-Gitlab-Webhook-UUID' => uuid,
'X-Gitlab-Event' => 'Push Hook',
- 'X-Gitlab-Event-UUID' => uuid,
+ 'X-Gitlab-Event-UUID' => recursion_uuid,
'X-Gitlab-Instance' => Gitlab.config.gitlab.base_url
}
end
before do
- # Set a stable value for the `X-Gitlab-Event-UUID` header.
- Gitlab::WebHooks::RecursionDetection.set_request_uuid(uuid)
+ # Set stable values for the `X-Gitlab-Webhook-UUID` and `X-Gitlab-Event-UUID` headers.
+ allow(SecureRandom).to receive(:uuid).and_return(uuid)
+ Gitlab::WebHooks::RecursionDetection.set_request_uuid(recursion_uuid)
end
context 'when there is an interpolation error' do
diff --git a/spec/services/webauthn/authenticate_service_spec.rb b/spec/services/webauthn/authenticate_service_spec.rb
index ca940dff0eb..99b8c7b0b36 100644
--- a/spec/services/webauthn/authenticate_service_spec.rb
+++ b/spec/services/webauthn/authenticate_service_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Webauthn::AuthenticateService, feature_category: :system_access d
get_result = client.get(challenge: challenge)
get_result['clientExtensionResults'] = {}
- service = Webauthn::AuthenticateService.new(user, get_result.to_json, challenge)
+ service = described_class.new(user, get_result.to_json, challenge)
expect(service.execute).to eq true
end
@@ -41,7 +41,7 @@ RSpec.describe Webauthn::AuthenticateService, feature_category: :system_access d
get_result = other_client.get(challenge: challenge)
get_result['clientExtensionResults'] = {}
- service = Webauthn::AuthenticateService.new(user, get_result.to_json, challenge)
+ service = described_class.new(user, get_result.to_json, challenge)
expect(service.execute).to eq false
end
@@ -49,7 +49,7 @@ RSpec.describe Webauthn::AuthenticateService, feature_category: :system_access d
context 'when device response includes invalid json' do
it 'returns false' do
- service = Webauthn::AuthenticateService.new(user, 'invalid JSON', '')
+ service = described_class.new(user, 'invalid JSON', '')
expect(service.execute).to eq false
end
end
diff --git a/spec/services/webauthn/register_service_spec.rb b/spec/services/webauthn/register_service_spec.rb
index 2286d261e94..734e8444b5d 100644
--- a/spec/services/webauthn/register_service_spec.rb
+++ b/spec/services/webauthn/register_service_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Webauthn::RegisterService, feature_category: :system_access do
webauthn_credential = WebAuthn::Credential.from_create(create_result)
params = { device_response: create_result.to_json, name: 'abc' }
- service = Webauthn::RegisterService.new(user, params, challenge)
+ service = described_class.new(user, params, challenge)
registration = service.execute
expect(registration.credential_xid).to eq(Base64.strict_encode64(webauthn_credential.raw_id))
@@ -27,7 +27,7 @@ RSpec.describe Webauthn::RegisterService, feature_category: :system_access do
create_result = client.create(challenge: Base64.strict_encode64(SecureRandom.random_bytes(16))) # rubocop:disable Rails/SaveBang
params = { device_response: create_result.to_json, name: 'abc' }
- service = Webauthn::RegisterService.new(user, params, challenge)
+ service = described_class.new(user, params, challenge)
registration = service.execute
expect(registration.errors.size).to eq(1)
diff --git a/spec/services/work_items/export_csv_service_spec.rb b/spec/services/work_items/export_csv_service_spec.rb
index 948ff89245e..4566289231f 100644
--- a/spec/services/work_items/export_csv_service_spec.rb
+++ b/spec/services/work_items/export_csv_service_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe WorkItems::ExportCsvService, :with_license, feature_category: :te
end
specify 'created_at' do
- expect(csv[0]['Created At (UTC)']).to eq(work_item_1.created_at.to_s(:csv))
+ expect(csv[0]['Created At (UTC)']).to eq(work_item_1.created_at.to_fs(:csv))
end
specify 'description' do
diff --git a/spec/services/work_items/update_service_spec.rb b/spec/services/work_items/update_service_spec.rb
index 30c16458353..8e19650d980 100644
--- a/spec/services/work_items/update_service_spec.rb
+++ b/spec/services/work_items/update_service_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
context 'when work item type is not the default Issue' do
before do
task_type = WorkItems::Type.default_by_type(:task)
- work_item.update_columns(issue_type: task_type.base_type, work_item_type_id: task_type.id)
+ work_item.update_columns(work_item_type_id: task_type.id)
end
it 'does not apply the quick action' do
@@ -55,7 +55,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
end
context 'when work item type is the default Issue' do
- let(:issue) { create(:work_item, :issue, description: '') }
+ let(:issue) { create(:work_item, description: '') }
it 'applies the quick action' do
expect do
@@ -383,6 +383,38 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
end
end
end
+
+ context 'for current user todos widget' do
+ let_it_be(:user_todo) { create(:todo, target: work_item, user: developer, project: project, state: :pending) }
+ let_it_be(:other_todo) { create(:todo, target: work_item, user: create(:user), project: project, state: :pending) }
+
+ context 'when action is mark_as_done' do
+ let(:widget_params) { { current_user_todos_widget: { action: 'mark_as_done' } } }
+
+ it 'marks current user todo as done' do
+ expect do
+ update_work_item
+ user_todo.reload
+ other_todo.reload
+ end.to change(user_todo, :state).from('pending').to('done').and not_change { other_todo.state }
+ end
+
+ it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
+ subject(:execute_service) { update_work_item }
+ end
+ end
+
+ context 'when action is add' do
+ let(:widget_params) { { current_user_todos_widget: { action: 'add' } } }
+
+ it 'adds a ToDo for the work item' do
+ expect do
+ update_work_item
+ work_item.reload
+ end.to change(Todo, :count).by(1)
+ end
+ end
+ end
end
describe 'label updates' do
diff --git a/spec/services/work_items/widgets/current_user_todos_service/update_service_spec.rb b/spec/services/work_items/widgets/current_user_todos_service/update_service_spec.rb
index 85b7e7a70df..aa7257e9e62 100644
--- a/spec/services/work_items/widgets/current_user_todos_service/update_service_spec.rb
+++ b/spec/services/work_items/widgets/current_user_todos_service/update_service_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe WorkItems::Widgets::CurrentUserTodosService::UpdateService, featu
todo = current_user.todos.last
- expect(todo.target).to eq(work_item)
+ expect(todo.target.id).to eq(work_item.id)
expect(todo).to be_pending
end
end