From e4384360a16dd9a19d4d2d25d0ef1f2b862ed2a6 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 19 Jul 2023 14:16:28 +0000 Subject: Add latest changes from gitlab-org/gitlab@16-2-stable-ee --- .../admin/plan_limits/update_service_spec.rb | 24 ++- .../alerts/todo/create_service_spec.rb | 2 +- .../application_settings/update_service_spec.rb | 9 + ...dependency_proxy_authentication_service_spec.rb | 2 +- .../merge_when_pipeline_succeeds_service_spec.rb | 171 ++----------------- spec/services/auto_merge_service_spec.rb | 21 ++- spec/services/award_emojis/add_service_spec.rb | 6 + spec/services/award_emojis/base_service_spec.rb | 37 ++++- spec/services/award_emojis/destroy_service_spec.rb | 6 + spec/services/boards/issues/list_service_spec.rb | 10 +- spec/services/boards/lists/list_service_spec.rb | 6 +- .../ci/create_pipeline_service/rules_spec.rb | 36 ---- spec/services/ci/create_pipeline_service_spec.rb | 4 +- .../atomic_processing_service_spec.rb | 28 ---- ..._build_fails_test_skips_rollback_on_failure.yml | 47 ++++++ .../ci/pipeline_schedules/create_service_spec.rb | 86 ++++++++++ .../ci/pipeline_schedules/update_service_spec.rb | 55 +++++- spec/services/ci/pipeline_trigger_service_spec.rb | 4 +- .../ci/process_sync_events_service_spec.rb | 4 +- spec/services/ci/register_job_service_spec.rb | 4 +- .../ci/runners/register_runner_service_spec.rb | 4 +- .../clusters/agent_tokens/create_service_spec.rb | 15 -- .../agents/authorize_proxy_user_service_spec.rb | 16 ++ .../prometheus_health_check_service_spec.rb | 115 ------------- .../generate_image_versions_service_spec.rb | 2 +- spec/services/draft_notes/create_service_spec.rb | 14 ++ spec/services/environments/create_service_spec.rb | 3 +- spec/services/environments/update_service_spec.rb | 15 ++ .../error_tracking/issue_details_service_spec.rb | 33 +++- .../issue_latest_event_service_spec.rb | 35 ++-- .../error_tracking/issue_update_service_spec.rb | 40 ++++- .../error_tracking/list_issues_service_spec.rb | 88 ++++++++-- spec/services/git/base_hooks_service_spec.rb | 17 +- spec/services/git/branch_hooks_service_spec.rb | 1 + spec/services/git/tag_hooks_service_spec.rb | 1 + spec/services/groups/participants_service_spec.rb | 73 ++++++-- spec/services/groups/update_service_spec.rb | 14 +- .../groups/update_shared_runners_service_spec.rb | 183 ++++++++++++++------ spec/services/import/github_service_spec.rb | 44 ++++- .../preprocess_milestones_service_spec.rb | 83 +++++++++ .../after_update_service_spec.rb | 2 +- .../integrations/group_mention_service_spec.rb | 143 ++++++++++++++++ .../issuable/discussions_list_service_spec.rb | 2 +- .../issuable/import_csv/base_service_spec.rb | 92 ++++++++++ spec/services/issuable/process_assignees_spec.rb | 14 +- spec/services/issues/build_service_spec.rb | 1 - spec/services/issues/create_service_spec.rb | 29 ++++ .../relative_position_rebalancing_service_spec.rb | 93 +++++++++++ spec/services/issues/reopen_service_spec.rb | 6 + spec/services/members/invite_service_spec.rb | 12 ++ .../merge_requests/after_create_service_spec.rb | 6 + .../merge_requests/cleanup_refs_service_spec.rb | 2 + .../merge_orchestration_service_spec.rb | 18 +- .../merge_requests/merge_to_ref_service_spec.rb | 12 -- .../mergeability_check_batch_service_spec.rb | 46 +++++ .../merge_requests/refresh_service_spec.rb | 45 +++++ .../services/merge_requests/reopen_service_spec.rb | 4 + .../services/merge_requests/update_service_spec.rb | 29 ++-- .../dashboard/cluster_dashboard_service_spec.rb | 2 +- spec/services/milestones/create_service_spec.rb | 68 ++++++-- spec/services/milestones/update_service_spec.rb | 90 +++++++--- .../namespace_settings/update_service_spec.rb | 16 ++ spec/services/notes/post_process_service_spec.rb | 3 + spec/services/notes/quick_actions_service_spec.rb | 4 +- spec/services/notification_service_spec.rb | 4 +- .../cleanup/execute_policy_service_spec.rb | 4 +- .../debian/find_or_create_package_service_spec.rb | 83 --------- .../debian/process_changes_service_spec.rb | 140 ---------------- .../npm/create_metadata_cache_service_spec.rb | 2 +- .../packages/npm/create_package_service_spec.rb | 14 +- .../packages/npm/generate_metadata_service_spec.rb | 6 +- .../nuget/extract_metadata_content_service_spec.rb | 64 +++++++ .../nuget/extract_metadata_file_service_spec.rb | 100 +++++++++++ .../nuget/metadata_extraction_service_spec.rb | 138 ++++----------- .../update_package_from_metadata_service_spec.rb | 2 +- .../last_used_service_spec.rb | 44 ----- .../revoke_token_family_service_spec.rb | 18 ++ .../personal_access_tokens/rotate_service_spec.rb | 7 + spec/services/projects/create_service_spec.rb | 30 ++-- spec/services/projects/destroy_service_spec.rb | 61 ++++++- spec/services/projects/download_service_spec.rb | 4 +- .../services/projects/participants_service_spec.rb | 16 ++ spec/services/projects/update_service_spec.rb | 2 +- .../proxy_variable_substitution_service_spec.rb | 2 +- .../quick_actions/interpret_service_spec.rb | 25 ++- .../resource_access_tokens/create_service_spec.rb | 16 +- .../synthetic_label_notes_builder_service_spec.rb | 2 +- .../custom_emails/create_service_spec.rb | 185 +++++++++++++++++++++ .../custom_emails/destroy_service_spec.rb | 95 +++++++++++ .../service_desk_settings/update_service_spec.rb | 14 +- spec/services/service_response_spec.rb | 13 ++ spec/services/snippets/update_service_spec.rb | 2 +- spec/services/spam/spam_action_service_spec.rb | 3 +- spec/services/spam/spam_verdict_service_spec.rb | 61 ++++++- .../system_notes/time_tracking_service_spec.rb | 12 +- spec/services/test_hooks/project_service_spec.rb | 21 +++ spec/services/todo_service_spec.rb | 51 ++++++ .../user_project_access_changed_service_spec.rb | 4 +- .../users/allow_possible_spam_service_spec.rb | 24 +++ spec/services/users/ban_service_spec.rb | 7 + .../users/disallow_possible_spam_service_spec.rb | 34 ++++ spec/services/web_hook_service_spec.rb | 11 +- .../services/webauthn/authenticate_service_spec.rb | 6 +- spec/services/webauthn/register_service_spec.rb | 4 +- .../services/work_items/export_csv_service_spec.rb | 2 +- spec/services/work_items/update_service_spec.rb | 36 +++- .../update_service_spec.rb | 2 +- 107 files changed, 2424 insertions(+), 1044 deletions(-) create mode 100644 spec/services/ci/pipeline_processing/test_cases/dag_build_fails_test_skips_rollback_on_failure.yml create mode 100644 spec/services/ci/pipeline_schedules/create_service_spec.rb delete mode 100644 spec/services/clusters/integrations/prometheus_health_check_service_spec.rb create mode 100644 spec/services/import_csv/preprocess_milestones_service_spec.rb create mode 100644 spec/services/integrations/group_mention_service_spec.rb create mode 100644 spec/services/issuable/import_csv/base_service_spec.rb create mode 100644 spec/services/merge_requests/mergeability_check_batch_service_spec.rb delete mode 100644 spec/services/packages/debian/find_or_create_package_service_spec.rb delete mode 100644 spec/services/packages/debian/process_changes_service_spec.rb create mode 100644 spec/services/packages/nuget/extract_metadata_content_service_spec.rb create mode 100644 spec/services/packages/nuget/extract_metadata_file_service_spec.rb create mode 100644 spec/services/personal_access_tokens/revoke_token_family_service_spec.rb create mode 100644 spec/services/service_desk/custom_emails/create_service_spec.rb create mode 100644 spec/services/service_desk/custom_emails/destroy_service_spec.rb create mode 100644 spec/services/users/allow_possible_spam_service_spec.rb create mode 100644 spec/services/users/disallow_possible_spam_service_spec.rb (limited to 'spec/services') 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 + + + DummyProject.DummyPackage + 1.0.0 + Dummy package + Test + Test + false + This is a dummy project + + + + + + + + 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 + + + + DummyProject.DummyPackage + 1.0.0 + Dummy package + Test + Test + false + This is a dummy project + + + + + + + + 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 -- cgit v1.2.3