diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 19:05:49 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 19:05:49 +0300 |
commit | 43a25d93ebdabea52f99b05e15b06250cd8f07d7 (patch) | |
tree | dceebdc68925362117480a5d672bcff122fb625b /spec/services | |
parent | 20c84b99005abd1c82101dfeff264ac50d2df211 (diff) |
Add latest changes from gitlab-org/gitlab@16-0-stable-eev16.0.0-rc42
Diffstat (limited to 'spec/services')
869 files changed, 11783 insertions, 4173 deletions
diff --git a/spec/services/access_token_validation_service_spec.rb b/spec/services/access_token_validation_service_spec.rb index 2bf74d64dc9..4cdce094358 100644 --- a/spec/services/access_token_validation_service_spec.rb +++ b/spec/services/access_token_validation_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AccessTokenValidationService do +RSpec.describe AccessTokenValidationService, feature_category: :system_access do describe ".include_any_scope?" do let(:request) { double("request") } diff --git a/spec/services/achievements/award_service_spec.rb b/spec/services/achievements/award_service_spec.rb new file mode 100644 index 00000000000..c70c1d5c22d --- /dev/null +++ b/spec/services/achievements/award_service_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Achievements::AwardService, feature_category: :user_profile do + describe '#execute' do + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + let_it_be(:recipient) { create(:user) } + + let(:achievement_id) { achievement.id } + let(:recipient_id) { recipient.id } + + subject(:response) { described_class.new(current_user, achievement_id, recipient_id).execute } + + before_all do + group.add_developer(developer) + group.add_maintainer(maintainer) + end + + context 'when user does not have permission' do + let(:current_user) { developer } + + it 'returns an error' do + expect(response).to be_error + expect(response.message).to match_array( + ['You have insufficient permissions to award this achievement']) + end + end + + context 'when user has permission' do + let(:current_user) { maintainer } + let(:notification_service) { instance_double(NotificationService) } + let(:mail_message) { instance_double(ActionMailer::MessageDelivery) } + + it 'creates an achievement and sends an e-mail' do + allow(NotificationService).to receive(:new).and_return(notification_service) + expect(notification_service).to receive(:new_achievement_email).with(recipient, achievement) + .and_return(mail_message) + expect(mail_message).to receive(:deliver_later) + + expect(response).to be_success + end + + context 'when the achievement is not persisted' do + let(:user_achievement) { instance_double('Achievements::UserAchievement') } + + it 'returns the correct error' do + allow(user_achievement).to receive(:persisted?).and_return(false) + allow(user_achievement).to receive(:errors).and_return(nil) + allow(Achievements::UserAchievement).to receive(:create).and_return(user_achievement) + + expect(response).to be_error + expect(response.message).to match_array(["Failed to award achievement"]) + end + end + + context 'when the achievement does not exist' do + let(:achievement_id) { non_existing_record_id } + + it 'returns the correct error' do + expect(response).to be_error + expect(response.message) + .to contain_exactly("Couldn't find Achievements::Achievement with 'id'=#{non_existing_record_id}") + end + end + + context 'when the recipient does not exist' do + let(:recipient_id) { non_existing_record_id } + + it 'returns the correct error' do + expect(response).to be_error + expect(response.message).to contain_exactly("Couldn't find User with 'id'=#{non_existing_record_id}") + end + end + end + end +end diff --git a/spec/services/achievements/destroy_service_spec.rb b/spec/services/achievements/destroy_service_spec.rb new file mode 100644 index 00000000000..7af10ceec6a --- /dev/null +++ b/spec/services/achievements/destroy_service_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Achievements::DestroyService, feature_category: :user_profile do + describe '#execute' do + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:group) { create(:group) } + + let(:achievement) { create(:achievement, namespace: group) } + + subject(:response) { described_class.new(current_user, achievement).execute } + + before_all do + group.add_developer(developer) + group.add_maintainer(maintainer) + end + + context 'when user does not have permission' do + let(:current_user) { developer } + + it 'returns an error' do + expect(response).to be_error + expect(response.message).to match_array( + ['You have insufficient permissions to delete this achievement']) + end + end + + context 'when user has permission' do + let(:current_user) { maintainer } + + it 'deletes the achievement' do + expect(response).to be_success + expect(Achievements::Achievement.find_by(id: achievement.id)).to be_nil + end + end + end +end diff --git a/spec/services/achievements/revoke_service_spec.rb b/spec/services/achievements/revoke_service_spec.rb new file mode 100644 index 00000000000..c9925f1e3df --- /dev/null +++ b/spec/services/achievements/revoke_service_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Achievements::RevokeService, feature_category: :user_profile do + describe '#execute' do + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + let_it_be(:user_achievement) { create(:user_achievement, achievement: achievement) } + + let(:user_achievement_param) { user_achievement } + + subject(:response) { described_class.new(current_user, user_achievement_param).execute } + + before_all do + group.add_developer(developer) + group.add_maintainer(maintainer) + end + + context 'when user does not have permission' do + let(:current_user) { developer } + + it 'returns an error' do + expect(response).to be_error + expect(response.message).to match_array( + ['You have insufficient permissions to revoke this achievement']) + end + end + + context 'when user has permission' do + let(:current_user) { maintainer } + + it 'revokes an achievement' do + expect(response).to be_success + end + + context 'when the achievement has already been revoked' do + let_it_be(:revoked_achievement) { create(:user_achievement, :revoked, achievement: achievement) } + let(:user_achievement_param) { revoked_achievement } + + it 'returns the correct error' do + expect(response).to be_error + expect(response.message) + .to contain_exactly('This achievement has already been revoked') + end + end + + context 'when the user achievement fails to save' do + let(:user_achievement_param) { instance_double('Achievements::UserAchievement') } + + it 'returns the correct error' do + allow(user_achievement_param).to receive(:save).and_return(false) + allow(user_achievement_param).to receive(:achievement).and_return(achievement) + allow(user_achievement_param).to receive(:revoked?).and_return(false) + allow(user_achievement_param).to receive(:errors).and_return(nil) + expect(user_achievement_param).to receive(:assign_attributes) + + expect(response).to be_error + expect(response.message).to match_array(["Failed to revoke achievement"]) + end + end + end + end +end diff --git a/spec/services/achievements/update_service_spec.rb b/spec/services/achievements/update_service_spec.rb new file mode 100644 index 00000000000..6168d60450b --- /dev/null +++ b/spec/services/achievements/update_service_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Achievements::UpdateService, feature_category: :user_profile do + describe '#execute' do + let_it_be(:user) { create(:user) } + + let(:params) { attributes_for(:achievement, namespace: group) } + + subject(:response) { described_class.new(user, group, params).execute } + + context 'when user does not have permission' do + let_it_be(:group) { create(:group) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + + before_all do + group.add_developer(user) + end + + it 'returns an error' do + expect(response).to be_error + expect(response.message).to match_array( + ['You have insufficient permission to update this achievement']) + end + end + + context 'when user has permission' do + let_it_be(:group) { create(:group) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + + before_all do + group.add_maintainer(user) + end + + it 'updates an achievement' do + expect(response).to be_success + end + + it 'returns an error when the achievement cannot be updated' do + params[:name] = nil + + expect(response).to be_error + expect(response.message).to include("Name can't be blank") + end + end + end +end diff --git a/spec/services/admin/abuse_report_update_service_spec.rb b/spec/services/admin/abuse_report_update_service_spec.rb new file mode 100644 index 00000000000..e85b516b87f --- /dev/null +++ b/spec/services/admin/abuse_report_update_service_spec.rb @@ -0,0 +1,199 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Admin::AbuseReportUpdateService, feature_category: :instance_resiliency do + let_it_be_with_reload(:abuse_report) { create(:abuse_report) } + let(:action) { 'ban_user' } + let(:close) { true } + let(:reason) { 'spam' } + let(:params) { { user_action: action, close: close, reason: reason, comment: 'obvious spam' } } + let_it_be(:admin) { create(:admin) } + + let(:service) { described_class.new(abuse_report, admin, params) } + + describe '#execute', :enable_admin_mode do + subject { service.execute } + + shared_examples 'returns an error response' do |error| + it 'returns an error response' do + expect(subject.status).to eq :error + expect(subject.message).to eq error + end + end + + shared_examples 'closes the report' do + it 'closes the report' do + expect { subject }.to change { abuse_report.closed? }.from(false).to(true) + end + end + + shared_examples 'does not close the report' do + it 'does not close the report' do + subject + expect(abuse_report.closed?).to be(false) + end + end + + shared_examples 'does not record an event' do + it 'does not record an event' do + expect { subject }.not_to change { abuse_report.events.count } + end + end + + shared_examples 'records an event' do |action:| + it 'records the event', :aggregate_failures do + expect { subject }.to change { abuse_report.events.count }.by(1) + + expect(abuse_report.events.last).to have_attributes( + action: action, + user: admin, + reason: reason, + comment: params[:comment] + ) + end + end + + context 'when invalid parameters are given' do + describe 'invalid user' do + describe 'when no user is given' do + let_it_be(:admin) { nil } + + it_behaves_like 'returns an error response', 'Admin is required' + end + + describe 'when given user is no admin' do + let_it_be(:admin) { create(:user) } + + it_behaves_like 'returns an error response', 'Admin is required' + end + end + + describe 'invalid action' do + describe 'when no action is given' do + let(:action) { '' } + let(:close) { 'false' } + + it_behaves_like 'returns an error response', 'Action is required' + end + + describe 'when unknown action is given' do + let(:action) { 'unknown' } + let(:close) { 'false' } + + it_behaves_like 'returns an error response', 'Action is required' + end + end + + describe 'invalid reason' do + let(:reason) { '' } + + it 'sets the reason to `other`' do + subject + + expect(abuse_report.events.last).to have_attributes(reason: 'other') + end + end + end + + describe 'when banning the user' do + it 'calls the Users::BanService' do + expect_next_instance_of(Users::BanService, admin) do |service| + expect(service).to receive(:execute).with(abuse_report.user).and_return(status: :success) + end + + subject + end + + context 'when closing the report' do + it_behaves_like 'closes the report' + it_behaves_like 'records an event', action: 'ban_user_and_close_report' + end + + context 'when not closing the report' do + let(:close) { 'false' } + + it_behaves_like 'does not close the report' + it_behaves_like 'records an event', action: 'ban_user' + end + + context 'when banning the user fails' do + before do + allow_next_instance_of(Users::BanService, admin) do |service| + allow(service).to receive(:execute).with(abuse_report.user) + .and_return(status: :error, message: 'Banning the user failed') + end + end + + it_behaves_like 'returns an error response', 'Banning the user failed' + it_behaves_like 'does not close the report' + it_behaves_like 'does not record an event' + end + end + + describe 'when blocking the user' do + let(:action) { 'block_user' } + + it 'calls the Users::BlockService' do + expect_next_instance_of(Users::BlockService, admin) do |service| + expect(service).to receive(:execute).with(abuse_report.user).and_return(status: :success) + end + + subject + end + + context 'when closing the report' do + it_behaves_like 'closes the report' + it_behaves_like 'records an event', action: 'block_user_and_close_report' + end + + context 'when not closing the report' do + let(:close) { 'false' } + + it_behaves_like 'does not close the report' + it_behaves_like 'records an event', action: 'block_user' + end + + context 'when blocking the user fails' do + before do + allow_next_instance_of(Users::BlockService, admin) do |service| + allow(service).to receive(:execute).with(abuse_report.user) + .and_return(status: :error, message: 'Blocking the user failed') + end + end + + it_behaves_like 'returns an error response', 'Blocking the user failed' + it_behaves_like 'does not close the report' + it_behaves_like 'does not record an event' + end + end + + describe 'when deleting the user' do + let(:action) { 'delete_user' } + + it 'calls the delete_async method' do + expect(abuse_report.user).to receive(:delete_async).with(deleted_by: admin) + subject + end + + context 'when closing the report' do + it_behaves_like 'closes the report' + it_behaves_like 'records an event', action: 'delete_user_and_close_report' + end + + context 'when not closing the report' do + let(:close) { 'false' } + + it_behaves_like 'does not close the report' + it_behaves_like 'records an event', action: 'delete_user' + end + end + + describe 'when only closing the report' do + let(:action) { '' } + + it_behaves_like 'closes the report' + it_behaves_like 'records an event', action: 'close_report' + end + end +end diff --git a/spec/services/admin/set_feature_flag_service_spec.rb b/spec/services/admin/set_feature_flag_service_spec.rb index 45ee914558a..e66802f6332 100644 --- a/spec/services/admin/set_feature_flag_service_spec.rb +++ b/spec/services/admin/set_feature_flag_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::SetFeatureFlagService do +RSpec.describe Admin::SetFeatureFlagService, feature_category: :feature_flags do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:group) { create(:group) } 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 fa4fd8ed0b2..fd81c0893ed 100644 --- a/spec/services/alert_management/alerts/todo/create_service_spec.rb +++ b/spec/services/alert_management/alerts/todo/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AlertManagement::Alerts::Todo::CreateService do +RSpec.describe AlertManagement::Alerts::Todo::CreateService, feature_category: :incident_management do let_it_be(:user) { create(:user) } let_it_be(:alert) { create(:alert_management_alert) } diff --git a/spec/services/alert_management/alerts/update_service_spec.rb b/spec/services/alert_management/alerts/update_service_spec.rb index 8375c8cdf7d..69e2f2de291 100644 --- a/spec/services/alert_management/alerts/update_service_spec.rb +++ b/spec/services/alert_management/alerts/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AlertManagement::Alerts::UpdateService do +RSpec.describe AlertManagement::Alerts::UpdateService, feature_category: :incident_management do let_it_be(:user_with_permissions) { create(:user) } let_it_be(:other_user_with_permissions) { create(:user) } let_it_be(:user_without_permissions) { create(:user) } diff --git a/spec/services/alert_management/create_alert_issue_service_spec.rb b/spec/services/alert_management/create_alert_issue_service_spec.rb index 7255a722d26..b8d93f99ae4 100644 --- a/spec/services/alert_management/create_alert_issue_service_spec.rb +++ b/spec/services/alert_management/create_alert_issue_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AlertManagement::CreateAlertIssueService do +RSpec.describe AlertManagement::CreateAlertIssueService, feature_category: :incident_management do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } diff --git a/spec/services/alert_management/http_integrations/create_service_spec.rb b/spec/services/alert_management/http_integrations/create_service_spec.rb index ac5c62caf84..5200ec27dd1 100644 --- a/spec/services/alert_management/http_integrations/create_service_spec.rb +++ b/spec/services/alert_management/http_integrations/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AlertManagement::HttpIntegrations::CreateService do +RSpec.describe AlertManagement::HttpIntegrations::CreateService, feature_category: :incident_management do let_it_be(:user_with_permissions) { create(:user) } let_it_be(:user_without_permissions) { create(:user) } let_it_be_with_reload(:project) { create(:project) } diff --git a/spec/services/alert_management/http_integrations/destroy_service_spec.rb b/spec/services/alert_management/http_integrations/destroy_service_spec.rb index cd949d728de..a8e9746cb85 100644 --- a/spec/services/alert_management/http_integrations/destroy_service_spec.rb +++ b/spec/services/alert_management/http_integrations/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AlertManagement::HttpIntegrations::DestroyService do +RSpec.describe AlertManagement::HttpIntegrations::DestroyService, feature_category: :incident_management do let_it_be(:user_with_permissions) { create(:user) } let_it_be(:user_without_permissions) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/services/alert_management/http_integrations/update_service_spec.rb b/spec/services/alert_management/http_integrations/update_service_spec.rb index 94c34d9a29c..3f1a0967aa9 100644 --- a/spec/services/alert_management/http_integrations/update_service_spec.rb +++ b/spec/services/alert_management/http_integrations/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AlertManagement::HttpIntegrations::UpdateService do +RSpec.describe AlertManagement::HttpIntegrations::UpdateService, feature_category: :incident_management do let_it_be(:user_with_permissions) { create(:user) } let_it_be(:user_without_permissions) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/services/alert_management/metric_images/upload_service_spec.rb b/spec/services/alert_management/metric_images/upload_service_spec.rb index 527d9db0fd9..2cafd2c9029 100644 --- a/spec/services/alert_management/metric_images/upload_service_spec.rb +++ b/spec/services/alert_management/metric_images/upload_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AlertManagement::MetricImages::UploadService do +RSpec.describe AlertManagement::MetricImages::UploadService, feature_category: :metrics do subject(:service) { described_class.new(alert, current_user, params) } let_it_be_with_refind(:project) { create(:project) } diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb index ae52a09be48..eb5f3808021 100644 --- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb +++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AlertManagement::ProcessPrometheusAlertService do +RSpec.describe AlertManagement::ProcessPrometheusAlertService, feature_category: :incident_management do let_it_be(:project, reload: true) { create(:project, :repository) } let(:service) { described_class.new(project, payload) } diff --git a/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb b/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb index 7bfae0cd9fc..c39965a2799 100644 --- a/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb +++ b/spec/services/analytics/cycle_analytics/stages/list_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Analytics::CycleAnalytics::Stages::ListService do +RSpec.describe Analytics::CycleAnalytics::Stages::ListService, feature_category: :value_stream_management do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:project_namespace) { project.project_namespace.reload } diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb index e20d59fb0ef..79d4fc67538 100644 --- a/spec/services/application_settings/update_service_spec.rb +++ b/spec/services/application_settings/update_service_spec.rb @@ -110,7 +110,7 @@ RSpec.describe ApplicationSettings::UpdateService do end end - describe 'markdown cache invalidators' do + describe 'markdown cache invalidators', feature_category: :team_planning do shared_examples 'invalidates markdown cache' do |attribute| let(:params) { attribute } @@ -144,7 +144,7 @@ RSpec.describe ApplicationSettings::UpdateService do end end - describe 'performance bar settings' do + describe 'performance bar settings', feature_category: :application_performance do using RSpec::Parameterized::TableSyntax where(:params_performance_bar_enabled, @@ -247,7 +247,7 @@ RSpec.describe ApplicationSettings::UpdateService do end end - context 'when external authorization is enabled' do + context 'when external authorization is enabled', feature_category: :system_access do before do enable_external_authorization_service_check end diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb index 4f8b90fcb4a..8a20f7775eb 100644 --- a/spec/services/audit_event_service_spec.rb +++ b/spec/services/audit_event_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AuditEventService, :with_license do +RSpec.describe AuditEventService, :with_license, feature_category: :audit_events do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user, :with_sign_ins) } let_it_be(:project_member) { create(:project_member, user: user) } diff --git a/spec/services/audit_events/build_service_spec.rb b/spec/services/audit_events/build_service_spec.rb index caf405a53aa..575ec9e58b8 100644 --- a/spec/services/audit_events/build_service_spec.rb +++ b/spec/services/audit_events/build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AuditEvents::BuildService do +RSpec.describe AuditEvents::BuildService, feature_category: :audit_events do let(:author) { build_stubbed(:author, current_sign_in_ip: '127.0.0.1') } let(:deploy_token) { build_stubbed(:deploy_token, user: author) } let(:scope) { build_stubbed(:group) } diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index ba7acd3d3df..90aba1ae54c 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Auth::ContainerRegistryAuthenticationService do +RSpec.describe Auth::ContainerRegistryAuthenticationService, feature_category: :container_registry do include AdminModeHelper it_behaves_like 'a container registry auth service' diff --git a/spec/services/auth/dependency_proxy_authentication_service_spec.rb b/spec/services/auth/dependency_proxy_authentication_service_spec.rb index 667f361dc34..8f92fbe272c 100644 --- a/spec/services/auth/dependency_proxy_authentication_service_spec.rb +++ b/spec/services/auth/dependency_proxy_authentication_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Auth::DependencyProxyAuthenticationService do +RSpec.describe Auth::DependencyProxyAuthenticationService, feature_category: :dependency_proxy do let_it_be(:user) { create(:user) } let(:service) { Auth::DependencyProxyAuthenticationService.new(nil, user) } diff --git a/spec/services/authorized_project_update/find_records_due_for_refresh_service_spec.rb b/spec/services/authorized_project_update/find_records_due_for_refresh_service_spec.rb index 691fb3f60f4..e8f86b4d7c5 100644 --- a/spec/services/authorized_project_update/find_records_due_for_refresh_service_spec.rb +++ b/spec/services/authorized_project_update/find_records_due_for_refresh_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService do +RSpec.describe AuthorizedProjectUpdate::FindRecordsDueForRefreshService, feature_category: :projects do # We're using let! here so that any expectations for the service class are not # triggered twice. let!(:project) { create(:project) } diff --git a/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb index 782f6858870..51cab6d188b 100644 --- a/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb +++ b/spec/services/authorized_project_update/periodic_recalculate_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AuthorizedProjectUpdate::PeriodicRecalculateService do +RSpec.describe AuthorizedProjectUpdate::PeriodicRecalculateService, feature_category: :projects do subject(:service) { described_class.new } describe '#execute' do diff --git a/spec/services/authorized_project_update/project_access_changed_service_spec.rb b/spec/services/authorized_project_update/project_access_changed_service_spec.rb index da428bece20..7c09d7755ca 100644 --- a/spec/services/authorized_project_update/project_access_changed_service_spec.rb +++ b/spec/services/authorized_project_update/project_access_changed_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AuthorizedProjectUpdate::ProjectAccessChangedService do +RSpec.describe AuthorizedProjectUpdate::ProjectAccessChangedService, feature_category: :projects do describe '#execute' do it 'executes projects_authorizations refresh' do expect(AuthorizedProjectUpdate::ProjectRecalculateWorker).to receive(:bulk_perform_async) diff --git a/spec/services/authorized_project_update/project_recalculate_per_user_service_spec.rb b/spec/services/authorized_project_update/project_recalculate_per_user_service_spec.rb index 62862d0e558..7b2dd52810f 100644 --- a/spec/services/authorized_project_update/project_recalculate_per_user_service_spec.rb +++ b/spec/services/authorized_project_update/project_recalculate_per_user_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AuthorizedProjectUpdate::ProjectRecalculatePerUserService, '#execute' do +RSpec.describe AuthorizedProjectUpdate::ProjectRecalculatePerUserService, '#execute', feature_category: :projects do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:another_user) { create(:user) } diff --git a/spec/services/authorized_project_update/project_recalculate_service_spec.rb b/spec/services/authorized_project_update/project_recalculate_service_spec.rb index c339faaeabf..8360f3c67ab 100644 --- a/spec/services/authorized_project_update/project_recalculate_service_spec.rb +++ b/spec/services/authorized_project_update/project_recalculate_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AuthorizedProjectUpdate::ProjectRecalculateService, '#execute' do +RSpec.describe AuthorizedProjectUpdate::ProjectRecalculateService, '#execute', feature_category: :projects do let_it_be(:project) { create(:project) } subject(:execute) { described_class.new(project).execute } diff --git a/spec/services/auto_merge/base_service_spec.rb b/spec/services/auto_merge/base_service_spec.rb index 6c804a14620..7afe5d406ba 100644 --- a/spec/services/auto_merge/base_service_spec.rb +++ b/spec/services/auto_merge/base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AutoMerge::BaseService do +RSpec.describe AutoMerge::BaseService, feature_category: :code_review_workflow do let(:project) { create(:project) } let(:user) { create(:user) } let(:service) { described_class.new(project, user, params) } 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 676f55be28a..a0b22267960 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 @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService do +RSpec.describe AutoMerge::MergeWhenPipelineSucceedsService, feature_category: :code_review_workflow do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :repository) } diff --git a/spec/services/auto_merge_service_spec.rb b/spec/services/auto_merge_service_spec.rb index 7584e44152e..94f4b414dca 100644 --- a/spec/services/auto_merge_service_spec.rb +++ b/spec/services/auto_merge_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AutoMergeService do +RSpec.describe AutoMergeService, feature_category: :code_review_workflow do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/services/award_emojis/add_service_spec.rb b/spec/services/award_emojis/add_service_spec.rb index 0fbb785e2d6..99dbe6dc606 100644 --- a/spec/services/award_emojis/add_service_spec.rb +++ b/spec/services/award_emojis/add_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AwardEmojis::AddService do +RSpec.describe AwardEmojis::AddService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:awardable) { create(:note, project: project) } diff --git a/spec/services/award_emojis/base_service_spec.rb b/spec/services/award_emojis/base_service_spec.rb index e0c8fd39ad9..f1ee4d1cfb8 100644 --- a/spec/services/award_emojis/base_service_spec.rb +++ b/spec/services/award_emojis/base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AwardEmojis::BaseService do +RSpec.describe AwardEmojis::BaseService, feature_category: :team_planning do let(:awardable) { build(:note) } let(:current_user) { build(:user) } diff --git a/spec/services/award_emojis/collect_user_emoji_service_spec.rb b/spec/services/award_emojis/collect_user_emoji_service_spec.rb index bf5aa0eb9ef..d75d5804f93 100644 --- a/spec/services/award_emojis/collect_user_emoji_service_spec.rb +++ b/spec/services/award_emojis/collect_user_emoji_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AwardEmojis::CollectUserEmojiService do +RSpec.describe AwardEmojis::CollectUserEmojiService, feature_category: :team_planning do describe '#execute' do it 'returns an Array containing the awarded emoji names' do user = create(:user) diff --git a/spec/services/award_emojis/copy_service_spec.rb b/spec/services/award_emojis/copy_service_spec.rb index abb9c65e25d..6c1d7fb21e2 100644 --- a/spec/services/award_emojis/copy_service_spec.rb +++ b/spec/services/award_emojis/copy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AwardEmojis::CopyService do +RSpec.describe AwardEmojis::CopyService, feature_category: :team_planning do let_it_be(:from_awardable) do create( :issue, diff --git a/spec/services/award_emojis/destroy_service_spec.rb b/spec/services/award_emojis/destroy_service_spec.rb index f743de7c59e..109bdbfa986 100644 --- a/spec/services/award_emojis/destroy_service_spec.rb +++ b/spec/services/award_emojis/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AwardEmojis::DestroyService do +RSpec.describe AwardEmojis::DestroyService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:awardable) { create(:note) } let_it_be(:project) { awardable.project } diff --git a/spec/services/award_emojis/toggle_service_spec.rb b/spec/services/award_emojis/toggle_service_spec.rb index 74e97c66193..61dcc22561f 100644 --- a/spec/services/award_emojis/toggle_service_spec.rb +++ b/spec/services/award_emojis/toggle_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AwardEmojis::ToggleService do +RSpec.describe AwardEmojis::ToggleService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public) } let_it_be(:awardable) { create(:note, project: project) } diff --git a/spec/services/base_container_service_spec.rb b/spec/services/base_container_service_spec.rb index 1de79eec702..7406f0aea93 100644 --- a/spec/services/base_container_service_spec.rb +++ b/spec/services/base_container_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BaseContainerService do +RSpec.describe BaseContainerService, feature_category: :container_registry do let(:project) { Project.new } let(:user) { User.new } diff --git a/spec/services/base_count_service_spec.rb b/spec/services/base_count_service_spec.rb index 18cab2e8e9a..9a731f52b09 100644 --- a/spec/services/base_count_service_spec.rb +++ b/spec/services/base_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BaseCountService, :use_clean_rails_memory_store_caching do +RSpec.describe BaseCountService, :use_clean_rails_memory_store_caching, feature_category: :shared do let(:service) { described_class.new } describe '#relation_for_count' do diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb index f6a9f0903ce..5aaef9d529c 100644 --- a/spec/services/boards/create_service_spec.rb +++ b/spec/services/boards/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::CreateService do +RSpec.describe Boards::CreateService, feature_category: :team_planning do describe '#execute' do context 'when board parent is a project' do let(:parent) { create(:project) } diff --git a/spec/services/boards/destroy_service_spec.rb b/spec/services/boards/destroy_service_spec.rb index cd6df832547..feaca3cfcce 100644 --- a/spec/services/boards/destroy_service_spec.rb +++ b/spec/services/boards/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::DestroyService do +RSpec.describe Boards::DestroyService, feature_category: :team_planning do context 'with project board' do let_it_be(:parent) { create(:project) } diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb index c4f1eb093dc..f9a9c338f58 100644 --- a/spec/services/boards/issues/create_service_spec.rb +++ b/spec/services/boards/issues/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::Issues::CreateService do +RSpec.describe Boards::Issues::CreateService, feature_category: :team_planning do describe '#execute' do let(:project) { create(:project) } let(:board) { create(:board, project: project) } diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 1959710bb0c..4089e9e6da0 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::Issues::ListService do +RSpec.describe Boards::Issues::ListService, feature_category: :team_planning do describe '#execute' do let_it_be(:user) { create(:user) } @@ -57,7 +57,15 @@ RSpec.describe Boards::Issues::ListService do end context 'when filtering' do - let_it_be(:incident) { create(:labeled_issue, project: project, milestone: m1, labels: [development, p1], issue_type: 'incident') } + let_it_be(:incident) do + create( + :labeled_issue, + :incident, + project: project, + milestone: m1, + labels: [development, p1] + ) + end context 'when filtering by type' do it 'only returns the specified type' do @@ -77,7 +85,6 @@ RSpec.describe Boards::Issues::ListService do end end - # rubocop: disable RSpec/MultipleMemoizedHelpers context 'when parent is a group' do let(:project) { create(:project, :empty_repo, namespace: group) } let(:project1) { create(:project, :empty_repo, namespace: group) } @@ -148,7 +155,6 @@ RSpec.describe Boards::Issues::ListService do it_behaves_like 'issues list service' end end - # rubocop: enable RSpec/MultipleMemoizedHelpers end describe '.initialize_relative_positions' do diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 3a25f13762c..9c173f3f86e 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::Issues::MoveService do +RSpec.describe Boards::Issues::MoveService, feature_category: :team_planning do describe '#execute' do context 'when parent is a project' do let(:user) { create(:user) } diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index cac26b3c88d..da317fdd474 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::Lists::CreateService do +RSpec.describe Boards::Lists::CreateService, feature_category: :team_planning do context 'when board parent is a project' do let_it_be(:parent) { create(:project) } let_it_be(:board) { create(:board, project: parent) } diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb index d5358bcc1e1..837635d49e8 100644 --- a/spec/services/boards/lists/destroy_service_spec.rb +++ b/spec/services/boards/lists/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::Lists::DestroyService do +RSpec.describe Boards::Lists::DestroyService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let(:list_type) { :list } diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb index 2d41de42581..90b705e05c3 100644 --- a/spec/services/boards/lists/list_service_spec.rb +++ b/spec/services/boards/lists/list_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::Lists::ListService do +RSpec.describe Boards::Lists::ListService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb index 2861fc48b4d..abf7d48e114 100644 --- a/spec/services/boards/lists/move_service_spec.rb +++ b/spec/services/boards/lists/move_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::Lists::MoveService do +RSpec.describe Boards::Lists::MoveService, feature_category: :team_planning do describe '#execute' do context 'when board parent is a project' do let(:project) { create(:project) } diff --git a/spec/services/boards/lists/update_service_spec.rb b/spec/services/boards/lists/update_service_spec.rb index 21216e1b945..341eaa52292 100644 --- a/spec/services/boards/lists/update_service_spec.rb +++ b/spec/services/boards/lists/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::Lists::UpdateService do +RSpec.describe Boards::Lists::UpdateService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let!(:list) { create(:list, board: board, position: 0) } diff --git a/spec/services/boards/visits/create_service_spec.rb b/spec/services/boards/visits/create_service_spec.rb index 8910345d170..4af4e914da5 100644 --- a/spec/services/boards/visits/create_service_spec.rb +++ b/spec/services/boards/visits/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Boards::Visits::CreateService do +RSpec.describe Boards::Visits::CreateService, feature_category: :team_planning do describe '#execute' do let(:user) { create(:user) } diff --git a/spec/services/branches/create_service_spec.rb b/spec/services/branches/create_service_spec.rb index 19a32aafa38..7fb7d9d440d 100644 --- a/spec/services/branches/create_service_spec.rb +++ b/spec/services/branches/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Branches::CreateService, :use_clean_rails_redis_caching do +RSpec.describe Branches::CreateService, :use_clean_rails_redis_caching, feature_category: :source_code_management do subject(:service) { described_class.new(project, user) } let_it_be(:project) { create(:project_empty_repo) } @@ -108,7 +108,7 @@ RSpec.describe Branches::CreateService, :use_clean_rails_redis_caching do control = RedisCommands::Recorder.new(pattern: ':branch_names:') { subject } - expect(control.by_command(:sadd).count).to eq(1) + expect(control).not_to exceed_redis_command_calls_limit(:sadd, 1) end end diff --git a/spec/services/branches/delete_merged_service_spec.rb b/spec/services/branches/delete_merged_service_spec.rb index 46611670fe1..23a892a56ef 100644 --- a/spec/services/branches/delete_merged_service_spec.rb +++ b/spec/services/branches/delete_merged_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Branches::DeleteMergedService do +RSpec.describe Branches::DeleteMergedService, feature_category: :source_code_management do include ProjectForksHelper subject(:service) { described_class.new(project, project.first_owner) } diff --git a/spec/services/branches/delete_service_spec.rb b/spec/services/branches/delete_service_spec.rb index 727cadc5a50..003645b1b8c 100644 --- a/spec/services/branches/delete_service_spec.rb +++ b/spec/services/branches/delete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Branches::DeleteService do +RSpec.describe Branches::DeleteService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } diff --git a/spec/services/branches/diverging_commit_counts_service_spec.rb b/spec/services/branches/diverging_commit_counts_service_spec.rb index 34a2b81c831..3cccc74735c 100644 --- a/spec/services/branches/diverging_commit_counts_service_spec.rb +++ b/spec/services/branches/diverging_commit_counts_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Branches::DivergingCommitCountsService do +RSpec.describe Branches::DivergingCommitCountsService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } let(:repository) { project.repository } diff --git a/spec/services/branches/validate_new_service_spec.rb b/spec/services/branches/validate_new_service_spec.rb index 02127c8c10d..a5b75a09353 100644 --- a/spec/services/branches/validate_new_service_spec.rb +++ b/spec/services/branches/validate_new_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Branches::ValidateNewService do +RSpec.describe Branches::ValidateNewService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } subject(:service) { described_class.new(project) } diff --git a/spec/services/bulk_create_integration_service_spec.rb b/spec/services/bulk_create_integration_service_spec.rb index 22bb1736f9f..57bdfdbd4cb 100644 --- a/spec/services/bulk_create_integration_service_spec.rb +++ b/spec/services/bulk_create_integration_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkCreateIntegrationService do +RSpec.describe BulkCreateIntegrationService, feature_category: :integrations do include JiraIntegrationHelpers before_all do diff --git a/spec/services/bulk_imports/archive_extraction_service_spec.rb b/spec/services/bulk_imports/archive_extraction_service_spec.rb index da9df31cde9..40f8d8718ae 100644 --- a/spec/services/bulk_imports/archive_extraction_service_spec.rb +++ b/spec/services/bulk_imports/archive_extraction_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::ArchiveExtractionService do +RSpec.describe BulkImports::ArchiveExtractionService, feature_category: :importers do let_it_be(:tmpdir) { Dir.mktmpdir } let_it_be(:filename) { 'symlink_export.tar' } let_it_be(:filepath) { File.join(tmpdir, filename) } diff --git a/spec/services/bulk_imports/batched_relation_export_service_spec.rb b/spec/services/bulk_imports/batched_relation_export_service_spec.rb new file mode 100644 index 00000000000..c361dfe5052 --- /dev/null +++ b/spec/services/bulk_imports/batched_relation_export_service_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe BulkImports::BatchedRelationExportService, feature_category: :importers do + let_it_be(:user) { create(:user) } + let_it_be(:portable) { create(:group) } + + let(:relation) { 'labels' } + let(:jid) { '123' } + + subject(:service) { described_class.new(user, portable, relation, jid) } + + describe '#execute' do + context 'when there are batches to export' do + let_it_be(:label) { create(:group_label, group: portable) } + + it 'marks export as started' do + service.execute + + export = portable.bulk_import_exports.first + + expect(export.reload.started?).to eq(true) + end + + it 'removes existing batches' do + expect_next_instance_of(BulkImports::Export) do |export| + expect(export.batches).to receive(:destroy_all) + end + + service.execute + end + + it 'enqueues export jobs for each batch & caches batch record ids' do + expect(BulkImports::RelationBatchExportWorker).to receive(:perform_async) + expect(Gitlab::Cache::Import::Caching).to receive(:set_add) + + service.execute + end + + it 'enqueues FinishBatchedRelationExportWorker' do + expect(BulkImports::FinishBatchedRelationExportWorker).to receive(:perform_async) + + service.execute + end + + context 'when there are multiple batches' do + it 'creates a batch record for each batch of records' do + stub_const("#{described_class.name}::BATCH_SIZE", 1) + + create_list(:group_label, 10, group: portable) + + service.execute + + export = portable.bulk_import_exports.first + + expect(export.batches.count).to eq(11) + end + end + end + + context 'when there are no batches to export' do + let(:relation) { 'milestones' } + + it 'marks export as finished' do + service.execute + + export = portable.bulk_import_exports.first + + expect(export.finished?).to eq(true) + expect(export.batches.count).to eq(0) + end + end + + context 'when exception occurs' do + it 'tracks exception and marks export as failed' do + allow_next_instance_of(BulkImports::Export) do |export| + allow(export).to receive(:update!).and_call_original + + allow(export) + .to receive(:update!) + .with(status_event: 'finish', total_objects_count: 0, batched: true, batches_count: 0, jid: jid, error: nil) + .and_raise(StandardError, 'Error!') + end + + expect(Gitlab::ErrorTracking) + .to receive(:track_exception) + .with(StandardError, portable_id: portable.id, portable_type: portable.class.name) + + service.execute + + export = portable.bulk_import_exports.first + + expect(export.reload.failed?).to eq(true) + end + end + end + + describe '.cache_key' do + it 'returns cache key given export and batch ids' do + expect(described_class.cache_key(1, 1)).to eq('bulk_imports/batched_relation_export/1/1') + end + end +end diff --git a/spec/services/bulk_imports/create_service_spec.rb b/spec/services/bulk_imports/create_service_spec.rb index 7f892cfe722..ff4afd6abd0 100644 --- a/spec/services/bulk_imports/create_service_spec.rb +++ b/spec/services/bulk_imports/create_service_spec.rb @@ -35,6 +35,9 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do ] end + let(:source_entity_identifier) { ERB::Util.url_encode(params[0][:source_full_path]) } + let(:source_entity_type) { BulkImports::CreateService::ENTITY_TYPES_MAPPING.fetch(params[0][:source_type]) } + subject { described_class.new(user, params, credentials) } describe '#execute' do @@ -59,6 +62,34 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do end end + context 'when direct transfer setting query returns a 404' do + it 'raises a ServiceResponse::Error' do + stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) + stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') + .to_return( + status: 200, + body: source_version.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + stub_request(:get, "http://gitlab.example/api/v4/#{source_entity_type}/#{source_entity_identifier}/export_relations/status?page=1&per_page=30&private_token=token") + .to_return(status: 404) + + expect_next_instance_of(BulkImports::Clients::HTTP) do |client| + expect(client).to receive(:get).and_raise(BulkImports::Error.setting_not_enabled) + end + + result = subject.execute + + expect(result).to be_a(ServiceResponse) + expect(result).to be_error + expect(result.message) + .to eq( + "Group import disabled on source or destination instance. " \ + "Ask an administrator to enable it on both instances and try again." + ) + end + end + context 'when required scopes are not present' do it 'returns ServiceResponse with error if token does not have api scope' do stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) @@ -68,9 +99,13 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do body: source_version.to_json, headers: { 'Content-Type' => 'application/json' } ) + stub_request(:get, "http://gitlab.example/api/v4/#{source_entity_type}/#{source_entity_identifier}/export_relations/status?page=1&per_page=30&private_token=token") + .to_return( + status: 200 + ) allow_next_instance_of(BulkImports::Clients::HTTP) do |client| - allow(client).to receive(:validate_instance_version!).and_raise(BulkImports::Error.scope_validation_failure) + allow(client).to receive(:validate_import_scopes!).and_raise(BulkImports::Error.scope_validation_failure) end result = subject.execute @@ -79,8 +114,8 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do expect(result).to be_error expect(result.message) .to eq( - "Import aborted as the provided personal access token does not have the required 'api' scope or is " \ - "no longer valid." + "Personal access token does not " \ + "have the required 'api' scope or is no longer valid." ) end end @@ -90,16 +125,21 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do stub_request(:get, 'http://gitlab.example/api/v4/version?private_token=token').to_return(status: 404) stub_request(:get, 'http://gitlab.example/api/v4/metadata?private_token=token') .to_return(status: 200, body: source_version.to_json, headers: { 'Content-Type' => 'application/json' }) + stub_request(:get, "http://gitlab.example/api/v4/#{source_entity_type}/#{source_entity_identifier}/export_relations/status?page=1&per_page=30&private_token=token") + .to_return( + status: 200 + ) stub_request(:get, 'http://gitlab.example/api/v4/personal_access_tokens/self?private_token=token') .to_return( status: 200, body: { 'scopes' => ['api'] }.to_json, headers: { 'Content-Type' => 'application/json' } ) + + parent_group.add_owner(user) end it 'creates bulk import' do - parent_group.add_owner(user) expect { subject.execute }.to change { BulkImport.count }.by(1) last_bulk_import = BulkImport.last @@ -111,7 +151,8 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do expect_snowplow_event( category: 'BulkImports::CreateService', action: 'create', - label: 'bulk_import_group' + label: 'bulk_import_group', + extra: { source_equals_destination: false } ) expect_snowplow_event( @@ -123,6 +164,23 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do ) end + context 'on the same instance' do + before do + allow(Settings.gitlab).to receive(:base_url).and_return('http://gitlab.example') + end + + it 'tracks the same instance migration' do + expect { subject.execute }.to change { BulkImport.count }.by(1) + + expect_snowplow_event( + category: 'BulkImports::CreateService', + action: 'create', + label: 'bulk_import_group', + extra: { source_equals_destination: true } + ) + end + end + describe 'projects migration flag' do let(:import) { BulkImport.last } @@ -169,11 +227,16 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do allow_next_instance_of(BulkImports::Clients::HTTP) do |instance| allow(instance).to receive(:instance_version).and_return(source_version) allow(instance).to receive(:instance_enterprise).and_return(false) + stub_request(:get, "http://gitlab.example/api/v4/#{source_entity_type}/#{source_entity_identifier}/export_relations/status?page=1&per_page=30&private_token=token") + .to_return( + status: 200 + ) end + + parent_group.add_owner(user) end it 'creates bulk import' do - parent_group.add_owner(user) expect { subject.execute }.to change { BulkImport.count }.by(1) last_bulk_import = BulkImport.last @@ -186,7 +249,8 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do expect_snowplow_event( category: 'BulkImports::CreateService', action: 'create', - label: 'bulk_import_group' + label: 'bulk_import_group', + extra: { source_equals_destination: false } ) expect_snowplow_event( @@ -198,6 +262,23 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do ) end + context 'on the same instance' do + before do + allow(Settings.gitlab).to receive(:base_url).and_return('http://gitlab.example') + end + + it 'tracks the same instance migration' do + expect { subject.execute }.to change { BulkImport.count }.by(1) + + expect_snowplow_event( + category: 'BulkImports::CreateService', + action: 'create', + label: 'bulk_import_group', + extra: { source_equals_destination: true } + ) + end + end + it 'creates bulk import entities' do expect { subject.execute }.to change { BulkImports::Entity.count }.by(3) end @@ -227,11 +308,10 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do expect(result).to be_a(ServiceResponse) expect(result).to be_error expect(result.message).to eq("Validation failed: Source full path can't be blank, " \ - "Source full path cannot start with a non-alphanumeric character except " \ - "for periods or underscores, can contain only alphanumeric characters, " \ - "forward slashes, periods, and underscores, cannot end with " \ - "a period or forward slash, and has a relative path structure " \ - "with no http protocol chars or leading or trailing forward slashes") + "Source full path must have a relative path structure with " \ + "no HTTP protocol characters, or leading or trailing forward slashes. " \ + "Path segments must not start or end with a special character, and " \ + "must not contain consecutive special characters.") end describe '#user-role' do @@ -263,6 +343,8 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do end it 'defines access_level as not a member' do + parent_group.members.delete_all + subject.execute expect_snowplow_event( category: 'BulkImports::CreateService', @@ -325,7 +407,210 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do end end - describe '.validate_destination_full_path' do + describe '#validate_setting_enabled!' do + let(:entity_source_id) { 'gid://gitlab/Model/12345' } + let(:graphql_client) { instance_double(BulkImports::Clients::Graphql) } + let(:http_client) { instance_double(BulkImports::Clients::HTTP) } + let(:http_response) { double(code: 200, success?: true) } # rubocop:disable RSpec/VerifiedDoubles + + before do + allow(BulkImports::Clients::HTTP).to receive(:new).and_return(http_client) + allow(BulkImports::Clients::Graphql).to receive(:new).and_return(graphql_client) + + allow(http_client).to receive(:instance_version).and_return(status: 200) + allow(http_client).to receive(:instance_enterprise).and_return(false) + allow(http_client).to receive(:validate_instance_version!).and_return(source_version) + allow(http_client).to receive(:validate_import_scopes!).and_return(true) + end + + context 'when the source_type is a group' do + context 'when the source_full_path contains only integer characters' do + let(:query_string) { BulkImports::Groups::Graphql::GetGroupQuery.new(context: nil).to_s } + let(:graphql_response) do + double(original_hash: { 'data' => { 'group' => { 'id' => entity_source_id } } }) # rubocop:disable RSpec/VerifiedDoubles + end + + let(:params) do + [ + { + source_type: 'group_entity', + source_full_path: '67890', + destination_slug: 'destination-group-1', + destination_namespace: 'destination1' + } + ] + end + + before do + allow(graphql_client).to receive(:parse).with(query_string) + allow(graphql_client).to receive(:execute).and_return(graphql_response) + + allow(http_client).to receive(:get) + .with("/groups/12345/export_relations/status") + .and_return(http_response) + + stub_request(:get, "http://gitlab.example/api/v4/groups/12345/export_relations/status?page=1&per_page=30&private_token=token") + .to_return(status: 200, body: "", headers: {}) + end + + it 'makes a graphql request using the group full path and an http request with the correct id' do + expect(graphql_client).to receive(:parse).with(query_string) + expect(graphql_client).to receive(:execute).and_return(graphql_response) + + expect(http_client).to receive(:get).with("/groups/12345/export_relations/status") + + subject.execute + end + end + end + + context 'when the source_type is a project' do + context 'when the source_full_path contains only integer characters' do + let(:query_string) { BulkImports::Projects::Graphql::GetProjectQuery.new(context: nil).to_s } + let(:graphql_response) do + double(original_hash: { 'data' => { 'project' => { 'id' => entity_source_id } } }) # rubocop:disable RSpec/VerifiedDoubles + end + + let(:params) do + [ + { + source_type: 'project_entity', + source_full_path: '67890', + destination_slug: 'destination-group-1', + destination_namespace: 'destination1' + } + ] + end + + before do + allow(graphql_client).to receive(:parse).with(query_string) + allow(graphql_client).to receive(:execute).and_return(graphql_response) + + allow(http_client).to receive(:get) + .with("/projects/12345/export_relations/status") + .and_return(http_response) + + stub_request(:get, "http://gitlab.example/api/v4/projects/12345/export_relations/status?page=1&per_page=30&private_token=token") + .to_return(status: 200, body: "", headers: {}) + end + + it 'makes a graphql request using the group full path and an http request with the correct id' do + expect(graphql_client).to receive(:parse).with(query_string) + expect(graphql_client).to receive(:execute).and_return(graphql_response) + + expect(http_client).to receive(:get).with("/projects/12345/export_relations/status") + + subject.execute + end + end + end + end + + describe '#validate_destination_namespace' do + context 'when the destination_namespace does not exist' do + let(:params) do + [ + { + source_type: 'group_entity', + source_full_path: 'full/path/to/source', + destination_slug: 'destination-slug', + destination_namespace: 'destination-namespace', + migrate_projects: migrate_projects + } + ] + end + + it 'returns ServiceResponse with an error message' do + result = subject.execute + + expect(result).to be_a(ServiceResponse) + expect(result).to be_error + expect(result.message) + .to eq("Import failed. Destination 'destination-namespace' is invalid, or you don't have permission.") + end + end + + context 'when the user does not have permission to create subgroups' do + let(:params) do + [ + { + source_type: 'group_entity', + source_full_path: 'full/path/to/source', + destination_slug: 'destination-slug', + destination_namespace: parent_group.path, + migrate_projects: migrate_projects + } + ] + end + + it 'returns ServiceResponse with an error message' do + parent_group.members.delete_all + + result = subject.execute + + expect(result).to be_a(ServiceResponse) + expect(result).to be_error + expect(result.message) + .to eq("Import failed. Destination '#{parent_group.path}' is invalid, or you don't have permission.") + end + end + + context 'when the user does not have permission to create projects' do + let(:params) do + [ + { + source_type: 'project_entity', + source_full_path: 'full/path/to/source', + destination_slug: 'destination-slug', + destination_namespace: parent_group.path, + migrate_projects: migrate_projects + } + ] + end + + it 'returns ServiceResponse with an error message' do + parent_group.members.delete_all + + result = subject.execute + + expect(result).to be_a(ServiceResponse) + expect(result).to be_error + expect(result.message) + .to eq("Import failed. Destination '#{parent_group.path}' is invalid, or you don't have permission.") + end + end + end + + describe '#validate_destination_slug' do + context 'when the destination_slug is invalid' do + let(:params) do + [ + { + source_type: 'group_entity', + source_full_path: 'full/path/to/source', + destination_slug: 'destin-*-ation-slug', + destination_namespace: parent_group.path, + migrate_projects: migrate_projects + } + ] + end + + it 'returns ServiceResponse with an error message' do + result = subject.execute + + expect(result).to be_a(ServiceResponse) + expect(result).to be_error + expect(result.message) + .to eq( + "Import failed. Destination URL " \ + "must not start or end with a special character and must " \ + "not contain consecutive special characters." + ) + end + end + end + + describe '#validate_destination_full_path' do context 'when the source_type is a group' do context 'when the provided destination_slug already exists in the destination_namespace' do let_it_be(:existing_subgroup) { create(:group, path: 'existing-subgroup', parent_id: parent_group.id ) } @@ -349,7 +634,7 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do expect(result).to be_error expect(result.message) .to eq( - "Import aborted as 'parent-group/existing-subgroup' already exists. " \ + "Import failed. 'parent-group/existing-subgroup' already exists. " \ "Change the destination and try again." ) end @@ -376,7 +661,7 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do expect(result).to be_error expect(result.message) .to eq( - "Import aborted as 'top-level-group' already exists. " \ + "Import failed. 'top-level-group' already exists. " \ "Change the destination and try again." ) end @@ -421,13 +706,15 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do end it 'returns ServiceResponse with an error message' do + existing_group.add_owner(user) + result = subject.execute expect(result).to be_a(ServiceResponse) expect(result).to be_error expect(result.message) .to eq( - "Import aborted as 'existing-group/existing-project' already exists. " \ + "Import failed. 'existing-group/existing-project' already exists. " \ "Change the destination and try again." ) end @@ -448,6 +735,8 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do end it 'returns success ServiceResponse' do + existing_group.add_owner(user) + result = subject.execute expect(result).to be_a(ServiceResponse) diff --git a/spec/services/bulk_imports/export_service_spec.rb b/spec/services/bulk_imports/export_service_spec.rb index 2414f7c5ca7..25a4547477c 100644 --- a/spec/services/bulk_imports/export_service_spec.rb +++ b/spec/services/bulk_imports/export_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::ExportService do +RSpec.describe BulkImports::ExportService, feature_category: :importers do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } @@ -13,17 +13,36 @@ RSpec.describe BulkImports::ExportService do subject { described_class.new(portable: group, user: user) } describe '#execute' do - it 'schedules RelationExportWorker for each top level relation' do - expect(subject).to receive(:execute).and_return(ServiceResponse.success).and_call_original - top_level_relations = BulkImports::FileTransfer.config_for(group).portable_relations - - top_level_relations.each do |relation| - expect(BulkImports::RelationExportWorker) - .to receive(:perform_async) - .with(user.id, group.id, group.class.name, relation) + let_it_be(:top_level_relations) { BulkImports::FileTransfer.config_for(group).portable_relations } + + before do + allow(subject).to receive(:execute).and_return(ServiceResponse.success).and_call_original + end + + context 'when export is not batched' do + it 'schedules RelationExportWorker for each top level relation' do + top_level_relations.each do |relation| + expect(BulkImports::RelationExportWorker) + .to receive(:perform_async) + .with(user.id, group.id, group.class.name, relation, false) + end + + subject.execute end + end + + context 'when export is batched' do + subject { described_class.new(portable: group, user: user, batched: true) } - subject.execute + it 'schedules RelationExportWorker with a `batched: true` flag' do + top_level_relations.each do |relation| + expect(BulkImports::RelationExportWorker) + .to receive(:perform_async) + .with(user.id, group.id, group.class.name, relation, true) + end + + subject.execute + end end context 'when exception occurs' do @@ -38,6 +57,20 @@ RSpec.describe BulkImports::ExportService do service.execute end + + context 'when user is not allowed to perform export' do + let(:another_user) { create(:user) } + + it 'does not schedule RelationExportWorker' do + another_user = create(:user) + service = described_class.new(portable: group, user: another_user) + response = service.execute + + expect(response.status).to eq(:error) + expect(response.message).to eq(Gitlab::ImportExport::Error) + expect(response.http_status).to eq(:unprocessable_entity) + end + end end end end diff --git a/spec/services/bulk_imports/file_decompression_service_spec.rb b/spec/services/bulk_imports/file_decompression_service_spec.rb index 77348428d60..9b8320aeac5 100644 --- a/spec/services/bulk_imports/file_decompression_service_spec.rb +++ b/spec/services/bulk_imports/file_decompression_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::FileDecompressionService do +RSpec.describe BulkImports::FileDecompressionService, feature_category: :importers do let_it_be(:tmpdir) { Dir.mktmpdir } let_it_be(:ndjson_filename) { 'labels.ndjson' } let_it_be(:ndjson_filepath) { File.join(tmpdir, ndjson_filename) } diff --git a/spec/services/bulk_imports/file_download_service_spec.rb b/spec/services/bulk_imports/file_download_service_spec.rb index 27f77b678e3..7c64d6efc65 100644 --- a/spec/services/bulk_imports/file_download_service_spec.rb +++ b/spec/services/bulk_imports/file_download_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::FileDownloadService do +RSpec.describe BulkImports::FileDownloadService, feature_category: :importers do describe '#execute' do let_it_be(:allowed_content_types) { %w(application/gzip application/octet-stream) } let_it_be(:file_size_limit) { 5.gigabytes } diff --git a/spec/services/bulk_imports/file_export_service_spec.rb b/spec/services/bulk_imports/file_export_service_spec.rb index 453fc1d0c0d..001fccb2054 100644 --- a/spec/services/bulk_imports/file_export_service_spec.rb +++ b/spec/services/bulk_imports/file_export_service_spec.rb @@ -2,21 +2,23 @@ require 'spec_helper' -RSpec.describe BulkImports::FileExportService do +RSpec.describe BulkImports::FileExportService, feature_category: :importers do let_it_be(:project) { create(:project) } + let(:relations) do + { + 'uploads' => BulkImports::UploadsExportService, + 'lfs_objects' => BulkImports::LfsObjectsExportService, + 'repository' => BulkImports::RepositoryBundleExportService, + 'design' => BulkImports::RepositoryBundleExportService + } + end + describe '#execute' do it 'executes export service and archives exported data for each file relation' do - relations = { - 'uploads' => BulkImports::UploadsExportService, - 'lfs_objects' => BulkImports::LfsObjectsExportService, - 'repository' => BulkImports::RepositoryBundleExportService, - 'design' => BulkImports::RepositoryBundleExportService - } - relations.each do |relation, klass| Dir.mktmpdir do |export_path| - service = described_class.new(project, export_path, relation) + service = described_class.new(project, export_path, relation, nil) expect_next_instance_of(klass) do |service| expect(service).to receive(:execute) @@ -31,18 +33,58 @@ RSpec.describe BulkImports::FileExportService do context 'when unsupported relation is passed' do it 'raises an error' do - service = described_class.new(project, nil, 'unsupported') + service = described_class.new(project, nil, 'unsupported', nil) expect { service.execute }.to raise_error(BulkImports::Error, 'Unsupported relation export type') end end end + describe '#execute_batch' do + it 'calls execute with provided array of record ids' do + relations.each do |relation, klass| + Dir.mktmpdir do |export_path| + service = described_class.new(project, export_path, relation, nil) + + expect_next_instance_of(klass) do |service| + expect(service).to receive(:execute).with({ batch_ids: [1, 2, 3] }) + end + + service.export_batch([1, 2, 3]) + end + end + end + end + describe '#exported_filename' do it 'returns filename of the exported file' do - service = described_class.new(project, nil, 'uploads') + service = described_class.new(project, nil, 'uploads', nil) expect(service.exported_filename).to eq('uploads.tar') end end + + describe '#exported_objects_count' do + context 'when relation is a collection' do + it 'returns a number of exported relations' do + %w[uploads lfs_objects].each do |relation| + service = described_class.new(project, nil, relation, nil) + + allow(service).to receive_message_chain(:export_service, :exported_objects_count).and_return(10) + + expect(service.exported_objects_count).to eq(10) + end + end + end + + context 'when relation is a repository' do + it 'returns 1' do + %w[repository design].each do |relation| + service = described_class.new(project, nil, relation, nil) + + expect(service.exported_objects_count).to eq(1) + end + end + end + end end diff --git a/spec/services/bulk_imports/lfs_objects_export_service_spec.rb b/spec/services/bulk_imports/lfs_objects_export_service_spec.rb index 894789c7941..587c99d9897 100644 --- a/spec/services/bulk_imports/lfs_objects_export_service_spec.rb +++ b/spec/services/bulk_imports/lfs_objects_export_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::LfsObjectsExportService do +RSpec.describe BulkImports::LfsObjectsExportService, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:lfs_json_filename) { "#{BulkImports::FileTransfer::ProjectConfig::LFS_OBJECTS_RELATION}.json" } let_it_be(:remote_url) { 'http://my-object-storage.local' } @@ -53,6 +53,19 @@ RSpec.describe BulkImports::LfsObjectsExportService do ) end + context 'when export is batched' do + it 'exports only specified lfs objects' do + new_lfs_object = create(:lfs_object, :with_file) + + project.lfs_objects << new_lfs_object + + service.execute(batch_ids: [new_lfs_object.id]) + + expect(File).to exist(File.join(export_path, new_lfs_object.oid)) + expect(File).not_to exist(File.join(export_path, lfs_object.oid)) + end + end + context 'when lfs object has file on disk missing' do it 'does not attempt to copy non-existent file' do FileUtils.rm(lfs_object.file.path) @@ -79,4 +92,14 @@ RSpec.describe BulkImports::LfsObjectsExportService do end end end + + describe '#exported_objects_count' do + it 'return the number of exported lfs objects' do + project.lfs_objects << create(:lfs_object, :with_file) + + service.execute + + expect(service.exported_objects_count).to eq(2) + end + end end diff --git a/spec/services/bulk_imports/relation_batch_export_service_spec.rb b/spec/services/bulk_imports/relation_batch_export_service_spec.rb new file mode 100644 index 00000000000..c3abd02aff8 --- /dev/null +++ b/spec/services/bulk_imports/relation_batch_export_service_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe BulkImports::RelationBatchExportService, feature_category: :importers do + let_it_be(:project) { create(:project) } + let_it_be(:label) { create(:label, project: project) } + let_it_be(:user) { create(:user) } + let_it_be(:export) { create(:bulk_import_export, :batched, project: project) } + let_it_be(:batch) { create(:bulk_import_export_batch, export: export) } + let_it_be(:cache_key) { BulkImports::BatchedRelationExportService.cache_key(export.id, batch.id) } + + subject(:service) { described_class.new(user.id, batch.id) } + + before(:all) do + Gitlab::Cache::Import::Caching.set_add(cache_key, label.id) + end + + after(:all) do + Gitlab::Cache::Import::Caching.expire(cache_key, 0) + end + + describe '#execute' do + it 'exports relation batch' do + expect(Gitlab::Cache::Import::Caching).to receive(:values_from_set).with(cache_key).and_call_original + + service.execute + batch.reload + + expect(batch.finished?).to eq(true) + expect(batch.objects_count).to eq(1) + expect(batch.error).to be_nil + expect(export.upload.export_file).to be_present + end + + it 'removes exported contents after export' do + double = instance_double(BulkImports::FileTransfer::ProjectConfig, export_path: 'foo') + + allow(BulkImports::FileTransfer).to receive(:config_for).and_return(double) + allow(double).to receive(:export_service_for).and_raise(StandardError, 'Error!') + allow(FileUtils).to receive(:remove_entry) + + expect(FileUtils).to receive(:remove_entry).with('foo') + + service.execute + end + + context 'when exception occurs' do + before do + allow(service).to receive(:gzip).and_raise(StandardError, 'Error!') + end + + it 'marks batch as failed' do + expect(Gitlab::ErrorTracking) + .to receive(:track_exception) + .with(StandardError, portable_id: project.id, portable_type: 'Project') + + service.execute + batch.reload + + expect(batch.failed?).to eq(true) + expect(batch.objects_count).to eq(0) + expect(batch.error).to eq('Error!') + end + end + end +end diff --git a/spec/services/bulk_imports/relation_export_service_spec.rb b/spec/services/bulk_imports/relation_export_service_spec.rb index f0f85217d2e..1c050fe4143 100644 --- a/spec/services/bulk_imports/relation_export_service_spec.rb +++ b/spec/services/bulk_imports/relation_export_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::RelationExportService do +RSpec.describe BulkImports::RelationExportService, feature_category: :importers do let_it_be(:jid) { 'jid' } let_it_be(:relation) { 'labels' } let_it_be(:user) { create(:user) } @@ -35,6 +35,10 @@ RSpec.describe BulkImports::RelationExportService do expect(export.reload.upload.export_file).to be_present expect(export.finished?).to eq(true) + expect(export.batched?).to eq(false) + expect(export.batches_count).to eq(0) + expect(export.batches.count).to eq(0) + expect(export.total_objects_count).to eq(0) end it 'removes temp export files' do @@ -133,13 +137,23 @@ RSpec.describe BulkImports::RelationExportService do include_examples 'tracks exception', ActiveRecord::RecordInvalid end + end + + context 'when export was batched' do + let(:relation) { 'milestones' } + let(:export) { create(:bulk_import_export, group: group, relation: relation, batched: true, batches_count: 2) } - context 'when user is not allowed to perform export' do - let(:another_user) { create(:user) } + it 'removes existing batches and marks export as not batched' do + create(:bulk_import_export_batch, batch_number: 1, export: export) + create(:bulk_import_export_batch, batch_number: 2, export: export) - subject { described_class.new(another_user, group, relation, jid) } + expect { described_class.new(user, group, relation, jid).execute } + .to change { export.reload.batches.count } + .from(2) + .to(0) - include_examples 'tracks exception', Gitlab::ImportExport::Error + expect(export.batched?).to eq(false) + expect(export.batches_count).to eq(0) end end end diff --git a/spec/services/bulk_imports/repository_bundle_export_service_spec.rb b/spec/services/bulk_imports/repository_bundle_export_service_spec.rb index f0d63de1ab9..92d5d21c33f 100644 --- a/spec/services/bulk_imports/repository_bundle_export_service_spec.rb +++ b/spec/services/bulk_imports/repository_bundle_export_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::RepositoryBundleExportService do +RSpec.describe BulkImports::RepositoryBundleExportService, feature_category: :importers do let(:project) { create(:project) } let(:export_path) { Dir.mktmpdir } diff --git a/spec/services/bulk_imports/tree_export_service_spec.rb b/spec/services/bulk_imports/tree_export_service_spec.rb index 6e26cb6dc2b..ae78858976f 100644 --- a/spec/services/bulk_imports/tree_export_service_spec.rb +++ b/spec/services/bulk_imports/tree_export_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::TreeExportService do +RSpec.describe BulkImports::TreeExportService, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:export_path) { Dir.mktmpdir } @@ -53,4 +53,14 @@ RSpec.describe BulkImports::TreeExportService do end end end + + describe '#export_batch' do + it 'serializes relation with specified ids' do + expect_next_instance_of(Gitlab::ImportExport::Json::StreamingSerializer) do |serializer| + expect(serializer).to receive(:serialize_relation).with(anything, batch_ids: [1, 2, 3]) + end + + subject.export_batch([1, 2, 3]) + end + end end diff --git a/spec/services/bulk_imports/uploads_export_service_spec.rb b/spec/services/bulk_imports/uploads_export_service_spec.rb index ad6e005485c..709ade4a504 100644 --- a/spec/services/bulk_imports/uploads_export_service_spec.rb +++ b/spec/services/bulk_imports/uploads_export_service_spec.rb @@ -2,10 +2,9 @@ require 'spec_helper' -RSpec.describe BulkImports::UploadsExportService do - let_it_be(:export_path) { Dir.mktmpdir } - let_it_be(:project) { create(:project, avatar: fixture_file_upload('spec/fixtures/rails_sample.png', 'image/png')) } - +RSpec.describe BulkImports::UploadsExportService, feature_category: :importers do + let(:export_path) { Dir.mktmpdir } + let(:project) { create(:project, avatar: fixture_file_upload('spec/fixtures/rails_sample.png', 'image/png')) } let!(:upload) { create(:upload, :with_file, :issuable_upload, uploader: FileUploader, model: project) } let(:exported_filepath) { File.join(export_path, upload.secret, upload.retrieve_uploader.filename) } @@ -23,6 +22,16 @@ RSpec.describe BulkImports::UploadsExportService do expect(File).to exist(exported_filepath) end + context 'when export is batched' do + it 'exports only specified uploads' do + service.execute(batch_ids: [upload.id]) + + expect(service.exported_objects_count).to eq(1) + expect(File).not_to exist(File.join(export_path, 'avatar', 'rails_sample.png')) + expect(File).to exist(exported_filepath) + end + end + context 'when upload has underlying file missing' do context 'with an upload missing its file' do it 'does not cause errors' do @@ -53,6 +62,16 @@ RSpec.describe BulkImports::UploadsExportService do } ) + expect(Gitlab::ErrorTracking) + .to receive(:log_exception) + .with( + instance_of(exception), { + portable_id: project.id, + portable_class: 'Project', + upload_id: project.avatar.upload.id + } + ) + service.execute expect(File).not_to exist(exported_filepath) @@ -73,4 +92,12 @@ RSpec.describe BulkImports::UploadsExportService do end end end + + describe '#exported_objects_count' do + it 'return the number of exported uploads' do + service.execute + + expect(service.exported_objects_count).to eq(2) + end + end end diff --git a/spec/services/bulk_push_event_payload_service_spec.rb b/spec/services/bulk_push_event_payload_service_spec.rb index 381c735c003..95e1c831498 100644 --- a/spec/services/bulk_push_event_payload_service_spec.rb +++ b/spec/services/bulk_push_event_payload_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkPushEventPayloadService do +RSpec.describe BulkPushEventPayloadService, feature_category: :source_code_management do let(:event) { create(:push_event) } let(:push_data) do diff --git a/spec/services/bulk_update_integration_service_spec.rb b/spec/services/bulk_update_integration_service_spec.rb index 24a868b524d..9095fa9a0fa 100644 --- a/spec/services/bulk_update_integration_service_spec.rb +++ b/spec/services/bulk_update_integration_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkUpdateIntegrationService do +RSpec.describe BulkUpdateIntegrationService, feature_category: :integrations do include JiraIntegrationHelpers before_all do @@ -56,14 +56,14 @@ RSpec.describe BulkUpdateIntegrationService do end it 'does not change the created_at timestamp' do - subgroup_integration.update_column(:created_at, Time.utc('2022-01-01')) + subgroup_integration.update_column(:created_at, Time.utc(2022, 1, 1)) expect do described_class.new(subgroup_integration, batch).execute end.not_to change { integration.reload.created_at } end - it 'sets the updated_at timestamp to the current time', time_travel_to: Time.utc('2022-01-01') do + it 'sets the updated_at timestamp to the current time', time_travel_to: Time.utc(2022, 1, 1) do expect do described_class.new(subgroup_integration, batch).execute end.to change { integration.reload.updated_at }.to(Time.current) @@ -85,14 +85,14 @@ RSpec.describe BulkUpdateIntegrationService do end it 'does not change the created_at timestamp' do - subgroup_integration.data_fields.update_column(:created_at, Time.utc('2022-01-02')) + subgroup_integration.data_fields.update_column(:created_at, Time.utc(2022, 1, 2)) expect do described_class.new(subgroup_integration, batch).execute end.not_to change { integration.data_fields.reload.created_at } end - it 'sets the updated_at timestamp to the current time', time_travel_to: Time.utc('2022-01-01') do + it 'sets the updated_at timestamp to the current time', time_travel_to: Time.utc(2022, 1, 1) do expect do described_class.new(subgroup_integration, batch).execute end.to change { integration.data_fields.reload.updated_at }.to(Time.current) diff --git a/spec/services/captcha/captcha_verification_service_spec.rb b/spec/services/captcha/captcha_verification_service_spec.rb index fe2199fb53e..b67e725bf91 100644 --- a/spec/services/captcha/captcha_verification_service_spec.rb +++ b/spec/services/captcha/captcha_verification_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Captcha::CaptchaVerificationService do +RSpec.describe Captcha::CaptchaVerificationService, feature_category: :team_planning do describe '#execute' do let(:captcha_response) { 'abc123' } let(:fake_ip) { '1.2.3.4' } diff --git a/spec/services/chat_names/find_user_service_spec.rb b/spec/services/chat_names/find_user_service_spec.rb index 10cb0a2f065..14bece4efb4 100644 --- a/spec/services/chat_names/find_user_service_spec.rb +++ b/spec/services/chat_names/find_user_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do +RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state, feature_category: :user_profile do describe '#execute' do subject { described_class.new(team_id, user_id).execute } diff --git a/spec/services/ci/abort_pipelines_service_spec.rb b/spec/services/ci/abort_pipelines_service_spec.rb index e43faf0af51..60f3ee11442 100644 --- a/spec/services/ci/abort_pipelines_service_spec.rb +++ b/spec/services/ci/abort_pipelines_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::AbortPipelinesService do +RSpec.describe Ci::AbortPipelinesService, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/ci/append_build_trace_service_spec.rb b/spec/services/ci/append_build_trace_service_spec.rb index 20f7967d1f1..113c88dc5f3 100644 --- a/spec/services/ci/append_build_trace_service_spec.rb +++ b/spec/services/ci/append_build_trace_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::AppendBuildTraceService do +RSpec.describe Ci::AppendBuildTraceService, feature_category: :continuous_integration do let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let_it_be_with_reload(:build) { create(:ci_build, :running, pipeline: pipeline) } diff --git a/spec/services/ci/archive_trace_service_spec.rb b/spec/services/ci/archive_trace_service_spec.rb index 3fb9d092ae7..e7f489e841a 100644 --- a/spec/services/ci/archive_trace_service_spec.rb +++ b/spec/services/ci/archive_trace_service_spec.rb @@ -63,19 +63,6 @@ RSpec.describe Ci::ArchiveTraceService, '#execute', feature_category: :continuou end end - context 'when job does not have trace' do - let(:job) { create(:ci_build, :success) } - - it 'leaves a warning message in sidekiq log' do - expect(Sidekiq.logger).to receive(:warn).with( - class: Ci::ArchiveTraceWorker.name, - message: 'The job does not have live trace but going to be archived.', - job_id: job.id) - - subject - end - end - context 'when the job is out of archival attempts' do before do create(:ci_build_trace_metadata, @@ -149,23 +136,6 @@ RSpec.describe Ci::ArchiveTraceService, '#execute', feature_category: :continuou subject end end - - context 'when job failed to archive trace but did not raise an exception' do - before do - allow_next_instance_of(Gitlab::Ci::Trace) do |instance| - allow(instance).to receive(:archive!) {} - end - end - - it 'leaves a warning message in sidekiq log' do - expect(Sidekiq.logger).to receive(:warn).with( - class: Ci::ArchiveTraceWorker.name, - message: 'The job does not have archived trace after archiving.', - job_id: job.id) - - subject - end - end end context 'when job is running' do @@ -175,8 +145,8 @@ RSpec.describe Ci::ArchiveTraceService, '#execute', feature_category: :continuou expect(Gitlab::ErrorTracking) .to receive(:track_and_raise_for_dev_exception) .with(::Gitlab::Ci::Trace::ArchiveError, - issue_url: 'https://gitlab.com/gitlab-org/gitlab-foss/issues/51502', - job_id: job.id).once + issue_url: 'https://gitlab.com/gitlab-org/gitlab-foss/issues/51502', + job_id: job.id).once expect(Sidekiq.logger).to receive(:warn).with( class: Ci::ArchiveTraceWorker.name, diff --git a/spec/services/ci/build_cancel_service_spec.rb b/spec/services/ci/build_cancel_service_spec.rb index fe036dc1368..314d2e86bd3 100644 --- a/spec/services/ci/build_cancel_service_spec.rb +++ b/spec/services/ci/build_cancel_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::BuildCancelService do +RSpec.describe Ci::BuildCancelService, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/services/ci/build_erase_service_spec.rb b/spec/services/ci/build_erase_service_spec.rb index e750a163621..35e74f76a26 100644 --- a/spec/services/ci/build_erase_service_spec.rb +++ b/spec/services/ci/build_erase_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::BuildEraseService do +RSpec.describe Ci::BuildEraseService, feature_category: :continuous_integration do let_it_be(:user) { user } let(:build) { create(:ci_build, :artifacts, :trace_artifact, artifacts_expire_at: 100.days.from_now) } diff --git a/spec/services/ci/build_report_result_service_spec.rb b/spec/services/ci/build_report_result_service_spec.rb index c5238b7f5e0..c3ce6714241 100644 --- a/spec/services/ci/build_report_result_service_spec.rb +++ b/spec/services/ci/build_report_result_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::BuildReportResultService do +RSpec.describe Ci::BuildReportResultService, feature_category: :continuous_integration do describe '#execute', :clean_gitlab_redis_shared_state do subject(:build_report_result) { described_class.new.execute(build) } diff --git a/spec/services/ci/build_unschedule_service_spec.rb b/spec/services/ci/build_unschedule_service_spec.rb index d784d9a2754..539c66047e4 100644 --- a/spec/services/ci/build_unschedule_service_spec.rb +++ b/spec/services/ci/build_unschedule_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::BuildUnscheduleService do +RSpec.describe Ci::BuildUnscheduleService, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/services/ci/catalog/validate_resource_service_spec.rb b/spec/services/ci/catalog/validate_resource_service_spec.rb new file mode 100644 index 00000000000..3bee37b7e55 --- /dev/null +++ b/spec/services/ci/catalog/validate_resource_service_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::Catalog::ValidateResourceService, feature_category: :pipeline_composition do + describe '#execute' do + context 'with a project that has a README and a description' do + it 'is valid' do + project = create(:project, :repository, description: 'Component project') + response = described_class.new(project, project.default_branch).execute + + expect(response).to be_success + end + end + + context 'with a project that has neither a description nor a README' do + it 'is not valid' do + project = create(:project, :empty_repo) + project.repository.create_file( + project.creator, + 'ruby.rb', + 'I like this', + message: 'Ruby like this', + branch_name: 'master' + ) + response = described_class.new(project, project.default_branch).execute + + expect(response.message).to eq('Project must have a README , Project must have a description') + end + end + + context 'with a project that has a description but not a README' do + it 'is not valid' do + project = create(:project, :empty_repo, description: 'project with no README') + project.repository.create_file( + project.creator, + 'text.txt', + 'I do not like this', + message: 'only text like text', + branch_name: 'master' + ) + response = described_class.new(project, project.default_branch).execute + + expect(response.message).to eq('Project must have a README') + end + end + + context 'with a project that has a README and not a description' do + it 'is not valid' do + project = create(:project, :repository) + response = described_class.new(project, project.default_branch).execute + + expect(response.message).to eq('Project must have a description') + end + end + end +end diff --git a/spec/services/ci/change_variable_service_spec.rb b/spec/services/ci/change_variable_service_spec.rb index f86a87132b1..fd2ddded375 100644 --- a/spec/services/ci/change_variable_service_spec.rb +++ b/spec/services/ci/change_variable_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::ChangeVariableService do +RSpec.describe Ci::ChangeVariableService, feature_category: :secrets_management do let(:service) { described_class.new(container: group, current_user: user, params: params) } let_it_be(:user) { create(:user) } diff --git a/spec/services/ci/change_variables_service_spec.rb b/spec/services/ci/change_variables_service_spec.rb index b710ca78554..e22aebb8f5d 100644 --- a/spec/services/ci/change_variables_service_spec.rb +++ b/spec/services/ci/change_variables_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::ChangeVariablesService do +RSpec.describe Ci::ChangeVariablesService, feature_category: :secrets_management do let(:service) { described_class.new(container: group, current_user: user, params: params) } let_it_be(:user) { create(:user) } diff --git a/spec/services/ci/compare_accessibility_reports_service_spec.rb b/spec/services/ci/compare_accessibility_reports_service_spec.rb index e0b84219834..57514c18042 100644 --- a/spec/services/ci/compare_accessibility_reports_service_spec.rb +++ b/spec/services/ci/compare_accessibility_reports_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::CompareAccessibilityReportsService do +RSpec.describe Ci::CompareAccessibilityReportsService, feature_category: :continuous_integration do let(:service) { described_class.new(project) } let(:project) { create(:project, :repository) } diff --git a/spec/services/ci/compare_codequality_reports_service_spec.rb b/spec/services/ci/compare_codequality_reports_service_spec.rb index ef762a2e9ad..de2300c354b 100644 --- a/spec/services/ci/compare_codequality_reports_service_spec.rb +++ b/spec/services/ci/compare_codequality_reports_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::CompareCodequalityReportsService do +RSpec.describe Ci::CompareCodequalityReportsService, feature_category: :continuous_integration do let(:service) { described_class.new(project) } let(:project) { create(:project, :repository) } diff --git a/spec/services/ci/compare_reports_base_service_spec.rb b/spec/services/ci/compare_reports_base_service_spec.rb index 20d8cd37553..2906d61066d 100644 --- a/spec/services/ci/compare_reports_base_service_spec.rb +++ b/spec/services/ci/compare_reports_base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::CompareReportsBaseService do +RSpec.describe Ci::CompareReportsBaseService, feature_category: :continuous_integration do let(:service) { described_class.new(project) } let(:project) { create(:project, :repository) } diff --git a/spec/services/ci/compare_test_reports_service_spec.rb b/spec/services/ci/compare_test_reports_service_spec.rb index f259072fe87..d29cef0c583 100644 --- a/spec/services/ci/compare_test_reports_service_spec.rb +++ b/spec/services/ci/compare_test_reports_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::CompareTestReportsService do +RSpec.describe Ci::CompareTestReportsService, feature_category: :continuous_integration do let(:service) { described_class.new(project) } let(:project) { create(:project, :repository) } diff --git a/spec/services/ci/components/fetch_service_spec.rb b/spec/services/ci/components/fetch_service_spec.rb index f2eaa8d31b4..532098b3b20 100644 --- a/spec/services/ci/components/fetch_service_spec.rb +++ b/spec/services/ci/components/fetch_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::Components::FetchService, feature_category: :pipeline_authoring do +RSpec.describe Ci::Components::FetchService, feature_category: :pipeline_composition do let_it_be(:project) { create(:project, :repository, create_tag: 'v1.0') } let_it_be(:user) { create(:user) } let_it_be(:current_user) { user } diff --git a/spec/services/ci/copy_cross_database_associations_service_spec.rb b/spec/services/ci/copy_cross_database_associations_service_spec.rb index 5938ac258d0..2be0cbd9582 100644 --- a/spec/services/ci/copy_cross_database_associations_service_spec.rb +++ b/spec/services/ci/copy_cross_database_associations_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::CopyCrossDatabaseAssociationsService do +RSpec.describe Ci::CopyCrossDatabaseAssociationsService, feature_category: :continuous_integration do let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let_it_be(:old_build) { create(:ci_build, pipeline: pipeline) } diff --git a/spec/services/ci/create_downstream_pipeline_service_spec.rb b/spec/services/ci/create_downstream_pipeline_service_spec.rb index 7b576339c61..6da6ec3379a 100644 --- a/spec/services/ci/create_downstream_pipeline_service_spec.rb +++ b/spec/services/ci/create_downstream_pipeline_service_spec.rb @@ -26,10 +26,13 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute', feature_category end let(:bridge) do - create(:ci_bridge, status: :pending, - user: user, - options: trigger, - pipeline: upstream_pipeline) + create( + :ci_bridge, + status: :pending, + user: user, + options: trigger, + pipeline: upstream_pipeline + ) end let(:service) { described_class.new(upstream_project, user) } @@ -704,8 +707,7 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute', feature_category context 'when user does not have access to push protected branch of downstream project' do before do - create(:protected_branch, :maintainers_can_push, - project: downstream_project, name: 'feature') + create(:protected_branch, :maintainers_can_push, project: downstream_project, name: 'feature') end it 'changes status of the bridge build' do diff --git a/spec/services/ci/create_pipeline_service/artifacts_spec.rb b/spec/services/ci/create_pipeline_service/artifacts_spec.rb index e5e405492a0..c193900b2d3 100644 --- a/spec/services/ci/create_pipeline_service/artifacts_spec.rb +++ b/spec/services/ci/create_pipeline_service/artifacts_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, feature_category: :build_artifacts do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/cache_spec.rb b/spec/services/ci/create_pipeline_service/cache_spec.rb index f9640f99031..2a65f92bfd6 100644 --- a/spec/services/ci/create_pipeline_service/cache_spec.rb +++ b/spec/services/ci/create_pipeline_service/cache_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do context 'cache' do let(:project) { create(:project, :custom_repo, files: files) } let(:user) { project.first_owner } @@ -38,7 +39,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes policy: 'pull-push', untracked: true, unprotect: false, - when: 'on_success' + when: 'on_success', + fallback_keys: [] } expect(pipeline).to be_persisted @@ -71,7 +73,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes paths: ['logs/'], policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] } expect(pipeline).to be_persisted @@ -88,7 +91,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes paths: ['logs/'], policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] } expect(pipeline).to be_persisted @@ -122,7 +126,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes paths: ['logs/'], policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] } expect(pipeline).to be_persisted @@ -139,7 +144,8 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes paths: ['logs/'], policy: 'pull-push', when: 'on_success', - unprotect: false + unprotect: false, + fallback_keys: [] } expect(pipeline).to be_persisted diff --git a/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb index 0ebcecdd6e6..036116dc037 100644 --- a/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb +++ b/spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, feature_category: :continuous_integration do describe 'creation errors and warnings' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb index 0d5017a763f..e6bdb2a3fc6 100644 --- a/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb +++ b/spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb @@ -2,11 +2,12 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, '#execute', :yaml_processor_feature_flag_corectness do - let_it_be(:group) { create(:group, name: 'my-organization') } +RSpec.describe Ci::CreatePipelineService, '#execute', :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do + let_it_be(:group) { create(:group) } - let(:upstream_project) { create(:project, :repository, name: 'upstream', group: group) } - let(:downstream_project) { create(:project, :repository, name: 'downstream', group: group) } + let(:upstream_project) { create(:project, :repository, group: group) } + let(:downstream_project) { create(:project, :repository, group: group) } let(:user) { create(:user) } let(:service) do @@ -27,7 +28,7 @@ RSpec.describe Ci::CreatePipelineService, '#execute', :yaml_processor_feature_fl stage: test resource_group: iOS trigger: - project: my-organization/downstream + project: #{downstream_project.full_path} strategy: depend YAML end diff --git a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb index dafa227c4c8..819946bfd27 100644 --- a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb +++ b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/custom_yaml_tags_spec.rb b/spec/services/ci/create_pipeline_service/custom_yaml_tags_spec.rb index 3b042f05fc0..b78ad68194a 100644 --- a/spec/services/ci/create_pipeline_service/custom_yaml_tags_spec.rb +++ b/spec/services/ci/create_pipeline_service/custom_yaml_tags_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do describe '!reference tags' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/dry_run_spec.rb b/spec/services/ci/create_pipeline_service/dry_run_spec.rb index de1ed251c82..7136fa5dc13 100644 --- a/spec/services/ci/create_pipeline_service/dry_run_spec.rb +++ b/spec/services/ci/create_pipeline_service/dry_run_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/environment_spec.rb b/spec/services/ci/create_pipeline_service/environment_spec.rb index b713cad2cad..96e54af43cd 100644 --- a/spec/services/ci/create_pipeline_service/environment_spec.rb +++ b/spec/services/ci/create_pipeline_service/environment_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :pipeline_composition do let_it_be(:project) { create(:project, :repository) } let_it_be(:developer) { create(:user) } diff --git a/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb b/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb index e84726d31f6..b40e504f99b 100644 --- a/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb +++ b/spec/services/ci/create_pipeline_service/evaluate_runner_tags_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :pipeline_composition do let_it_be(:group) { create(:group, :private) } let_it_be(:group_variable) { create(:ci_group_variable, group: group, key: 'RUNNER_TAG', value: 'group') } let_it_be(:project) { create(:project, :repository, group: group) } diff --git a/spec/services/ci/create_pipeline_service/include_spec.rb b/spec/services/ci/create_pipeline_service/include_spec.rb index f18b4883aaf..1280ab4b7bd 100644 --- a/spec/services/ci/create_pipeline_service/include_spec.rb +++ b/spec/services/ci/create_pipeline_service/include_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::CreatePipelineService, -:yaml_processor_feature_flag_corectness, feature_category: :pipeline_authoring do + :yaml_processor_feature_flag_corectness, feature_category: :pipeline_composition do include RepoHelpers context 'include:' do diff --git a/spec/services/ci/create_pipeline_service/limit_active_jobs_spec.rb b/spec/services/ci/create_pipeline_service/limit_active_jobs_spec.rb index 003d109a27c..b0730eaf215 100644 --- a/spec/services/ci/create_pipeline_service/limit_active_jobs_spec.rb +++ b/spec/services/ci/create_pipeline_service/limit_active_jobs_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } let_it_be(:existing_pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/services/ci/create_pipeline_service/logger_spec.rb b/spec/services/ci/create_pipeline_service/logger_spec.rb index ecb24a61075..6a1987fcc7c 100644 --- a/spec/services/ci/create_pipeline_service/logger_spec.rb +++ b/spec/services/ci/create_pipeline_service/logger_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe Ci::CreatePipelineService, # rubocop: disable RSpec/FilePath - :yaml_processor_feature_flag_corectness, - feature_category: :continuous_integration do + :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do describe 'pipeline logger' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } @@ -142,7 +142,7 @@ RSpec.describe Ci::CreatePipelineService, # rubocop: disable RSpec/FilePath describe 'pipeline includes count' do before do - stub_const('Gitlab::Ci::Config::External::Context::MAX_INCLUDES', 2) + stub_const('Gitlab::Ci::Config::External::Context::TEMP_MAX_INCLUDES', 2) end context 'when the includes count exceeds the maximum' do diff --git a/spec/services/ci/create_pipeline_service/merge_requests_spec.rb b/spec/services/ci/create_pipeline_service/merge_requests_spec.rb index 80f48451e5c..25f6e43d600 100644 --- a/spec/services/ci/create_pipeline_service/merge_requests_spec.rb +++ b/spec/services/ci/create_pipeline_service/merge_requests_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do context 'merge requests handling' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } @@ -30,11 +31,13 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes context 'when pushing a change' do context 'when a merge request already exists' do let!(:merge_request) do - create(:merge_request, - source_project: project, - source_branch: 'feature', - target_project: project, - target_branch: 'master') + create( + :merge_request, + source_project: project, + source_branch: 'feature', + target_project: project, + target_branch: 'master' + ) end it 'does not create a pipeline' do diff --git a/spec/services/ci/create_pipeline_service/needs_spec.rb b/spec/services/ci/create_pipeline_service/needs_spec.rb index 38e330316ea..068cad68e64 100644 --- a/spec/services/ci/create_pipeline_service/needs_spec.rb +++ b/spec/services/ci/create_pipeline_service/needs_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :pipeline_composition do context 'needs' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/parallel_spec.rb b/spec/services/ci/create_pipeline_service/parallel_spec.rb index 5ee378a9719..71434fe0b0c 100644 --- a/spec/services/ci/create_pipeline_service/parallel_spec.rb +++ b/spec/services/ci/create_pipeline_service/parallel_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/parameter_content_spec.rb b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb index cae88bb67cf..16555dd68d6 100644 --- a/spec/services/ci/create_pipeline_service/parameter_content_spec.rb +++ b/spec/services/ci/create_pipeline_service/parameter_content_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb index eb17935967c..e644273df9a 100644 --- a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb +++ b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, '#execute', :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, '#execute', :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/services/ci/create_pipeline_service/partitioning_spec.rb b/spec/services/ci/create_pipeline_service/partitioning_spec.rb index a87135cefdd..70c4eb49698 100644 --- a/spec/services/ci/create_pipeline_service/partitioning_spec.rb +++ b/spec/services/ci/create_pipeline_service/partitioning_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, :aggregate_failures, -:ci_partitionable, feature_category: :continuous_integration do + :ci_partitionable, feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb b/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb index db110bdc608..d935824e6cc 100644 --- a/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb +++ b/spec/services/ci/create_pipeline_service/pre_post_stages_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do describe '.pre/.post stages' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/rate_limit_spec.rb b/spec/services/ci/create_pipeline_service/rate_limit_spec.rb index dfa74870341..26a9484dbc4 100644 --- a/spec/services/ci/create_pipeline_service/rate_limit_spec.rb +++ b/spec/services/ci/create_pipeline_service/rate_limit_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' RSpec.describe Ci::CreatePipelineService, :freeze_time, - :clean_gitlab_redis_rate_limiting, - :yaml_processor_feature_flag_corectness do + :clean_gitlab_redis_rate_limiting, + :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do describe 'rate limiting' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb index 26bb8b7d006..87112137675 100644 --- a/spec/services/ci/create_pipeline_service/rules_spec.rb +++ b/spec/services/ci/create_pipeline_service/rules_spec.rb @@ -1,25 +1,18 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, feature_category: :pipeline_authoring do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, feature_category: :pipeline_composition do let(:project) { create(:project, :repository) } let(:user) { project.first_owner } let(:ref) { 'refs/heads/master' } let(:source) { :push } - let(:service) { described_class.new(project, user, { ref: ref }) } - let(:response) { execute_service } + let(:service) { described_class.new(project, user, initialization_params) } + let(:response) { service.execute(source) } let(:pipeline) { response.payload } let(:build_names) { pipeline.builds.pluck(:name) } - def execute_service(before: '00000000', variables_attributes: nil) - params = { ref: ref, before: before, after: project.commit(ref).sha, variables_attributes: variables_attributes } - - described_class - .new(project, user, params) - .execute(source) do |pipeline| - yield(pipeline) if block_given? - end - end + let(:base_initialization_params) { { ref: ref, before: '00000000', after: project.commit(ref).sha, variables_attributes: nil } } + let(:initialization_params) { base_initialization_params } context 'job:rules' do let(:regular_job) { find_job('regular-job') } @@ -393,6 +386,109 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes expect(regular_job.allow_failure).to eq(true) end end + + context 'with needs:' do + let(:config) do + <<-EOY + job1: + script: ls + + job2: + script: ls + rules: + - if: $var == null + needs: [job1] + - when: on_success + + job3: + script: ls + rules: + - if: $var == null + needs: [job1] + - needs: [job2] + + job4: + script: ls + needs: [job1] + rules: + - if: $var == null + needs: [job2] + - when: on_success + needs: [job3] + EOY + end + + let(:job1) { pipeline.builds.find_by(name: 'job1') } + let(:job2) { pipeline.builds.find_by(name: 'job2') } + let(:job3) { pipeline.builds.find_by(name: 'job3') } + let(:job4) { pipeline.builds.find_by(name: 'job4') } + + context 'when the `$var` rule matches' do + it 'creates a pipeline with 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 contain_exactly(an_object_having_attributes(name: 'job1')) + expect(job3.needs).to contain_exactly(an_object_having_attributes(name: 'job1')) + expect(job4.needs).to contain_exactly(an_object_having_attributes(name: 'job2')) + 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 with 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 contain_exactly(an_object_having_attributes(name: 'job2')) + 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 context 'changes:' do @@ -516,11 +612,10 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes ) end + let(:initialization_params) { base_initialization_params.merge(before: nil) } let(:changed_file) { 'file2.txt' } let(:ref) { 'feature_2' } - let(:response) { execute_service(before: nil) } - context 'for jobs rules' do let(:config) do <<-EOY @@ -1230,9 +1325,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes end context 'with pipeline variables' do - let(:pipeline) do - execute_service(variables_attributes: variables_attributes).payload - end + let(:initialization_params) { base_initialization_params.merge(variables_attributes: variables_attributes) } let(:config) do <<-EOY @@ -1267,10 +1360,10 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes end context 'with trigger variables' do - let(:pipeline) do - execute_service do |pipeline| + let(:response) do + service.execute(source) do |pipeline| pipeline.variables.build(variables) - end.payload + end end let(:config) do @@ -1434,10 +1527,10 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes [{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAL' }] end - let(:pipeline) do - execute_service do |pipeline| + let(:response) do + service.execute(source) do |pipeline| pipeline.variables.build(variables) - end.payload + end end let(:config) do diff --git a/spec/services/ci/create_pipeline_service/scripts_spec.rb b/spec/services/ci/create_pipeline_service/scripts_spec.rb index 50b558e505a..d541257a086 100644 --- a/spec/services/ci/create_pipeline_service/scripts_spec.rb +++ b/spec/services/ci/create_pipeline_service/scripts_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } @@ -83,30 +84,5 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes options: { script: ["echo 'hello job3 script'"] } ) end - - context 'when the FF ci_hooks_pre_get_sources_script is disabled' do - before do - stub_feature_flags(ci_hooks_pre_get_sources_script: false) - end - - it 'creates jobs without hook data' do - expect(pipeline).to be_created_successfully - expect(pipeline.builds.find_by(name: 'job1')).to have_attributes( - name: 'job1', - stage: 'test', - options: { script: ["echo 'hello job1 script'"] } - ) - expect(pipeline.builds.find_by(name: 'job2')).to have_attributes( - name: 'job2', - stage: 'test', - options: { script: ["echo 'hello job2 script'"] } - ) - expect(pipeline.builds.find_by(name: 'job3')).to have_attributes( - name: 'job3', - stage: 'test', - options: { script: ["echo 'hello job3 script'"] } - ) - end - end end end diff --git a/spec/services/ci/create_pipeline_service/tags_spec.rb b/spec/services/ci/create_pipeline_service/tags_spec.rb index 7450df11eac..cb2dbf1c3a4 100644 --- a/spec/services/ci/create_pipeline_service/tags_spec.rb +++ b/spec/services/ci/create_pipeline_service/tags_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, feature_category: :continuous_integration do describe 'tags:' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service/variables_spec.rb b/spec/services/ci/create_pipeline_service/variables_spec.rb index fd138bde656..aac9a0c9c2d 100644 --- a/spec/services/ci/create_pipeline_service/variables_spec.rb +++ b/spec/services/ci/create_pipeline_service/variables_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness do +RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectness, + feature_category: :secrets_management do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { project.first_owner } diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index b0ba07ea295..b08dda72a69 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -794,7 +794,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes before do config = YAML.dump( deploy: { - environment: { name: "review/id1$CI_PIPELINE_ID/id2$CI_BUILD_ID" }, + environment: { name: "review/id1$CI_PIPELINE_ID/id2$CI_JOB_ID" }, script: 'ls' } ) @@ -802,7 +802,7 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes stub_ci_pipeline_yaml_file(config) end - it 'skipps persisted variables in environment name' do + it 'skips persisted variables in environment name' do result = execute_service.payload expect(result).to be_persisted @@ -810,6 +810,32 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes end end + context 'when FF `ci_remove_legacy_predefined_variables` is disabled' do + before do + stub_feature_flags(ci_remove_legacy_predefined_variables: false) + end + + context 'with environment name including persisted variables' do + before do + config = YAML.dump( + deploy: { + environment: { name: "review/id1$CI_PIPELINE_ID/id2$CI_BUILD_ID" }, + script: 'ls' + } + ) + + stub_ci_pipeline_yaml_file(config) + end + + it 'skips persisted variables in environment name' do + result = execute_service.payload + + expect(result).to be_persisted + expect(Environment.find_by(name: "review/id1/id2")).to be_present + end + end + end + context 'environment with Kubernetes configuration' do let(:kubernetes_namespace) { 'custom-namespace' } @@ -1167,8 +1193,10 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes context 'when pipeline is running for a tag' do before do - config = YAML.dump(test: { script: 'test', only: ['branches'] }, - deploy: { script: 'deploy', only: ['tags'] }) + config = YAML.dump( + test: { script: 'test', only: ['branches'] }, + deploy: { script: 'deploy', only: ['tags'] } + ) stub_ci_pipeline_yaml_file(config) end @@ -1343,11 +1371,13 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes describe 'Pipeline for external pull requests' do let(:response) do - execute_service(source: source, - external_pull_request: pull_request, - ref: ref_name, - source_sha: source_sha, - target_sha: target_sha) + execute_service( + source: source, + external_pull_request: pull_request, + ref: ref_name, + source_sha: source_sha, + target_sha: target_sha + ) end let(:pipeline) { response.payload } @@ -1499,11 +1529,13 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes describe 'Pipelines for merge requests' do let(:response) do - execute_service(source: source, - merge_request: merge_request, - ref: ref_name, - source_sha: source_sha, - target_sha: target_sha) + execute_service( + source: source, + merge_request: merge_request, + ref: ref_name, + source_sha: source_sha, + target_sha: target_sha + ) end let(:pipeline) { response.payload } @@ -1898,5 +1930,278 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes end end end + + describe 'pipeline components' do + let(:components_project) do + create(:project, :repository, creator: user, namespace: user.namespace) + end + + let(:component_path) do + "#{Gitlab.config.gitlab.host}/#{components_project.full_path}/my-component@v0.1" + end + + let(:template) do + <<~YAML + spec: + inputs: + stage: + suffix: + default: my-job + --- + test-$[[ inputs.suffix ]]: + stage: $[[ inputs.stage ]] + script: run tests + YAML + end + + let(:sha) do + components_project.repository.create_file( + user, + 'my-component/template.yml', + template, + message: 'Add my first CI component', + branch_name: 'master' + ) + end + + let(:config) do + <<~YAML + include: + - component: #{component_path} + inputs: + stage: my-stage + + stages: + - my-stage + + test-1: + stage: my-stage + script: run test-1 + YAML + end + + before do + stub_ci_pipeline_yaml_file(config) + end + + context 'when there is no version with specified tag' do + before do + components_project.repository.add_tag(user, 'v0.01', sha) + end + + it 'does not create a pipeline' do + response = execute_service(save_on_errors: true) + + pipeline = response.payload + + expect(pipeline).to be_persisted + expect(pipeline.yaml_errors) + .to include "my-component@v0.1' - content not found" + end + end + + context 'when there is a proper revision available' do + before do + components_project.repository.add_tag(user, 'v0.1', sha) + end + + context 'when component is valid' do + it 'creates a pipeline using a pipeline component' do + response = execute_service(save_on_errors: true) + + pipeline = response.payload + + expect(pipeline).to be_persisted + expect(pipeline.yaml_errors).to be_blank + expect(pipeline.statuses.count).to eq 2 + expect(pipeline.statuses.map(&:name)).to match_array %w[test-1 test-my-job] + end + end + + context 'when interpolation is invalid' do + let(:template) do + <<~YAML + spec: + inputs: + stage: + --- + test: + stage: $[[ inputs.stage ]] + script: rspec --suite $[[ inputs.suite ]] + YAML + end + + it 'does not create a pipeline' do + response = execute_service(save_on_errors: true) + + pipeline = response.payload + + expect(pipeline).to be_persisted + expect(pipeline.yaml_errors) + .to include 'interpolation interrupted by errors, unknown interpolation key: `suite`' + end + end + + context 'when there is a syntax error in the template' do + let(:template) do + <<~YAML + spec: + inputs: + stage: + --- + :test + stage: $[[ inputs.stage ]] + YAML + end + + it 'does not create a pipeline' do + response = execute_service(save_on_errors: true) + + pipeline = response.payload + + expect(pipeline).to be_persisted + expect(pipeline.yaml_errors) + .to include 'content does not have a valid YAML syntax' + end + end + end + end + + # TODO: Remove this test section when include:with is removed as part of https://gitlab.com/gitlab-org/gitlab/-/issues/408369 + describe 'pipeline components using include:with instead of include:inputs' do + let(:components_project) do + create(:project, :repository, creator: user, namespace: user.namespace) + end + + let(:component_path) do + "#{Gitlab.config.gitlab.host}/#{components_project.full_path}/my-component@v0.1" + end + + let(:template) do + <<~YAML + spec: + inputs: + stage: + suffix: + default: my-job + --- + test-$[[ inputs.suffix ]]: + stage: $[[ inputs.stage ]] + script: run tests + YAML + end + + let(:sha) do + components_project.repository.create_file( + user, + 'my-component/template.yml', + template, + message: 'Add my first CI component', + branch_name: 'master' + ) + end + + let(:config) do + <<~YAML + include: + - component: #{component_path} + with: + stage: my-stage + + stages: + - my-stage + + test-1: + stage: my-stage + script: run test-1 + YAML + end + + before do + stub_ci_pipeline_yaml_file(config) + end + + context 'when there is no version with specified tag' do + before do + components_project.repository.add_tag(user, 'v0.01', sha) + end + + it 'does not create a pipeline' do + response = execute_service(save_on_errors: true) + + pipeline = response.payload + + expect(pipeline).to be_persisted + expect(pipeline.yaml_errors) + .to include "my-component@v0.1' - content not found" + end + end + + context 'when there is a proper revision available' do + before do + components_project.repository.add_tag(user, 'v0.1', sha) + end + + context 'when component is valid' do + it 'creates a pipeline using a pipeline component' do + response = execute_service(save_on_errors: true) + + pipeline = response.payload + + expect(pipeline).to be_persisted + expect(pipeline.yaml_errors).to be_blank + expect(pipeline.statuses.count).to eq 2 + expect(pipeline.statuses.map(&:name)).to match_array %w[test-1 test-my-job] + end + end + + context 'when interpolation is invalid' do + let(:template) do + <<~YAML + spec: + inputs: + stage: + --- + test: + stage: $[[ inputs.stage ]] + script: rspec --suite $[[ inputs.suite ]] + YAML + end + + it 'does not create a pipeline' do + response = execute_service(save_on_errors: true) + + pipeline = response.payload + + expect(pipeline).to be_persisted + expect(pipeline.yaml_errors) + .to include 'interpolation interrupted by errors, unknown interpolation key: `suite`' + end + end + + context 'when there is a syntax error in the template' do + let(:template) do + <<~YAML + spec: + inputs: + stage: + --- + :test + stage: $[[ inputs.stage ]] + YAML + end + + it 'does not create a pipeline' do + response = execute_service(save_on_errors: true) + + pipeline = response.payload + + expect(pipeline).to be_persisted + expect(pipeline.yaml_errors) + .to include 'content does not have a valid YAML syntax' + end + end + end + end end end diff --git a/spec/services/ci/create_web_ide_terminal_service_spec.rb b/spec/services/ci/create_web_ide_terminal_service_spec.rb index 3462b48cfe7..b22ca1472b7 100644 --- a/spec/services/ci/create_web_ide_terminal_service_spec.rb +++ b/spec/services/ci/create_web_ide_terminal_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::CreateWebIdeTerminalService do +RSpec.describe Ci::CreateWebIdeTerminalService, feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/services/ci/daily_build_group_report_result_service_spec.rb b/spec/services/ci/daily_build_group_report_result_service_spec.rb index 32651247adb..bb6ce559fbd 100644 --- a/spec/services/ci/daily_build_group_report_result_service_spec.rb +++ b/spec/services/ci/daily_build_group_report_result_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::DailyBuildGroupReportResultService, '#execute' do +RSpec.describe Ci::DailyBuildGroupReportResultService, '#execute', feature_category: :continuous_integration do let_it_be(:group) { create(:group, :private) } let_it_be(:pipeline) { create(:ci_pipeline, project: create(:project, group: group), created_at: '2020-02-06 00:01:10') } let_it_be(:rspec_job) { create(:ci_build, pipeline: pipeline, name: 'rspec 3/3', coverage: 80) } diff --git a/spec/services/ci/delete_objects_service_spec.rb b/spec/services/ci/delete_objects_service_spec.rb index 448f8979681..939b72cef3b 100644 --- a/spec/services/ci/delete_objects_service_spec.rb +++ b/spec/services/ci/delete_objects_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::DeleteObjectsService, :aggregate_failure do +RSpec.describe Ci::DeleteObjectsService, :aggregate_failures, feature_category: :continuous_integration do let(:service) { described_class.new } let(:artifact) { create(:ci_job_artifact, :archive) } let(:data) { [artifact] } diff --git a/spec/services/ci/delete_unit_tests_service_spec.rb b/spec/services/ci/delete_unit_tests_service_spec.rb index 4c63c513d48..2f07e709107 100644 --- a/spec/services/ci/delete_unit_tests_service_spec.rb +++ b/spec/services/ci/delete_unit_tests_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::DeleteUnitTestsService do +RSpec.describe Ci::DeleteUnitTestsService, feature_category: :continuous_integration do describe '#execute' do let!(:unit_test_1) { create(:ci_unit_test) } let!(:unit_test_2) { create(:ci_unit_test) } diff --git a/spec/services/ci/deployments/destroy_service_spec.rb b/spec/services/ci/deployments/destroy_service_spec.rb index 60a57c05728..d0e7f5acb2b 100644 --- a/spec/services/ci/deployments/destroy_service_spec.rb +++ b/spec/services/ci/deployments/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Ci::Deployments::DestroyService do +RSpec.describe ::Ci::Deployments::DestroyService, feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let(:environment) { create(:environment, project: project) } diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb index 6bd7fe7559c..a1883d90b0a 100644 --- a/spec/services/ci/destroy_pipeline_service_spec.rb +++ b/spec/services/ci/destroy_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Ci::DestroyPipelineService do +RSpec.describe ::Ci::DestroyPipelineService, feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.id) } diff --git a/spec/services/ci/destroy_secure_file_service_spec.rb b/spec/services/ci/destroy_secure_file_service_spec.rb index 6a30d33f4ca..321efc2ed71 100644 --- a/spec/services/ci/destroy_secure_file_service_spec.rb +++ b/spec/services/ci/destroy_secure_file_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Ci::DestroySecureFileService do +RSpec.describe ::Ci::DestroySecureFileService, feature_category: :continuous_integration do let_it_be(:maintainer_user) { create(:user) } let_it_be(:developer_user) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/services/ci/disable_user_pipeline_schedules_service_spec.rb b/spec/services/ci/disable_user_pipeline_schedules_service_spec.rb index 4ff8dcf075b..d422cf0dab9 100644 --- a/spec/services/ci/disable_user_pipeline_schedules_service_spec.rb +++ b/spec/services/ci/disable_user_pipeline_schedules_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::DisableUserPipelineSchedulesService do +RSpec.describe Ci::DisableUserPipelineSchedulesService, feature_category: :continuous_integration do describe '#execute' do let(:user) { create(:user) } diff --git a/spec/services/ci/drop_pipeline_service_spec.rb b/spec/services/ci/drop_pipeline_service_spec.rb index ddb53712d9c..ed45b3460c1 100644 --- a/spec/services/ci/drop_pipeline_service_spec.rb +++ b/spec/services/ci/drop_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::DropPipelineService do +RSpec.describe Ci::DropPipelineService, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let(:failure_reason) { :user_blocked } diff --git a/spec/services/ci/ensure_stage_service_spec.rb b/spec/services/ci/ensure_stage_service_spec.rb index 026814edda6..5d6025095a1 100644 --- a/spec/services/ci/ensure_stage_service_spec.rb +++ b/spec/services/ci/ensure_stage_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::EnsureStageService, '#execute' do +RSpec.describe Ci::EnsureStageService, '#execute', feature_category: :continuous_integration do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } diff --git a/spec/services/ci/expire_pipeline_cache_service_spec.rb b/spec/services/ci/expire_pipeline_cache_service_spec.rb index 8cfe756faf3..3d0ce456aa5 100644 --- a/spec/services/ci/expire_pipeline_cache_service_spec.rb +++ b/spec/services/ci/expire_pipeline_cache_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::ExpirePipelineCacheService do +RSpec.describe Ci::ExpirePipelineCacheService, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb b/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb index d5881d3b204..1b548aaf614 100644 --- a/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb +++ b/spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::ExternalPullRequests::CreatePipelineService do +RSpec.describe Ci::ExternalPullRequests::CreatePipelineService, feature_category: :continuous_integration do describe '#execute' do let_it_be(:project) { create(:project, :auto_devops, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/services/ci/find_exposed_artifacts_service_spec.rb b/spec/services/ci/find_exposed_artifacts_service_spec.rb index 6e11c153a75..69360e73b86 100644 --- a/spec/services/ci/find_exposed_artifacts_service_spec.rb +++ b/spec/services/ci/find_exposed_artifacts_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::FindExposedArtifactsService do +RSpec.describe Ci::FindExposedArtifactsService, feature_category: :build_artifacts do include Gitlab::Routing let(:metadata) do diff --git a/spec/services/ci/generate_codequality_mr_diff_report_service_spec.rb b/spec/services/ci/generate_codequality_mr_diff_report_service_spec.rb index 63bc7a1caf8..c33b182e9a9 100644 --- a/spec/services/ci/generate_codequality_mr_diff_report_service_spec.rb +++ b/spec/services/ci/generate_codequality_mr_diff_report_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::GenerateCodequalityMrDiffReportService do +RSpec.describe Ci::GenerateCodequalityMrDiffReportService, feature_category: :code_review_workflow do let(:service) { described_class.new(project) } let(:project) { create(:project, :repository) } diff --git a/spec/services/ci/generate_coverage_reports_service_spec.rb b/spec/services/ci/generate_coverage_reports_service_spec.rb index 212e6be9d07..811431bf9d6 100644 --- a/spec/services/ci/generate_coverage_reports_service_spec.rb +++ b/spec/services/ci/generate_coverage_reports_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::GenerateCoverageReportsService do +RSpec.describe Ci::GenerateCoverageReportsService, feature_category: :code_testing do let_it_be(:project) { create(:project, :repository) } let(:service) { described_class.new(project) } diff --git a/spec/services/ci/generate_kubeconfig_service_spec.rb b/spec/services/ci/generate_kubeconfig_service_spec.rb index c0858b0f0c9..a03c6ef0c9d 100644 --- a/spec/services/ci/generate_kubeconfig_service_spec.rb +++ b/spec/services/ci/generate_kubeconfig_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::GenerateKubeconfigService do +RSpec.describe Ci::GenerateKubeconfigService, feature_category: :deployment_management do describe '#execute' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } @@ -13,12 +13,12 @@ RSpec.describe Ci::GenerateKubeconfigService do let_it_be(:project_agent_authorization) do agent = create(:cluster_agent, project: agent_project) - create(:agent_project_authorization, agent: agent, project: project) + create(:agent_ci_access_project_authorization, agent: agent, project: project) end let_it_be(:group_agent_authorization) do agent = create(:cluster_agent, project: agent_project) - create(:agent_group_authorization, agent: agent, group: group) + create(:agent_ci_access_group_authorization, agent: agent, group: group) end let(:template) do @@ -33,7 +33,7 @@ RSpec.describe Ci::GenerateKubeconfigService do let(:agent_authorizations) { [project_agent_authorization, group_agent_authorization] } let(:filter_service) do instance_double( - ::Clusters::Agents::FilterAuthorizationsService, + ::Clusters::Agents::Authorizations::CiAccess::FilterService, execute: agent_authorizations ) end @@ -42,7 +42,7 @@ RSpec.describe Ci::GenerateKubeconfigService do before do allow(Gitlab::Kubernetes::Kubeconfig::Template).to receive(:new).and_return(template) - allow(::Clusters::Agents::FilterAuthorizationsService).to receive(:new).and_return(filter_service) + allow(::Clusters::Agents::Authorizations::CiAccess::FilterService).to receive(:new).and_return(filter_service) end it 'returns a Kubeconfig Template' do @@ -59,7 +59,7 @@ RSpec.describe Ci::GenerateKubeconfigService do end it "filters the pipeline's agents by `nil` environment" do - expect(::Clusters::Agents::FilterAuthorizationsService).to receive(:new).with( + expect(::Clusters::Agents::Authorizations::CiAccess::FilterService).to receive(:new).with( pipeline.cluster_agent_authorizations, environment: nil ) @@ -89,7 +89,7 @@ RSpec.describe Ci::GenerateKubeconfigService do subject(:execute) { described_class.new(pipeline, token: build.token, environment: 'production').execute } it "filters the pipeline's agents by the specified environment" do - expect(::Clusters::Agents::FilterAuthorizationsService).to receive(:new).with( + expect(::Clusters::Agents::Authorizations::CiAccess::FilterService).to receive(:new).with( pipeline.cluster_agent_authorizations, environment: 'production' ) diff --git a/spec/services/ci/generate_terraform_reports_service_spec.rb b/spec/services/ci/generate_terraform_reports_service_spec.rb index c32e8bcaeb8..b2142d391b8 100644 --- a/spec/services/ci/generate_terraform_reports_service_spec.rb +++ b/spec/services/ci/generate_terraform_reports_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::GenerateTerraformReportsService do +RSpec.describe Ci::GenerateTerraformReportsService, feature_category: :infrastructure_as_code do let_it_be(:project) { create(:project, :repository) } describe '#execute' do diff --git a/spec/services/ci/job_artifacts/bulk_delete_by_project_service_spec.rb b/spec/services/ci/job_artifacts/bulk_delete_by_project_service_spec.rb new file mode 100644 index 00000000000..a180837f9a9 --- /dev/null +++ b/spec/services/ci/job_artifacts/bulk_delete_by_project_service_spec.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Ci::JobArtifacts::BulkDeleteByProjectService, "#execute", feature_category: :build_artifacts do + subject(:execute) do + described_class.new( + job_artifact_ids: job_artifact_ids, + current_user: current_user, + project: project).execute + end + + let_it_be(:current_user) { create(:user) } + let_it_be(:build, reload: true) do + create(:ci_build, :artifacts, :trace_artifact, user: current_user) + end + + let_it_be(:project) { build.project } + let_it_be(:job_artifact_ids) { build.job_artifacts.map(&:id) } + + describe '#execute' do + context 'when number of artifacts exceeds limits to delete' do + let_it_be(:second_build, reload: true) do + create(:ci_build, :artifacts, :trace_artifact, user: current_user, project: project) + end + + let_it_be(:job_artifact_ids) { ::Ci::JobArtifact.all.map(&:id) } + + before do + project.add_maintainer(current_user) + stub_const("#{described_class}::JOB_ARTIFACTS_COUNT_LIMIT", 1) + end + + it 'fails to destroy' do + result = execute + + expect(result).to be_error + expect(result[:message]).to eq('Can only delete up to 1 job artifacts per call') + end + end + + context 'when requested not existing artifacts do delete' do + let_it_be(:deleted_build, reload: true) do + create(:ci_build, :artifacts, :trace_artifact, user: current_user, project: project) + end + + let_it_be(:deleted_job_artifacts) { deleted_build.job_artifacts } + let_it_be(:job_artifact_ids) { ::Ci::JobArtifact.all.map(&:id) } + + before do + project.add_maintainer(current_user) + deleted_job_artifacts.each(&:destroy!) + end + + it 'fails to destroy' do + result = execute + + expect(result).to be_error + expect(result[:message]).to eq("Artifacts (#{deleted_job_artifacts.map(&:id).join(',')}) not found") + end + end + + context 'when maintainer has access to the project' do + before do + project.add_maintainer(current_user) + end + + it 'is successful' do + result = execute + + expect(result).to be_success + expect(result.payload).to eq( + { + destroyed_count: job_artifact_ids.count, + destroyed_ids: job_artifact_ids, + errors: [] + } + ) + expect(::Ci::JobArtifact.where(id: job_artifact_ids).count).to eq(0) + end + + context 'and partially owns artifacts' do + let_it_be(:orphan_artifact) { create(:ci_job_artifact, :archive) } + let_it_be(:orphan_artifact_id) { orphan_artifact.id } + let_it_be(:owned_artifacts_ids) { build.job_artifacts.erasable.map(&:id) } + let_it_be(:job_artifact_ids) { [orphan_artifact_id] + owned_artifacts_ids } + + it 'fails to destroy' do + result = execute + + expect(result).to be_error + expect(result[:message]).to be('Not all artifacts belong to requested project') + expect(::Ci::JobArtifact.where(id: job_artifact_ids).count).to eq(3) + end + end + + context 'and request all artifacts from a different project' do + let_it_be(:different_project_artifact) { create(:ci_job_artifact, :archive) } + let_it_be(:job_artifact_ids) { [different_project_artifact] } + + let_it_be(:different_build, reload: true) do + create(:ci_build, :artifacts, :trace_artifact, user: current_user) + end + + let_it_be(:different_project) { different_build.project } + + before do + different_project.add_maintainer(current_user) + end + + it 'returns a error' do + result = execute + + expect(result).to be_error + expect(result[:message]).to be('Not all artifacts belong to requested project') + expect(::Ci::JobArtifact.where(id: job_artifact_ids).count).to eq(job_artifact_ids.count) + end + end + end + end +end diff --git a/spec/services/ci/job_artifacts/create_service_spec.rb b/spec/services/ci/job_artifacts/create_service_spec.rb index 47e9e5994ef..f71d7feb04a 100644 --- a/spec/services/ci/job_artifacts/create_service_spec.rb +++ b/spec/services/ci/job_artifacts/create_service_spec.rb @@ -2,108 +2,215 @@ require 'spec_helper' -RSpec.describe Ci::JobArtifacts::CreateService do +RSpec.describe Ci::JobArtifacts::CreateService, :clean_gitlab_redis_shared_state, feature_category: :build_artifacts do + include WorkhorseHelpers + include Gitlab::Utils::Gzip + let_it_be(:project) { create(:project) } let(:service) { described_class.new(job) } let(:job) { create(:ci_build, project: project) } - let(:artifacts_sha256) { '0' * 64 } - let(:metadata_file) { nil } - let(:artifacts_file) do - file_to_upload('spec/fixtures/ci_build_artifacts.zip', sha256: artifacts_sha256) - end + describe '#authorize', :aggregate_failures do + let(:artifact_type) { 'archive' } + let(:filesize) { nil } - let(:params) do - { - 'artifact_type' => 'archive', - 'artifact_format' => 'zip' - }.with_indifferent_access - end + subject(:authorize) { service.authorize(artifact_type: artifact_type, filesize: filesize) } - def file_to_upload(path, params = {}) - upload = Tempfile.new('upload') - FileUtils.copy(path, upload.path) - # This is a workaround for https://github.com/docker/for-linux/issues/1015 - FileUtils.touch(upload.path) + shared_examples_for 'handling lsif artifact' do + context 'when artifact is lsif' do + let(:artifact_type) { 'lsif' } - UploadedFile.new(upload.path, **params) - end + it 'includes ProcessLsif in the headers' do + expect(authorize[:headers][:ProcessLsif]).to eq(true) + end + end + end - describe '#execute' do - subject { service.execute(artifacts_file, params, metadata_file: metadata_file) } + shared_examples_for 'validating requirements' do + context 'when filesize is specified' do + let(:max_artifact_size) { 10 } - context 'when artifacts file is uploaded' do - it 'logs the created artifact' do - expect(Gitlab::Ci::Artifacts::Logger) - .to receive(:log_created) - .with(an_instance_of(Ci::JobArtifact)) + before do + allow(Ci::JobArtifact) + .to receive(:max_artifact_size) + .with(type: artifact_type, project: project) + .and_return(max_artifact_size) + end - subject - end + context 'and filesize exceeds the limit' do + let(:filesize) { max_artifact_size + 1 } - it 'returns artifact in the response' do - response = subject - new_artifact = job.job_artifacts.last + it 'returns error' do + expect(authorize[:status]).to eq(:error) + end + end - expect(response[:artifact]).to eq(new_artifact) - end + context 'and filesize does not exceed the limit' do + let(:filesize) { max_artifact_size - 1 } - it 'saves artifact for the given type' do - expect { subject }.to change { Ci::JobArtifact.count }.by(1) + it 'returns success' do + expect(authorize[:status]).to eq(:success) + end + end + end + end - new_artifact = job.job_artifacts.last - expect(new_artifact.project).to eq(job.project) - expect(new_artifact.file).to be_present - expect(new_artifact.file_type).to eq(params['artifact_type']) - expect(new_artifact.file_format).to eq(params['artifact_format']) - expect(new_artifact.file_sha256).to eq(artifacts_sha256) - expect(new_artifact.locked).to eq(job.pipeline.locked) + shared_examples_for 'uploading to temp location' do |store_type| + # We are not testing the entire headers here because this is fully tested + # in workhorse_authorize's spec. We just want to confirm that it indeed used the temp path + # by checking some indicators in the headers returned. + if store_type == :object_storage + it 'includes the authorize headers' do + expect(authorize[:status]).to eq(:success) + expect(authorize[:headers][:RemoteObject][:StoreURL]).to include(ObjectStorage::TMP_UPLOAD_PATH) + end + else + it 'includes the authorize headers' do + expect(authorize[:status]).to eq(:success) + expect(authorize[:headers][:TempPath]).to include(ObjectStorage::TMP_UPLOAD_PATH) + end end - it 'sets accessibility level by default to public' do - expect { subject }.to change { Ci::JobArtifact.count }.by(1) + it_behaves_like 'handling lsif artifact' + it_behaves_like 'validating requirements' + end - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_public_accessibility - end + context 'when object storage is enabled' do + context 'and direct upload is enabled' do + let(:final_store_path) { '12/34/abc-123' } - context 'when accessibility level passed as private' do before do - params.merge!('accessibility' => 'private') + stub_artifacts_object_storage(JobArtifactUploader, direct_upload: true) + allow(JobArtifactUploader).to receive(:generate_final_store_path).and_return(final_store_path) end - it 'sets accessibility level to private' do - expect { subject }.to change { Ci::JobArtifact.count }.by(1) + it 'includes the authorize headers' do + expect(authorize[:status]).to eq(:success) - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_private_accessibility + expect(authorize[:headers][:RemoteObject][:ID]).to eq(final_store_path) + + # We are not testing the entire headers here because this is fully tested + # in workhorse_authorize's spec. We just want to confirm that it indeed used the final path + # by checking some indicators in the headers returned. + expect(authorize[:headers][:RemoteObject][:StoreURL]) + .to include(final_store_path) + + # We have to ensure to tell Workhorse to skip deleting the file after upload + # because we are uploading the file to its final location + expect(authorize[:headers][:RemoteObject][:SkipDelete]).to eq(true) + end + + it_behaves_like 'handling lsif artifact' + it_behaves_like 'validating requirements' + + context 'with ci_artifacts_upload_to_final_location feature flag disabled' do + before do + stub_feature_flags(ci_artifacts_upload_to_final_location: false) + end + + it_behaves_like 'uploading to temp location', :object_storage end end - context 'when accessibility passed as public' do + context 'and direct upload is disabled' do before do - params.merge!('accessibility' => 'public') + stub_artifacts_object_storage(JobArtifactUploader, direct_upload: false) end + it_behaves_like 'uploading to temp location', :local_storage + end + end + + context 'when object storage is disabled' do + it_behaves_like 'uploading to temp location', :local_storage + end + end + + describe '#execute' do + let(:artifacts_sha256) { '0' * 64 } + let(:metadata_file) { nil } + + let(:params) do + { + 'artifact_type' => 'archive', + 'artifact_format' => 'zip' + }.with_indifferent_access + end + + subject(:execute) { service.execute(artifacts_file, params, metadata_file: metadata_file) } + + shared_examples_for 'handling accessibility' do + shared_examples 'public accessibility' do it 'sets accessibility to public level' do - expect { subject }.to change { Ci::JobArtifact.count }.by(1) + expect(job.job_artifacts).to all be_public_accessibility + end + end - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_public_accessibility + shared_examples 'private accessibility' do + it 'sets accessibility to private level' do + expect(job.job_artifacts).to all be_private_accessibility + end + end + + context 'when non_public_artifacts flag is disabled' do + before do + stub_feature_flags(non_public_artifacts: false) + end + + it_behaves_like 'public accessibility' + end + + context 'when non_public_artifacts flag is enabled' do + context 'and accessibility is defined in the params' do + context 'and is passed as private' do + before do + params.merge!('accessibility' => 'private') + end + + it_behaves_like 'private accessibility' + end + + context 'and is passed as public' do + before do + params.merge!('accessibility' => 'public') + end + + it_behaves_like 'public accessibility' + end + end + + context 'and accessibility is not defined in the params' do + context 'and job has no public artifacts defined in its CI config' do + it_behaves_like 'public accessibility' + end + + context 'and job artifacts defined as private in the CI config' do + let(:job) { create(:ci_build, :with_private_artifacts_config, project: project) } + + it_behaves_like 'private accessibility' + end + + context 'and job artifacts defined as public in the CI config' do + let(:job) { create(:ci_build, :with_public_artifacts_config, project: project) } + + it_behaves_like 'public accessibility' + end end end context 'when accessibility passed as invalid value' do before do - params.merge!('accessibility' => 'invalid_value') + params.merge!('accessibility' => 'foo') end it 'fails with argument error' do - expect { subject }.to raise_error(ArgumentError) + expect { execute }.to raise_error(ArgumentError, "'foo' is not a valid accessibility") end end + end + shared_examples_for 'handling metadata file' do context 'when metadata file is also uploaded' do let(:metadata_file) do file_to_upload('spec/fixtures/ci_build_artifacts_metadata.gz', sha256: artifacts_sha256) @@ -113,8 +220,8 @@ RSpec.describe Ci::JobArtifacts::CreateService do stub_application_setting(default_artifacts_expire_in: '1 day') end - it 'saves metadata artifact' do - expect { subject }.to change { Ci::JobArtifact.count }.by(2) + it 'creates a new metadata job artifact' do + expect { execute }.to change { Ci::JobArtifact.where(file_type: :metadata).count }.by(1) new_artifact = job.job_artifacts.last expect(new_artifact.project).to eq(job.project) @@ -125,13 +232,6 @@ RSpec.describe Ci::JobArtifacts::CreateService do expect(new_artifact.locked).to eq(job.pipeline.locked) end - it 'sets accessibility by default to public' do - expect { subject }.to change { Ci::JobArtifact.count }.by(2) - - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_public_accessibility - end - it 'logs the created artifact and metadata' do expect(Gitlab::Ci::Artifacts::Logger) .to receive(:log_created) @@ -140,36 +240,12 @@ RSpec.describe Ci::JobArtifacts::CreateService do subject end - context 'when accessibility level passed as private' do - before do - params.merge!('accessibility' => 'private') - end - - it 'sets accessibility to private level' do - expect { subject }.to change { Ci::JobArtifact.count }.by(2) - - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_private_accessibility - end - end - - context 'when accessibility passed as public' do - before do - params.merge!('accessibility' => 'public') - end - - it 'sets accessibility level to public' do - expect { subject }.to change { Ci::JobArtifact.count }.by(2) - - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_public_accessibility - end - end + it_behaves_like 'handling accessibility' it 'sets expiration date according to application settings' do expected_expire_at = 1.day.from_now - expect(subject).to match(a_hash_including(status: :success, artifact: anything)) + expect(execute).to match(a_hash_including(status: :success, artifact: anything)) archive_artifact, metadata_artifact = job.job_artifacts.last(2) expect(job.artifacts_expire_at).to be_within(1.minute).of(expected_expire_at) @@ -185,7 +261,7 @@ RSpec.describe Ci::JobArtifacts::CreateService do it 'sets expiration date according to the parameter' do expected_expire_at = 2.hours.from_now - expect(subject).to match(a_hash_including(status: :success, artifact: anything)) + expect(execute).to match(a_hash_including(status: :success, artifact: anything)) archive_artifact, metadata_artifact = job.job_artifacts.last(2) expect(job.artifacts_expire_at).to be_within(1.minute).of(expected_expire_at) @@ -202,7 +278,7 @@ RSpec.describe Ci::JobArtifacts::CreateService do it 'sets expiration date according to the parameter' do expected_expire_at = nil - expect(subject).to be_truthy + expect(execute).to be_truthy archive_artifact, metadata_artifact = job.job_artifacts.last(2) expect(job.artifacts_expire_at).to eq(expected_expire_at) @@ -213,96 +289,237 @@ RSpec.describe Ci::JobArtifacts::CreateService do end end - context 'when artifacts file already exists' do - let!(:existing_artifact) do - create(:ci_job_artifact, :archive, file_sha256: existing_sha256, job: job) - end + shared_examples_for 'handling dotenv' do |storage_type| + context 'when artifact type is dotenv' do + let(:params) do + { + 'artifact_type' => 'dotenv', + 'artifact_format' => 'gzip' + }.with_indifferent_access + end - context 'when sha256 of uploading artifact is the same of the existing one' do - let(:existing_sha256) { artifacts_sha256 } + if storage_type == :object_storage + let(:object_body) { File.read('spec/fixtures/build.env.gz') } + let(:upload_filename) { 'build.env.gz' } - it 'ignores the changes' do - expect { subject }.not_to change { Ci::JobArtifact.count } - expect(subject).to match(a_hash_including(status: :success)) + before do + stub_request(:get, %r{s3.amazonaws.com/#{remote_path}}) + .to_return(status: 200, body: File.read('spec/fixtures/build.env.gz')) + end + else + let(:artifacts_file) do + file_to_upload('spec/fixtures/build.env.gz', sha256: artifacts_sha256) + end + end + + it 'calls parse service' do + expect_any_instance_of(Ci::ParseDotenvArtifactService) do |service| + expect(service).to receive(:execute).once.and_call_original + end + + expect(execute[:status]).to eq(:success) + expect(job.job_variables.as_json(only: [:key, :value, :source])).to contain_exactly( + hash_including('key' => 'KEY1', 'value' => 'VAR1', 'source' => 'dotenv'), + hash_including('key' => 'KEY2', 'value' => 'VAR2', 'source' => 'dotenv')) end end + end - context 'when sha256 of uploading artifact is different than the existing one' do - let(:existing_sha256) { '1' * 64 } + shared_examples_for 'handling object storage errors' do + shared_examples 'rescues object storage error' do |klass, message, expected_message| + it "handles #{klass}" do + allow_next_instance_of(JobArtifactUploader) do |uploader| + allow(uploader).to receive(:store!).and_raise(klass, message) + end - it 'returns error status' do - expect(Gitlab::ErrorTracking).to receive(:track_exception).and_call_original + expect(Gitlab::ErrorTracking) + .to receive(:track_exception) + .and_call_original - expect { subject }.not_to change { Ci::JobArtifact.count } - expect(subject).to match( + expect(execute).to match( a_hash_including( - http_status: :bad_request, message: 'another artifact of the same type already exists', status: :error)) + http_status: :service_unavailable, + message: expected_message || message, + status: :error)) end end + + it_behaves_like 'rescues object storage error', + Errno::EIO, 'some/path', 'Input/output error - some/path' + + it_behaves_like 'rescues object storage error', + Google::Apis::ServerError, 'Server error' + + it_behaves_like 'rescues object storage error', + Signet::RemoteServerError, 'The service is currently unavailable' end - context 'when artifact type is dotenv' do - let(:artifacts_file) do - file_to_upload('spec/fixtures/build.env.gz', sha256: artifacts_sha256) - end + shared_examples_for 'validating requirements' do + context 'when filesize is specified' do + let(:max_artifact_size) { 10 } + + before do + allow(Ci::JobArtifact) + .to receive(:max_artifact_size) + .with(type: 'archive', project: project) + .and_return(max_artifact_size) - let(:params) do - { - 'artifact_type' => 'dotenv', - 'artifact_format' => 'gzip' - }.with_indifferent_access + allow(artifacts_file).to receive(:size).and_return(filesize) + end + + context 'and filesize exceeds the limit' do + let(:filesize) { max_artifact_size + 1 } + + it 'returns error' do + expect(execute[:status]).to eq(:error) + end + end + + context 'and filesize does not exceed the limit' do + let(:filesize) { max_artifact_size - 1 } + + it 'returns success' do + expect(execute[:status]).to eq(:success) + end + end end + end - it 'calls parse service' do - expect_any_instance_of(Ci::ParseDotenvArtifactService) do |service| - expect(service).to receive(:execute).once.and_call_original + shared_examples_for 'handling existing artifact' do + context 'when job already has an artifact of the same file type' do + let!(:existing_artifact) do + create(:ci_job_artifact, params[:artifact_type], file_sha256: existing_sha256, job: job) end - expect(subject[:status]).to eq(:success) - expect(job.job_variables.as_json(only: [:key, :value, :source])).to contain_exactly( - hash_including('key' => 'KEY1', 'value' => 'VAR1', 'source' => 'dotenv'), - hash_including('key' => 'KEY2', 'value' => 'VAR2', 'source' => 'dotenv')) + context 'when sha256 of uploading artifact is the same of the existing one' do + let(:existing_sha256) { artifacts_sha256 } + + it 'ignores the changes' do + expect { execute }.not_to change { Ci::JobArtifact.count } + expect(execute).to match(a_hash_including(status: :success)) + end + end + + context 'when sha256 of uploading artifact is different than the existing one' do + let(:existing_sha256) { '1' * 64 } + + it 'returns error status' do + expect(Gitlab::ErrorTracking).to receive(:track_exception).and_call_original + + expect { execute }.not_to change { Ci::JobArtifact.count } + expect(execute).to match( + a_hash_including( + http_status: :bad_request, + message: 'another artifact of the same type already exists', + status: :error + ) + ) + end + end + end + end + + shared_examples_for 'logging artifact' do + it 'logs the created artifact' do + expect(Gitlab::Ci::Artifacts::Logger) + .to receive(:log_created) + .with(an_instance_of(Ci::JobArtifact)) + + execute end end - context 'with job partitioning', :ci_partitionable do - let(:pipeline) { create(:ci_pipeline, project: project, partition_id: ci_testing_partition_id) } - let(:job) { create(:ci_build, pipeline: pipeline) } + shared_examples_for 'handling uploads' do + context 'when artifacts file is uploaded' do + it 'creates a new job artifact' do + expect { execute }.to change { Ci::JobArtifact.count }.by(1) - it 'sets partition_id on artifacts' do - expect { subject }.to change { Ci::JobArtifact.count } + new_artifact = execute[:artifact] + expect(new_artifact).to eq(job.job_artifacts.last) + expect(new_artifact.project).to eq(job.project) + expect(new_artifact.file.filename).to eq(artifacts_file.original_filename) + expect(new_artifact.file_identifier).to eq(artifacts_file.original_filename) + expect(new_artifact.file_type).to eq(params['artifact_type']) + expect(new_artifact.file_format).to eq(params['artifact_format']) + expect(new_artifact.file_sha256).to eq(artifacts_sha256) + expect(new_artifact.locked).to eq(job.pipeline.locked) + expect(new_artifact.size).to eq(artifacts_file.size) - artifacts_partitions = job.job_artifacts.map(&:partition_id).uniq + expect(execute[:status]).to eq(:success) + end - expect(artifacts_partitions).to eq([ci_testing_partition_id]) + it_behaves_like 'handling accessibility' + it_behaves_like 'handling metadata file' + it_behaves_like 'handling partitioning' + it_behaves_like 'logging artifact' end end - shared_examples 'rescues object storage error' do |klass, message, expected_message| - it "handles #{klass}" do - allow_next_instance_of(JobArtifactUploader) do |uploader| - allow(uploader).to receive(:store!).and_raise(klass, message) + shared_examples_for 'handling partitioning' do + context 'with job partitioned', :ci_partitionable do + let(:pipeline) { create(:ci_pipeline, project: project, partition_id: ci_testing_partition_id) } + let(:job) { create(:ci_build, pipeline: pipeline) } + + it 'sets partition_id on artifacts' do + expect { execute }.to change { Ci::JobArtifact.count } + + artifacts_partitions = job.job_artifacts.map(&:partition_id).uniq + + expect(artifacts_partitions).to eq([ci_testing_partition_id]) end + end + end - expect(Gitlab::ErrorTracking) - .to receive(:track_exception) - .and_call_original + context 'when object storage and direct upload is enabled' do + let(:fog_connection) { stub_artifacts_object_storage(JobArtifactUploader, direct_upload: true) } + let(:remote_path) { File.join(remote_store_path, remote_id) } + let(:object_body) { File.open('spec/fixtures/ci_build_artifacts.zip') } + let(:upload_filename) { 'artifacts.zip' } + let(:object) do + fog_connection.directories + .new(key: 'artifacts') + .files + .create( # rubocop:disable Rails/SaveBang + key: remote_path, + body: object_body + ) + end - expect(subject).to match( - a_hash_including( - http_status: :service_unavailable, - message: expected_message || message, - status: :error)) + let(:artifacts_file) do + fog_to_uploaded_file( + object, + filename: upload_filename, + sha256: artifacts_sha256, + remote_id: remote_id + ) end + + let(:remote_id) { 'generated-remote-id-12345' } + let(:remote_store_path) { ObjectStorage::TMP_UPLOAD_PATH } + + it_behaves_like 'handling uploads' + it_behaves_like 'handling dotenv', :object_storage + it_behaves_like 'handling object storage errors' + it_behaves_like 'validating requirements' end - it_behaves_like 'rescues object storage error', - Errno::EIO, 'some/path', 'Input/output error - some/path' + context 'when using local storage' do + let(:artifacts_file) do + file_to_upload('spec/fixtures/ci_build_artifacts.zip', sha256: artifacts_sha256) + end + + it_behaves_like 'handling uploads' + it_behaves_like 'handling dotenv', :local_storage + it_behaves_like 'validating requirements' + end + end - it_behaves_like 'rescues object storage error', - Google::Apis::ServerError, 'Server error' + def file_to_upload(path, params = {}) + upload = Tempfile.new('upload') + FileUtils.copy(path, upload.path) + # This is a workaround for https://github.com/docker/for-linux/issues/1015 + FileUtils.touch(upload.path) - it_behaves_like 'rescues object storage error', - Signet::RemoteServerError, 'The service is currently unavailable' + UploadedFile.new(upload.path, **params) end end diff --git a/spec/services/ci/job_artifacts/delete_project_artifacts_service_spec.rb b/spec/services/ci/job_artifacts/delete_project_artifacts_service_spec.rb index 74fa42962f3..9c711e54b00 100644 --- a/spec/services/ci/job_artifacts/delete_project_artifacts_service_spec.rb +++ b/spec/services/ci/job_artifacts/delete_project_artifacts_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::JobArtifacts::DeleteProjectArtifactsService do +RSpec.describe Ci::JobArtifacts::DeleteProjectArtifactsService, feature_category: :build_artifacts do let_it_be(:project) { create(:project) } subject { described_class.new(project: project) } diff --git a/spec/services/ci/job_artifacts/delete_service_spec.rb b/spec/services/ci/job_artifacts/delete_service_spec.rb index 78e8be48255..1560d0fc6f4 100644 --- a/spec/services/ci/job_artifacts/delete_service_spec.rb +++ b/spec/services/ci/job_artifacts/delete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::JobArtifacts::DeleteService do +RSpec.describe Ci::JobArtifacts::DeleteService, feature_category: :build_artifacts do let_it_be(:build, reload: true) do create(:ci_build, :artifacts, :trace_artifact, artifacts_expire_at: 100.days.from_now) end diff --git a/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb b/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb index 457be67c1ea..cdbb0c0f8ce 100644 --- a/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb +++ b/spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::JobArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_shared_state, -feature_category: :build_artifacts do + feature_category: :build_artifacts do include ExclusiveLeaseHelpers let(:service) { described_class.new } @@ -39,32 +39,12 @@ feature_category: :build_artifacts do second_artifact end - context 'with ci_destroy_unlocked_job_artifacts feature flag disabled' do - before do - stub_feature_flags(ci_destroy_unlocked_job_artifacts: false) - end - - it 'performs a consistent number of queries' do - control = ActiveRecord::QueryRecorder.new { service.execute } - - more_artifacts - - expect { subject }.not_to exceed_query_limit(control.count) - end - end - - context 'with ci_destroy_unlocked_job_artifacts feature flag enabled' do - before do - stub_feature_flags(ci_destroy_unlocked_job_artifacts: true) - end - - it 'performs a consistent number of queries' do - control = ActiveRecord::QueryRecorder.new { service.execute } + it 'performs a consistent number of queries' do + control = ActiveRecord::QueryRecorder.new { service.execute } - more_artifacts + more_artifacts - expect { subject }.not_to exceed_query_limit(control.count) - end + expect { subject }.not_to exceed_query_limit(control.count) end end @@ -251,6 +231,16 @@ feature_category: :build_artifacts do end end + context 'when some artifacts are trace' do + let!(:artifact) { create(:ci_job_artifact, :expired, job: job, locked: job.pipeline.locked) } + let!(:trace_artifact) { create(:ci_job_artifact, :trace, :expired, job: job, locked: job.pipeline.locked) } + + it 'destroys only non trace artifacts' do + expect { subject }.to change { Ci::JobArtifact.count }.by(-1) + expect(trace_artifact).to be_persisted + end + end + context 'when all artifacts are locked' do let!(:artifact) { create(:ci_job_artifact, :expired, job: locked_job, locked: locked_job.pipeline.locked) } diff --git a/spec/services/ci/job_artifacts/destroy_associations_service_spec.rb b/spec/services/ci/job_artifacts/destroy_associations_service_spec.rb index ca36c923dcf..f4839ccb04b 100644 --- a/spec/services/ci/job_artifacts/destroy_associations_service_spec.rb +++ b/spec/services/ci/job_artifacts/destroy_associations_service_spec.rb @@ -2,24 +2,37 @@ require 'spec_helper' -RSpec.describe Ci::JobArtifacts::DestroyAssociationsService do +RSpec.describe Ci::JobArtifacts::DestroyAssociationsService, feature_category: :build_artifacts do let_it_be(:project_1) { create(:project) } let_it_be(:project_2) { create(:project) } let_it_be(:artifact_1, refind: true) { create(:ci_job_artifact, :zip, project: project_1) } - let_it_be(:artifact_2, refind: true) { create(:ci_job_artifact, :zip, project: project_2) } - let_it_be(:artifact_3, refind: true) { create(:ci_job_artifact, :zip, project: project_1) } + let_it_be(:artifact_2, refind: true) { create(:ci_job_artifact, :junit, project: project_2) } + let_it_be(:artifact_3, refind: true) { create(:ci_job_artifact, :terraform, project: project_1) } + let_it_be(:artifact_4, refind: true) { create(:ci_job_artifact, :trace, project: project_2) } + let_it_be(:artifact_5, refind: true) { create(:ci_job_artifact, :metadata, project: project_2) } - let(:artifacts) { Ci::JobArtifact.where(id: [artifact_1.id, artifact_2.id, artifact_3.id]) } + let_it_be(:locked_artifact, refind: true) { create(:ci_job_artifact, :zip, :locked, project: project_1) } + + let(:artifact_ids_to_be_removed) { [artifact_1.id, artifact_2.id, artifact_3.id, artifact_4.id, artifact_5.id] } + let(:artifacts) { Ci::JobArtifact.where(id: artifact_ids_to_be_removed) } let(:service) { described_class.new(artifacts) } describe '#destroy_records' do - it 'removes artifacts without updating statistics' do + it 'removes all types of artifacts without updating statistics' do expect_next_instance_of(Ci::JobArtifacts::DestroyBatchService) do |service| expect(service).to receive(:execute).with(update_stats: false).and_call_original end - expect { service.destroy_records }.to change { Ci::JobArtifact.count }.by(-3) + expect { service.destroy_records }.to change { Ci::JobArtifact.count }.by(-artifact_ids_to_be_removed.count) + end + + context 'with a locked artifact' do + let(:artifact_ids_to_be_removed) { [artifact_1.id, locked_artifact.id] } + + it 'removes all artifacts' do + expect { service.destroy_records }.to change { Ci::JobArtifact.count }.by(-artifact_ids_to_be_removed.count) + end end context 'when there are no artifacts' do @@ -42,7 +55,11 @@ RSpec.describe Ci::JobArtifacts::DestroyAssociationsService do have_attributes(amount: -artifact_1.size, ref: artifact_1.id), have_attributes(amount: -artifact_3.size, ref: artifact_3.id) ] - project2_increments = [have_attributes(amount: -artifact_2.size, ref: artifact_2.id)] + project2_increments = [ + have_attributes(amount: -artifact_2.size, ref: artifact_2.id), + have_attributes(amount: -artifact_4.size, ref: artifact_4.id), + have_attributes(amount: -artifact_5.size, ref: artifact_5.id) + ] expect(ProjectStatistics).to receive(:bulk_increment_statistic).once .with(project_1, :build_artifacts_size, match_array(project1_increments)) diff --git a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb index cde42783d8c..6f9dcf47535 100644 --- a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb +++ b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::JobArtifacts::DestroyBatchService do - let(:artifacts) { Ci::JobArtifact.where(id: [artifact_with_file.id, artifact_without_file.id, trace_artifact.id]) } +RSpec.describe Ci::JobArtifacts::DestroyBatchService, feature_category: :build_artifacts do + let(:artifacts) { Ci::JobArtifact.where(id: [artifact_with_file.id, artifact_without_file.id]) } let(:skip_projects_on_refresh) { false } let(:service) do described_class.new( @@ -25,10 +25,6 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do create(:ci_job_artifact) end - let_it_be(:trace_artifact, refind: true) do - create(:ci_job_artifact, :trace, :expired) - end - describe '#execute' do subject(:execute) { service.execute } @@ -60,11 +56,6 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do execute end - it 'preserves trace artifacts' do - expect { subject } - .to not_change { Ci::JobArtifact.exists?(trace_artifact.id) } - end - context 'when artifact belongs to a project that is undergoing stats refresh' do let!(:artifact_under_refresh_1) do create(:ci_job_artifact, :zip) @@ -287,7 +278,7 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do end it 'reports the number of destroyed artifacts' do - is_expected.to eq(destroyed_artifacts_count: 0, statistics_updates: {}, status: :success) + is_expected.to eq(destroyed_artifacts_count: 0, destroyed_ids: [], statistics_updates: {}, status: :success) end end end diff --git a/spec/services/ci/job_artifacts/expire_project_build_artifacts_service_spec.rb b/spec/services/ci/job_artifacts/expire_project_build_artifacts_service_spec.rb index fb9dd6b876b..69cdf39107a 100644 --- a/spec/services/ci/job_artifacts/expire_project_build_artifacts_service_spec.rb +++ b/spec/services/ci/job_artifacts/expire_project_build_artifacts_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::JobArtifacts::ExpireProjectBuildArtifactsService do +RSpec.describe Ci::JobArtifacts::ExpireProjectBuildArtifactsService, feature_category: :build_artifacts do let_it_be(:project) { create(:project) } let_it_be(:pipeline, reload: true) { create(:ci_pipeline, :unlocked, project: project) } diff --git a/spec/services/ci/job_artifacts/track_artifact_report_service_spec.rb b/spec/services/ci/job_artifacts/track_artifact_report_service_spec.rb index d4d56825e1f..af0c9b0833d 100644 --- a/spec/services/ci/job_artifacts/track_artifact_report_service_spec.rb +++ b/spec/services/ci/job_artifacts/track_artifact_report_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::JobArtifacts::TrackArtifactReportService do +RSpec.describe Ci::JobArtifacts::TrackArtifactReportService, feature_category: :build_artifacts do describe '#execute', :clean_gitlab_redis_shared_state do let_it_be(:group) { create(:group, :private) } let_it_be(:project) { create(:project, group: group) } @@ -33,13 +33,9 @@ RSpec.describe Ci::JobArtifacts::TrackArtifactReportService do .with(test_event_name_1, values: user1.id) .and_call_original - expect { track_artifact_report } - .to change { - counter.unique_events(event_names: test_event_name_1, - start_date: start_time, - end_date: end_time) - } - .by 1 + expect { track_artifact_report }.to change { + counter.unique_events(event_names: test_event_name_1, start_date: start_time, end_date: end_time) + }.by 1 end end @@ -81,13 +77,9 @@ RSpec.describe Ci::JobArtifacts::TrackArtifactReportService do expect do described_class.new.execute(pipeline1) described_class.new.execute(pipeline2) - end - .to change { - counter.unique_events(event_names: test_event_name_1, - start_date: start_time, - end_date: end_time) - } - .by 1 + end.to change { + counter.unique_events(event_names: test_event_name_1, start_date: start_time, end_date: end_time) + }.by 1 end end @@ -109,13 +101,9 @@ RSpec.describe Ci::JobArtifacts::TrackArtifactReportService do expect do described_class.new.execute(pipeline1) described_class.new.execute(pipeline2) - end - .to change { - counter.unique_events(event_names: test_event_name_1, - start_date: start_time, - end_date: end_time) - } - .by 2 + end.to change { + counter.unique_events(event_names: test_event_name_1, start_date: start_time, end_date: end_time) + }.by 2 end end @@ -134,13 +122,9 @@ RSpec.describe Ci::JobArtifacts::TrackArtifactReportService do .with(test_event_name_2, values: user1.id) .and_call_original - expect { track_artifact_report } - .to change { - counter.unique_events(event_names: test_event_name_2, - start_date: start_time, - end_date: end_time) - } - .by 1 + expect { track_artifact_report }.to change { + counter.unique_events(event_names: test_event_name_2, start_date: start_time, end_date: end_time) + }.by 1 end end @@ -158,13 +142,9 @@ RSpec.describe Ci::JobArtifacts::TrackArtifactReportService do expect do described_class.new.execute(pipeline1) described_class.new.execute(pipeline2) - end - .to change { - counter.unique_events(event_names: test_event_name_2, - start_date: start_time, - end_date: end_time) - } - .by 1 + end.to change { + counter.unique_events(event_names: test_event_name_2, start_date: start_time, end_date: end_time) + }.by 1 end end @@ -186,13 +166,9 @@ RSpec.describe Ci::JobArtifacts::TrackArtifactReportService do expect do described_class.new.execute(pipeline1) described_class.new.execute(pipeline2) - end - .to change { - counter.unique_events(event_names: test_event_name_2, - start_date: start_time, - end_date: end_time) - } - .by 2 + end.to change { + counter.unique_events(event_names: test_event_name_2, start_date: start_time, end_date: end_time) + }.by 2 end end end diff --git a/spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb b/spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb index 67412e41fb8..5f6a89b89e1 100644 --- a/spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb +++ b/spec/services/ci/job_artifacts/update_unknown_locked_status_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::JobArtifacts::UpdateUnknownLockedStatusService, :clean_gitlab_redis_shared_state do +RSpec.describe Ci::JobArtifacts::UpdateUnknownLockedStatusService, :clean_gitlab_redis_shared_state, + feature_category: :build_artifacts do include ExclusiveLeaseHelpers let(:service) { described_class.new } diff --git a/spec/services/ci/job_token_scope/add_project_service_spec.rb b/spec/services/ci/job_token_scope/add_project_service_spec.rb index e6674ee384f..dc7ad81afef 100644 --- a/spec/services/ci/job_token_scope/add_project_service_spec.rb +++ b/spec/services/ci/job_token_scope/add_project_service_spec.rb @@ -37,8 +37,8 @@ RSpec.describe Ci::JobTokenScope::AddProjectService, feature_category: :continuo it_behaves_like 'adds project' - it 'creates an outbound link by default' do - expect(resulting_direction).to eq('outbound') + it 'creates an inbound link by default' do + expect(resulting_direction).to eq('inbound') end context 'when direction is specified' do diff --git a/spec/services/ci/list_config_variables_service_spec.rb b/spec/services/ci/list_config_variables_service_spec.rb index e2bbdefef7f..07c9085b83a 100644 --- a/spec/services/ci/list_config_variables_service_spec.rb +++ b/spec/services/ci/list_config_variables_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Ci::ListConfigVariablesService, -:use_clean_rails_memory_store_caching, feature_category: :pipeline_authoring do + :use_clean_rails_memory_store_caching, feature_category: :secrets_management do include ReactiveCachingHelpers let(:ci_config) { {} } diff --git a/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb b/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb index c4558bddc85..b7b32d2a0af 100644 --- a/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb +++ b/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PipelineArtifacts::CoverageReportService do +RSpec.describe Ci::PipelineArtifacts::CoverageReportService, feature_category: :build_artifacts do describe '#execute' do let_it_be(:project) { create(:project, :repository) } diff --git a/spec/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service_spec.rb b/spec/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service_spec.rb index 5d854b61f14..20265a0ca48 100644 --- a/spec/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service_spec.rb +++ b/spec/services/ci/pipeline_artifacts/create_code_quality_mr_diff_report_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Ci::PipelineArtifacts::CreateCodeQualityMrDiffReportService do +RSpec.describe ::Ci::PipelineArtifacts::CreateCodeQualityMrDiffReportService, feature_category: :build_artifacts do describe '#execute' do let(:merge_request) { create(:merge_request) } let(:project) { merge_request.project } diff --git a/spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb b/spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb index 47e8766c215..b46648760e1 100644 --- a/spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb +++ b/spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::PipelineArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_shared_state do +RSpec.describe Ci::PipelineArtifacts::DestroyAllExpiredService, :clean_gitlab_redis_shared_state, + feature_category: :build_artifacts do let(:service) { described_class.new } describe '.execute' do diff --git a/spec/services/ci/pipeline_bridge_status_service_spec.rb b/spec/services/ci/pipeline_bridge_status_service_spec.rb index 1346f68c952..3d8219251d6 100644 --- a/spec/services/ci/pipeline_bridge_status_service_spec.rb +++ b/spec/services/ci/pipeline_bridge_status_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PipelineBridgeStatusService do +RSpec.describe Ci::PipelineBridgeStatusService, feature_category: :continuous_integration do let(:user) { build(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb b/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb index ab4ba20e716..06139c091b9 100644 --- a/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb +++ b/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PipelineCreation::StartPipelineService do +RSpec.describe Ci::PipelineCreation::StartPipelineService, feature_category: :continuous_integration do let(:pipeline) { build(:ci_pipeline) } subject(:service) { described_class.new(pipeline) } diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb index d0aa1ba4c6c..89b3c45485b 100644 --- a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb +++ b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection do +RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection, + feature_category: :continuous_integration do using RSpec::Parameterized::TableSyntax let_it_be(:pipeline) { create(:ci_pipeline) } @@ -31,15 +32,15 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection let(:collection) { described_class.new(pipeline) } - describe '#set_processable_status' do - it 'does update existing status of processable' do - collection.set_processable_status(test_a.id, 'success', 100) + describe '#set_job_status' do + it 'does update existing status of job' do + collection.set_job_status(test_a.id, 'success', 100) - expect(collection.status_for_names(['test-a'], dag: false)).to eq('success') + expect(collection.status_of_jobs(['test-a'])).to eq('success') end - it 'ignores a missing processable' do - collection.set_processable_status(-1, 'failed', 100) + it 'ignores a missing job' do + collection.set_job_status(-1, 'failed', 100) end end @@ -49,24 +50,21 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection end end - describe '#status_for_names' do - where(:names, :status, :dag) do - %w[build-a] | 'success' | false - %w[build-a build-b] | 'failed' | false - %w[build-a test-a] | 'running' | false - %w[build-a] | 'success' | true - %w[build-a build-b] | 'failed' | true - %w[build-a test-a] | 'pending' | true + describe '#status_of_jobs' do + where(:names, :status) do + %w[build-a] | 'success' + %w[build-a build-b] | 'failed' + %w[build-a test-a] | 'running' end with_them do it 'returns composite status of given names' do - expect(collection.status_for_names(names, dag: dag)).to eq(status) + expect(collection.status_of_jobs(names)).to eq(status) end end end - describe '#status_for_prior_stage_position' do + describe '#status_of_jobs_prior_to_stage' do where(:stage, :status) do 0 | 'success' 1 | 'failed' @@ -74,13 +72,13 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection end with_them do - it 'returns composite status for processables in prior stages' do - expect(collection.status_for_prior_stage_position(stage)).to eq(status) + it 'returns composite status for jobs in prior stages' do + expect(collection.status_of_jobs_prior_to_stage(stage)).to eq(status) end end end - describe '#status_for_stage_position' do + describe '#status_of_stage' do where(:stage, :status) do 0 | 'failed' 1 | 'running' @@ -88,23 +86,23 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection end with_them do - it 'returns composite status for processables at a given stages' do - expect(collection.status_for_stage_position(stage)).to eq(status) + it 'returns composite status for jobs at a given stages' do + expect(collection.status_of_stage(stage)).to eq(status) end end end - describe '#created_processable_ids_for_stage_position' do - it 'returns IDs of processables at a given stage position' do - expect(collection.created_processable_ids_for_stage_position(0)).to be_empty - expect(collection.created_processable_ids_for_stage_position(1)).to be_empty - expect(collection.created_processable_ids_for_stage_position(2)).to contain_exactly(deploy.id) + describe '#created_job_ids_in_stage' do + it 'returns IDs of jobs at a given stage position' do + expect(collection.created_job_ids_in_stage(0)).to be_empty + expect(collection.created_job_ids_in_stage(1)).to be_empty + expect(collection.created_job_ids_in_stage(2)).to contain_exactly(deploy.id) end end - describe '#processing_processables' do - it 'returns processables marked as processing' do - expect(collection.processing_processables.map { |processable| processable[:id] }) + describe '#processing_jobs' do + it 'returns jobs marked as processing' do + expect(collection.processing_jobs.map { |job| job[:id] }) .to contain_exactly(build_a.id, build_b.id, test_a.id, test_b.id, deploy.id) 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 c1669e0424a..8c52603e769 100644 --- a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb +++ b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb @@ -59,17 +59,17 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService, feature_category end def event_on_jobs(event, job_names) - statuses = pipeline.latest_statuses.by_name(job_names).to_a - expect(statuses.count).to eq(job_names.count) # ensure that we have the same counts + jobs = pipeline.latest_statuses.by_name(job_names).to_a + expect(jobs.count).to eq(job_names.count) # ensure that we have the same counts - statuses.each do |status| + jobs.each do |job| case event when 'play' - status.play(user) + job.play(user) when 'retry' - ::Ci::RetryJobService.new(project, user).execute(status) + ::Ci::RetryJobService.new(project, user).execute(job) else - status.public_send("#{event}!") + job.public_send("#{event}!") end end end @@ -646,8 +646,7 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService, feature_category # Users need ability to merge into a branch in order to trigger # protected manual actions. # - create(:protected_branch, :developers_can_merge, - name: 'master', project: project) + create(:protected_branch, :developers_can_merge, name: 'master', project: project) end it 'properly processes entire pipeline' do @@ -983,8 +982,8 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService, feature_category bridge1 = all_builds.find_by(name: 'deploy: [ovh, monitoring]') bridge2 = all_builds.find_by(name: 'deploy: [ovh, app]') - downstream_job1 = bridge1.downstream_pipeline.processables.first - downstream_job2 = bridge2.downstream_pipeline.processables.first + downstream_job1 = bridge1.downstream_pipeline.all_jobs.first + downstream_job2 = bridge2.downstream_pipeline.all_jobs.first expect(downstream_job1.scoped_variables.to_hash).to include('PROVIDER' => 'ovh', 'STACK' => 'monitoring') expect(downstream_job2.scoped_variables.to_hash).to include('PROVIDER' => 'ovh', 'STACK' => 'app') @@ -1068,7 +1067,7 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService, feature_category private def all_builds - pipeline.processables.order(:stage_idx, :id) + pipeline.all_jobs.order(:stage_idx, :id) end def builds diff --git a/spec/services/ci/pipeline_processing/test_cases/dag_test_on_failure_no_needs.yml b/spec/services/ci/pipeline_processing/test_cases/dag_test_on_failure_no_needs.yml new file mode 100644 index 00000000000..12c51828628 --- /dev/null +++ b/spec/services/ci/pipeline_processing/test_cases/dag_test_on_failure_no_needs.yml @@ -0,0 +1,31 @@ +config: + test1: + stage: test + script: exit 0 + needs: [] + + test2: + stage: test + when: on_failure + script: exit 0 + needs: [] + +init: + expect: + pipeline: pending + stages: + test: pending + jobs: + test1: pending + test2: skipped + +transitions: + - event: success + jobs: [test1] + expect: + pipeline: success + stages: + test: success + jobs: + test1: success + test2: skipped diff --git a/spec/services/ci/pipeline_processing/test_cases/stage_build_cancels_test1_and_test2_have_when.yml b/spec/services/ci/pipeline_processing/test_cases/stage_build_cancels_test1_and_test2_have_when.yml new file mode 100644 index 00000000000..cc92aaba679 --- /dev/null +++ b/spec/services/ci/pipeline_processing/test_cases/stage_build_cancels_test1_and_test2_have_when.yml @@ -0,0 +1,46 @@ +config: + build: + stage: build + script: sleep 10 + + test1: + stage: test + script: exit 0 + when: on_success + + test2: + stage: test + script: exit 0 + when: on_failure + + deploy: + stage: deploy + script: exit 0 + +init: + expect: + pipeline: pending + stages: + build: pending + test: created + deploy: created + jobs: + build: pending + test1: created + test2: created + deploy: created + +transitions: + - event: cancel + jobs: [build] + expect: + pipeline: canceled + stages: + build: canceled + test: skipped + deploy: skipped + jobs: + build: canceled + test1: skipped + test2: skipped + deploy: skipped diff --git a/spec/services/ci/pipeline_processing/test_cases/stage_build_cancels_with_allow_failure_test1_and_test2_have_when.yml b/spec/services/ci/pipeline_processing/test_cases/stage_build_cancels_with_allow_failure_test1_and_test2_have_when.yml new file mode 100644 index 00000000000..34f01afe1de --- /dev/null +++ b/spec/services/ci/pipeline_processing/test_cases/stage_build_cancels_with_allow_failure_test1_and_test2_have_when.yml @@ -0,0 +1,47 @@ +config: + build: + stage: build + script: sleep 10 + allow_failure: true + + test1: + stage: test + script: exit 0 + when: on_success + + test2: + stage: test + script: exit 0 + when: on_failure + + deploy: + stage: deploy + script: exit 0 + +init: + expect: + pipeline: pending + stages: + build: pending + test: created + deploy: created + jobs: + build: pending + test1: created + test2: created + deploy: created + +transitions: + - event: cancel + jobs: [build] + expect: + pipeline: pending + stages: + build: success + test: pending + deploy: created + jobs: + build: canceled + test1: pending + test2: skipped + deploy: created diff --git a/spec/services/ci/pipeline_processing/test_cases/stage_test_on_failure_no_prev_stage.yml b/spec/services/ci/pipeline_processing/test_cases/stage_test_on_failure_no_prev_stage.yml new file mode 100644 index 00000000000..57b3aa9ae80 --- /dev/null +++ b/spec/services/ci/pipeline_processing/test_cases/stage_test_on_failure_no_prev_stage.yml @@ -0,0 +1,29 @@ +config: + test1: + stage: test + script: exit 0 + + test2: + stage: test + when: on_failure + script: exit 0 + +init: + expect: + pipeline: pending + stages: + test: pending + jobs: + test1: pending + test2: skipped + +transitions: + - event: success + jobs: [test1] + expect: + pipeline: success + stages: + test: success + jobs: + test1: success + test2: skipped diff --git a/spec/services/ci/pipeline_schedules/take_ownership_service_spec.rb b/spec/services/ci/pipeline_schedules/take_ownership_service_spec.rb index 9a3aad20d89..1d45a06f9ea 100644 --- a/spec/services/ci/pipeline_schedules/take_ownership_service_spec.rb +++ b/spec/services/ci/pipeline_schedules/take_ownership_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PipelineSchedules::TakeOwnershipService do +RSpec.describe Ci::PipelineSchedules::TakeOwnershipService, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:owner) { create(:user) } let_it_be(:reporter) { create(:user) } diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb index 4946367380e..b6e07e82bb5 100644 --- a/spec/services/ci/pipeline_trigger_service_spec.rb +++ b/spec/services/ci/pipeline_trigger_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PipelineTriggerService do +RSpec.describe Ci::PipelineTriggerService, feature_category: :continuous_integration do include AfterNextHelpers let_it_be(:project) { create(:project, :repository) } diff --git a/spec/services/ci/pipelines/add_job_service_spec.rb b/spec/services/ci/pipelines/add_job_service_spec.rb index c62aa9506bd..6380a6a5ec3 100644 --- a/spec/services/ci/pipelines/add_job_service_spec.rb +++ b/spec/services/ci/pipelines/add_job_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::Pipelines::AddJobService do +RSpec.describe Ci::Pipelines::AddJobService, feature_category: :continuous_integration do include ExclusiveLeaseHelpers let_it_be_with_reload(:pipeline) { create(:ci_pipeline) } @@ -86,5 +86,15 @@ RSpec.describe Ci::Pipelines::AddJobService do expect(execute.payload[:job]).to eq(job) end end + + it 'locks pipelines and stages before persisting builds', :aggregate_failures do + expect(job).not_to be_persisted + + recorder = ActiveRecord::QueryRecorder.new(skip_cached: false) { execute } + entries = recorder.log.select { |query| query.match(/LOCK|INSERT INTO ".{0,2}ci_builds"/) } + + expect(entries.size).to eq(2) + expect(entries.first).to match(/LOCK "ci_pipelines", "ci_stages" IN ROW SHARE MODE;/) + end end end diff --git a/spec/services/ci/pipelines/hook_service_spec.rb b/spec/services/ci/pipelines/hook_service_spec.rb index 8d138a3d957..e773ae2d2c3 100644 --- a/spec/services/ci/pipelines/hook_service_spec.rb +++ b/spec/services/ci/pipelines/hook_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::Pipelines::HookService do +RSpec.describe Ci::Pipelines::HookService, feature_category: :continuous_integration do describe '#execute_hooks' do let_it_be(:namespace) { create(:namespace) } let_it_be(:project) { create(:project, :repository, namespace: namespace) } diff --git a/spec/services/ci/play_bridge_service_spec.rb b/spec/services/ci/play_bridge_service_spec.rb index 56b1615a56d..5727ed64f8b 100644 --- a/spec/services/ci/play_bridge_service_spec.rb +++ b/spec/services/ci/play_bridge_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PlayBridgeService, '#execute' do +RSpec.describe Ci::PlayBridgeService, '#execute', feature_category: :continuous_integration do let(:project) { create(:project) } let(:user) { create(:user) } let(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb index fc07801b672..46b6622d6ec 100644 --- a/spec/services/ci/play_build_service_spec.rb +++ b/spec/services/ci/play_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PlayBuildService, '#execute' do +RSpec.describe Ci::PlayBuildService, '#execute', feature_category: :continuous_integration do let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } @@ -16,8 +16,7 @@ RSpec.describe Ci::PlayBuildService, '#execute' do let(:project) { create(:project) } it 'allows user to play build if protected branch rules are met' do - create(:protected_branch, :developers_can_merge, - name: build.ref, project: project) + create(:protected_branch, :developers_can_merge, name: build.ref, project: project) service.execute(build) diff --git a/spec/services/ci/play_manual_stage_service_spec.rb b/spec/services/ci/play_manual_stage_service_spec.rb index 24f0a21f3dd..dd8e037b129 100644 --- a/spec/services/ci/play_manual_stage_service_spec.rb +++ b/spec/services/ci/play_manual_stage_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PlayManualStageService, '#execute' do +RSpec.describe Ci::PlayManualStageService, '#execute', feature_category: :continuous_integration do let(:current_user) { create(:user) } let(:pipeline) { create(:ci_pipeline, user: current_user) } let(:project) { pipeline.project } @@ -11,10 +11,7 @@ RSpec.describe Ci::PlayManualStageService, '#execute' do let(:stage_status) { 'manual' } let(:stage) do - create(:ci_stage, - pipeline: pipeline, - project: project, - name: 'test') + create(:ci_stage, pipeline: pipeline, project: project, name: 'test') end before do diff --git a/spec/services/ci/prepare_build_service_spec.rb b/spec/services/ci/prepare_build_service_spec.rb index f75cb322fe9..8583b8e667c 100644 --- a/spec/services/ci/prepare_build_service_spec.rb +++ b/spec/services/ci/prepare_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PrepareBuildService do +RSpec.describe Ci::PrepareBuildService, feature_category: :continuous_integration do describe '#execute' do let(:build) { create(:ci_build, :preparing) } diff --git a/spec/services/ci/process_build_service_spec.rb b/spec/services/ci/process_build_service_spec.rb index de308bb1a87..d1442b75731 100644 --- a/spec/services/ci/process_build_service_spec.rb +++ b/spec/services/ci/process_build_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Ci::ProcessBuildService, '#execute' do +RSpec.describe Ci::ProcessBuildService, '#execute', feature_category: :continuous_integration do using RSpec::Parameterized::TableSyntax let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 404e1bf7c87..d1586ad4c8b 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::ProcessPipelineService do +RSpec.describe Ci::ProcessPipelineService, feature_category: :continuous_integration do let_it_be(:project) { create(:project) } let(:pipeline) do diff --git a/spec/services/ci/process_sync_events_service_spec.rb b/spec/services/ci/process_sync_events_service_spec.rb index 7ab7911e578..84b6d7d96f6 100644 --- a/spec/services/ci/process_sync_events_service_spec.rb +++ b/spec/services/ci/process_sync_events_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::ProcessSyncEventsService do +RSpec.describe Ci::ProcessSyncEventsService, feature_category: :continuous_integration do let!(:group) { create(:group) } let!(:project1) { create(:project, group: group) } let!(:project2) { create(:project, group: group) } @@ -147,8 +147,7 @@ RSpec.describe Ci::ProcessSyncEventsService do context 'when the FFs use_traversal_ids and use_traversal_ids_for_ancestors are disabled' do before do - stub_feature_flags(use_traversal_ids: false, - use_traversal_ids_for_ancestors: false) + stub_feature_flags(use_traversal_ids: false, use_traversal_ids_for_ancestors: false) end it_behaves_like 'event consuming' diff --git a/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb b/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb index 0b100af5902..a9ee5216d81 100644 --- a/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb +++ b/spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::PrometheusMetrics::ObserveHistogramsService do +RSpec.describe Ci::PrometheusMetrics::ObserveHistogramsService, feature_category: :continuous_integration do let_it_be(:project) { create(:project) } let(:params) { {} } diff --git a/spec/services/ci/queue/pending_builds_strategy_spec.rb b/spec/services/ci/queue/pending_builds_strategy_spec.rb index 6f22c256c17..ea9207ddb8f 100644 --- a/spec/services/ci/queue/pending_builds_strategy_spec.rb +++ b/spec/services/ci/queue/pending_builds_strategy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::Queue::PendingBuildsStrategy do +RSpec.describe Ci::Queue::PendingBuildsStrategy, feature_category: :continuous_integration do let_it_be(:group) { create(:group) } let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) } let_it_be(:project) { create(:project, group: group) } diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index 9183df359b4..6fb61bb3ec5 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -14,128 +14,157 @@ module Ci let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } describe '#execute' do - subject(:execute) { described_class.new(runner, runner_machine).execute } + subject(:execute) { described_class.new(runner, runner_manager).execute } - context 'with runner_machine specified' do - let(:runner) { project_runner } - let!(:runner_machine) { create(:ci_runner_machine, runner: project_runner) } + let(:runner_manager) { nil } + + context 'checks database loadbalancing stickiness' do + let(:runner) { shared_runner } before do - pending_job.update!(tag_list: ["linux"]) - pending_job.reload - pending_job.create_queuing_entry! - project_runner.update!(tag_list: ["linux"]) + project.update!(shared_runners_enabled: false) end - it 'sets runner_machine on job' do - expect { execute }.to change { pending_job.reload.runner_machine }.from(nil).to(runner_machine) + it 'result is valid if replica did caught-up', :aggregate_failures do + expect(ApplicationRecord.sticking).to receive(:all_caught_up?).with(:runner, runner.id) { true } - expect(execute.build).to eq(pending_job) + expect { execute }.not_to change { Ci::RunnerManagerBuild.count }.from(0) + expect(execute).to be_valid + expect(execute.build).to be_nil + expect(execute.build_json).to be_nil end - end - - context 'with no runner machine' do - let(:runner_machine) { nil } - - context 'checks database loadbalancing stickiness' do - let(:runner) { shared_runner } - - before do - project.update!(shared_runners_enabled: false) - end - it 'result is valid if replica did caught-up', :aggregate_failures do - expect(ApplicationRecord.sticking).to receive(:all_caught_up?).with(:runner, runner.id) { true } + it 'result is invalid if replica did not caught-up', :aggregate_failures do + expect(ApplicationRecord.sticking).to receive(:all_caught_up?) + .with(:runner, shared_runner.id) { false } - expect(execute).to be_valid - expect(execute.build).to be_nil - expect(execute.build_json).to be_nil - end + expect(subject).not_to be_valid + expect(subject.build).to be_nil + expect(subject.build_json).to be_nil + end + end - it 'result is invalid if replica did not caught-up', :aggregate_failures do - expect(ApplicationRecord.sticking).to receive(:all_caught_up?) - .with(:runner, shared_runner.id) { false } + shared_examples 'handles runner assignment' do + context 'runner follows tag list' do + subject(:build) { build_on(project_runner, runner_manager: project_runner_manager) } - expect(subject).not_to be_valid - expect(subject.build).to be_nil - expect(subject.build_json).to be_nil - end - end + let(:project_runner_manager) { nil } - shared_examples 'handles runner assignment' do - context 'runner follow tag list' do - it "picks build with the same tag" do + context 'when job has tag' do + before do pending_job.update!(tag_list: ["linux"]) pending_job.reload pending_job.create_queuing_entry! - project_runner.update!(tag_list: ["linux"]) - expect(build_on(project_runner)).to eq(pending_job) end - it "does not pick build with different tag" do - pending_job.update!(tag_list: ["linux"]) - pending_job.reload - pending_job.create_queuing_entry! - project_runner.update!(tag_list: ["win32"]) - expect(build_on(project_runner)).to be_falsey + context 'and runner has matching tag' do + before do + project_runner.update!(tag_list: ["linux"]) + end + + context 'with no runner manager specified' do + it 'picks build' do + expect(build).to eq(pending_job) + expect(pending_job.runner_manager).to be_nil + end + end + + context 'with runner manager specified' do + let(:project_runner_manager) { create(:ci_runner_machine, runner: project_runner) } + + it 'picks build and assigns runner manager' do + expect(build).to eq(pending_job) + expect(pending_job.runner_manager).to eq(project_runner_manager) + end + end end - it "picks build without tag" do - expect(build_on(project_runner)).to eq(pending_job) + it 'does not pick build with different tag' do + project_runner.update!(tag_list: ["win32"]) + expect(build).to be_falsey end - it "does not pick build with tag" do - pending_job.update!(tag_list: ["linux"]) - pending_job.reload + it 'does not pick build with tag' do pending_job.create_queuing_entry! - expect(build_on(project_runner)).to be_falsey + expect(build).to be_falsey end + end - it "pick build without tag" do - project_runner.update!(tag_list: ["win32"]) - expect(build_on(project_runner)).to eq(pending_job) + context 'when job has no tag' do + it 'picks build' do + expect(build).to eq(pending_job) + end + + context 'when runner has tag' do + before do + project_runner.update!(tag_list: ["win32"]) + end + + it 'picks build' do + expect(build).to eq(pending_job) + end end end + end - context 'deleted projects' do + context 'deleted projects' do + before do + project.update!(pending_delete: true) + end + + context 'for shared runners' do before do - project.update!(pending_delete: true) + project.update!(shared_runners_enabled: true) end - context 'for shared runners' do - before do - project.update!(shared_runners_enabled: true) - end + it 'does not pick a build' do + expect(build_on(shared_runner)).to be_nil + end + end + + context 'for project runner' do + subject(:build) { build_on(project_runner, runner_manager: project_runner_manager) } + let(:project_runner_manager) { nil } + + context 'with no runner manager specified' do it 'does not pick a build' do - expect(build_on(shared_runner)).to be_nil + expect(build).to be_nil + expect(pending_job.reload).to be_failed + expect(pending_job.queuing_entry).to be_nil + expect(Ci::RunnerManagerBuild.all).to be_empty end end - context 'for project runner' do + context 'with runner manager specified' do + let(:project_runner_manager) { create(:ci_runner_machine, runner: project_runner) } + it 'does not pick a build' do - expect(build_on(project_runner)).to be_nil + expect(build).to be_nil expect(pending_job.reload).to be_failed expect(pending_job.queuing_entry).to be_nil + expect(Ci::RunnerManagerBuild.all).to be_empty end end end + end - context 'allow shared runners' do - before do - project.update!(shared_runners_enabled: true) - pipeline.reload - pending_job.reload - pending_job.create_queuing_entry! - end + context 'allow shared runners' do + before do + project.update!(shared_runners_enabled: true) + pipeline.reload + pending_job.reload + pending_job.create_queuing_entry! + end - context 'when build owner has been blocked' do - let(:user) { create(:user, :blocked) } + context 'when build owner has been blocked' do + let(:user) { create(:user, :blocked) } - before do - pending_job.update!(user: user) - end + before do + pending_job.update!(user: user) + end + context 'with no runner manager specified' do it 'does not pick the build and drops the build' do expect(build_on(shared_runner)).to be_falsey @@ -143,690 +172,701 @@ module Ci end end - context 'for multiple builds' do - let!(:project2) { create :project, shared_runners_enabled: true } - let!(:pipeline2) { create :ci_pipeline, project: project2 } - let!(:project3) { create :project, shared_runners_enabled: true } - let!(:pipeline3) { create :ci_pipeline, project: project3 } - let!(:build1_project1) { pending_job } - let!(:build2_project1) { create(:ci_build, :pending, :queued, pipeline: pipeline) } - let!(:build3_project1) { create(:ci_build, :pending, :queued, pipeline: pipeline) } - let!(:build1_project2) { create(:ci_build, :pending, :queued, pipeline: pipeline2) } - let!(:build2_project2) { create(:ci_build, :pending, :queued, pipeline: pipeline2) } - let!(:build1_project3) { create(:ci_build, :pending, :queued, pipeline: pipeline3) } + context 'with runner manager specified' do + let(:runner_manager) { create(:ci_runner_machine, runner: runner) } - it 'picks builds one-by-one' do - expect(Ci::Build).to receive(:find).with(pending_job.id).and_call_original + it 'does not pick the build and does not create join record' do + expect(build_on(shared_runner, runner_manager: runner_manager)).to be_falsey - expect(build_on(shared_runner)).to eq(build1_project1) + expect(Ci::RunnerManagerBuild.all).to be_empty end + end + end - context 'when using fair scheduling' do - context 'when all builds are pending' do - it 'prefers projects without builds first' do - # it gets for one build from each of the projects - expect(build_on(shared_runner)).to eq(build1_project1) - expect(build_on(shared_runner)).to eq(build1_project2) - expect(build_on(shared_runner)).to eq(build1_project3) - - # then it gets a second build from each of the projects - expect(build_on(shared_runner)).to eq(build2_project1) - expect(build_on(shared_runner)).to eq(build2_project2) - - # in the end the third build - expect(build_on(shared_runner)).to eq(build3_project1) - end - end + context 'for multiple builds' do + let!(:project2) { create :project, shared_runners_enabled: true } + let!(:pipeline2) { create :ci_pipeline, project: project2 } + let!(:project3) { create :project, shared_runners_enabled: true } + let!(:pipeline3) { create :ci_pipeline, project: project3 } + let!(:build1_project1) { pending_job } + let!(:build2_project1) { create(:ci_build, :pending, :queued, pipeline: pipeline) } + let!(:build3_project1) { create(:ci_build, :pending, :queued, pipeline: pipeline) } + let!(:build1_project2) { create(:ci_build, :pending, :queued, pipeline: pipeline2) } + let!(:build2_project2) { create(:ci_build, :pending, :queued, pipeline: pipeline2) } + let!(:build1_project3) { create(:ci_build, :pending, :queued, pipeline: pipeline3) } + + it 'picks builds one-by-one' do + expect(Ci::Build).to receive(:find).with(pending_job.id).and_call_original + + expect(build_on(shared_runner)).to eq(build1_project1) + end - context 'when some builds transition to success' do - it 'equalises number of running builds' do - # after finishing the first build for project 1, get a second build from the same project - expect(build_on(shared_runner)).to eq(build1_project1) - build1_project1.reload.success - expect(build_on(shared_runner)).to eq(build2_project1) - - expect(build_on(shared_runner)).to eq(build1_project2) - build1_project2.reload.success - expect(build_on(shared_runner)).to eq(build2_project2) - expect(build_on(shared_runner)).to eq(build1_project3) - expect(build_on(shared_runner)).to eq(build3_project1) - end + context 'when using fair scheduling' do + context 'when all builds are pending' do + it 'prefers projects without builds first' do + # it gets for one build from each of the projects + expect(build_on(shared_runner)).to eq(build1_project1) + expect(build_on(shared_runner)).to eq(build1_project2) + expect(build_on(shared_runner)).to eq(build1_project3) + + # then it gets a second build from each of the projects + expect(build_on(shared_runner)).to eq(build2_project1) + expect(build_on(shared_runner)).to eq(build2_project2) + + # in the end the third build + expect(build_on(shared_runner)).to eq(build3_project1) end end - context 'when using DEFCON mode that disables fair scheduling' do - before do - stub_feature_flags(ci_queueing_disaster_recovery_disable_fair_scheduling: true) + context 'when some builds transition to success' do + it 'equalises number of running builds' do + # after finishing the first build for project 1, get a second build from the same project + expect(build_on(shared_runner)).to eq(build1_project1) + build1_project1.reload.success + expect(build_on(shared_runner)).to eq(build2_project1) + + expect(build_on(shared_runner)).to eq(build1_project2) + build1_project2.reload.success + expect(build_on(shared_runner)).to eq(build2_project2) + expect(build_on(shared_runner)).to eq(build1_project3) + expect(build_on(shared_runner)).to eq(build3_project1) end + end + end - context 'when all builds are pending' do - it 'returns builds in order of creation (FIFO)' do - # it gets for one build from each of the projects - expect(build_on(shared_runner)).to eq(build1_project1) - expect(build_on(shared_runner)).to eq(build2_project1) - expect(build_on(shared_runner)).to eq(build3_project1) - expect(build_on(shared_runner)).to eq(build1_project2) - expect(build_on(shared_runner)).to eq(build2_project2) - expect(build_on(shared_runner)).to eq(build1_project3) - end + context 'when using DEFCON mode that disables fair scheduling' do + before do + stub_feature_flags(ci_queueing_disaster_recovery_disable_fair_scheduling: true) + end + + context 'when all builds are pending' do + it 'returns builds in order of creation (FIFO)' do + # it gets for one build from each of the projects + expect(build_on(shared_runner)).to eq(build1_project1) + expect(build_on(shared_runner)).to eq(build2_project1) + expect(build_on(shared_runner)).to eq(build3_project1) + expect(build_on(shared_runner)).to eq(build1_project2) + expect(build_on(shared_runner)).to eq(build2_project2) + expect(build_on(shared_runner)).to eq(build1_project3) end + end - context 'when some builds transition to success' do - it 'returns builds in order of creation (FIFO)' do - expect(build_on(shared_runner)).to eq(build1_project1) - build1_project1.reload.success - expect(build_on(shared_runner)).to eq(build2_project1) - - expect(build_on(shared_runner)).to eq(build3_project1) - build2_project1.reload.success - expect(build_on(shared_runner)).to eq(build1_project2) - expect(build_on(shared_runner)).to eq(build2_project2) - expect(build_on(shared_runner)).to eq(build1_project3) - end + context 'when some builds transition to success' do + it 'returns builds in order of creation (FIFO)' do + expect(build_on(shared_runner)).to eq(build1_project1) + build1_project1.reload.success + expect(build_on(shared_runner)).to eq(build2_project1) + + expect(build_on(shared_runner)).to eq(build3_project1) + build2_project1.reload.success + expect(build_on(shared_runner)).to eq(build1_project2) + expect(build_on(shared_runner)).to eq(build2_project2) + expect(build_on(shared_runner)).to eq(build1_project3) end end end + end - context 'shared runner' do - let(:response) { described_class.new(shared_runner, nil).execute } - let(:build) { response.build } + context 'shared runner' do + let(:response) { described_class.new(shared_runner, nil).execute } + let(:build) { response.build } - it { expect(build).to be_kind_of(Build) } - it { expect(build).to be_valid } - it { expect(build).to be_running } - it { expect(build.runner).to eq(shared_runner) } - it { expect(Gitlab::Json.parse(response.build_json)['id']).to eq(build.id) } - end + it { expect(build).to be_kind_of(Build) } + it { expect(build).to be_valid } + it { expect(build).to be_running } + it { expect(build.runner).to eq(shared_runner) } + it { expect(Gitlab::Json.parse(response.build_json)['id']).to eq(build.id) } + end - context 'project runner' do - let(:build) { build_on(project_runner) } + context 'project runner' do + let(:build) { build_on(project_runner) } - it { expect(build).to be_kind_of(Build) } - it { expect(build).to be_valid } - it { expect(build).to be_running } - it { expect(build.runner).to eq(project_runner) } - end + it { expect(build).to be_kind_of(Build) } + it { expect(build).to be_valid } + it { expect(build).to be_running } + it { expect(build.runner).to eq(project_runner) } end + end - context 'disallow shared runners' do - before do - project.update!(shared_runners_enabled: false) - end + context 'disallow shared runners' do + before do + project.update!(shared_runners_enabled: false) + end - context 'shared runner' do - let(:build) { build_on(shared_runner) } + context 'shared runner' do + let(:build) { build_on(shared_runner) } - it { expect(build).to be_nil } - end + it { expect(build).to be_nil } + end - context 'project runner' do - let(:build) { build_on(project_runner) } + context 'project runner' do + let(:build) { build_on(project_runner) } - it { expect(build).to be_kind_of(Build) } - it { expect(build).to be_valid } - it { expect(build).to be_running } - it { expect(build.runner).to eq(project_runner) } - end + it { expect(build).to be_kind_of(Build) } + it { expect(build).to be_valid } + it { expect(build).to be_running } + it { expect(build.runner).to eq(project_runner) } end + end - context 'disallow when builds are disabled' do - before do - project.update!(shared_runners_enabled: true, group_runners_enabled: true) - project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) + context 'disallow when builds are disabled' do + before do + project.update!(shared_runners_enabled: true, group_runners_enabled: true) + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) - pending_job.reload.create_queuing_entry! - end + pending_job.reload.create_queuing_entry! + end - context 'and uses shared runner' do - let(:build) { build_on(shared_runner) } + context 'and uses shared runner' do + let(:build) { build_on(shared_runner) } - it { expect(build).to be_nil } - end + it { expect(build).to be_nil } + end - context 'and uses group runner' do - let(:build) { build_on(group_runner) } + context 'and uses group runner' do + let(:build) { build_on(group_runner) } - it { expect(build).to be_nil } - end + it { expect(build).to be_nil } + end - context 'and uses project runner' do - let(:build) { build_on(project_runner) } + context 'and uses project runner' do + let(:build) { build_on(project_runner) } - it 'does not pick a build' do - expect(build).to be_nil - expect(pending_job.reload).to be_failed - expect(pending_job.queuing_entry).to be_nil - end + it 'does not pick a build' do + expect(build).to be_nil + expect(pending_job.reload).to be_failed + expect(pending_job.queuing_entry).to be_nil end end + end - context 'allow group runners' do - before do - project.update!(group_runners_enabled: true) - end + context 'allow group runners' do + before do + project.update!(group_runners_enabled: true) + end - context 'for multiple builds' do - let!(:project2) { create(:project, group_runners_enabled: true, group: group) } - let!(:pipeline2) { create(:ci_pipeline, project: project2) } - let!(:project3) { create(:project, group_runners_enabled: true, group: group) } - let!(:pipeline3) { create(:ci_pipeline, project: project3) } + context 'for multiple builds' do + let!(:project2) { create(:project, group_runners_enabled: true, group: group) } + let!(:pipeline2) { create(:ci_pipeline, project: project2) } + let!(:project3) { create(:project, group_runners_enabled: true, group: group) } + let!(:pipeline3) { create(:ci_pipeline, project: project3) } - let!(:build1_project1) { pending_job } - let!(:build2_project1) { create(:ci_build, :queued, pipeline: pipeline) } - let!(:build3_project1) { create(:ci_build, :queued, pipeline: pipeline) } - let!(:build1_project2) { create(:ci_build, :queued, pipeline: pipeline2) } - let!(:build2_project2) { create(:ci_build, :queued, pipeline: pipeline2) } - let!(:build1_project3) { create(:ci_build, :queued, pipeline: pipeline3) } + let!(:build1_project1) { pending_job } + let!(:build2_project1) { create(:ci_build, :queued, pipeline: pipeline) } + let!(:build3_project1) { create(:ci_build, :queued, pipeline: pipeline) } + let!(:build1_project2) { create(:ci_build, :queued, pipeline: pipeline2) } + let!(:build2_project2) { create(:ci_build, :queued, pipeline: pipeline2) } + let!(:build1_project3) { create(:ci_build, :queued, pipeline: pipeline3) } - # these shouldn't influence the scheduling - let!(:unrelated_group) { create(:group) } - let!(:unrelated_project) { create(:project, group_runners_enabled: true, group: unrelated_group) } - let!(:unrelated_pipeline) { create(:ci_pipeline, project: unrelated_project) } - let!(:build1_unrelated_project) { create(:ci_build, :pending, :queued, pipeline: unrelated_pipeline) } - let!(:unrelated_group_runner) { create(:ci_runner, :group, groups: [unrelated_group]) } + # these shouldn't influence the scheduling + let!(:unrelated_group) { create(:group) } + let!(:unrelated_project) { create(:project, group_runners_enabled: true, group: unrelated_group) } + let!(:unrelated_pipeline) { create(:ci_pipeline, project: unrelated_project) } + let!(:build1_unrelated_project) { create(:ci_build, :pending, :queued, pipeline: unrelated_pipeline) } + let!(:unrelated_group_runner) { create(:ci_runner, :group, groups: [unrelated_group]) } - it 'does not consider builds from other group runners' do - queue = ::Ci::Queue::BuildQueueService.new(group_runner) + it 'does not consider builds from other group runners' do + queue = ::Ci::Queue::BuildQueueService.new(group_runner) - expect(queue.builds_for_group_runner.size).to eq 6 - build_on(group_runner) + expect(queue.builds_for_group_runner.size).to eq 6 + build_on(group_runner) - expect(queue.builds_for_group_runner.size).to eq 5 - build_on(group_runner) + expect(queue.builds_for_group_runner.size).to eq 5 + build_on(group_runner) - expect(queue.builds_for_group_runner.size).to eq 4 - build_on(group_runner) + expect(queue.builds_for_group_runner.size).to eq 4 + build_on(group_runner) - expect(queue.builds_for_group_runner.size).to eq 3 - build_on(group_runner) + expect(queue.builds_for_group_runner.size).to eq 3 + build_on(group_runner) - expect(queue.builds_for_group_runner.size).to eq 2 - build_on(group_runner) + expect(queue.builds_for_group_runner.size).to eq 2 + build_on(group_runner) - expect(queue.builds_for_group_runner.size).to eq 1 - build_on(group_runner) + expect(queue.builds_for_group_runner.size).to eq 1 + build_on(group_runner) - expect(queue.builds_for_group_runner.size).to eq 0 - expect(build_on(group_runner)).to be_nil - end + expect(queue.builds_for_group_runner.size).to eq 0 + expect(build_on(group_runner)).to be_nil end + end - context 'group runner' do - let(:build) { build_on(group_runner) } + context 'group runner' do + let(:build) { build_on(group_runner) } - it { expect(build).to be_kind_of(Build) } - it { expect(build).to be_valid } - it { expect(build).to be_running } - it { expect(build.runner).to eq(group_runner) } - end + it { expect(build).to be_kind_of(Build) } + it { expect(build).to be_valid } + it { expect(build).to be_running } + it { expect(build.runner).to eq(group_runner) } end + end - context 'disallow group runners' do - before do - project.update!(group_runners_enabled: false) + context 'disallow group runners' do + before do + project.update!(group_runners_enabled: false) - pending_job.reload.create_queuing_entry! - end + pending_job.reload.create_queuing_entry! + end - context 'group runner' do - let(:build) { build_on(group_runner) } + context 'group runner' do + let(:build) { build_on(group_runner) } - it { expect(build).to be_nil } - end + it { expect(build).to be_nil } end + end - 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!) - .with(pending_job, anything).and_raise(ActiveRecord::StaleObjectError) - end + 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!) + .with(pending_job, anything).and_raise(ActiveRecord::StaleObjectError) + end - subject { described_class.new(project_runner, nil).execute } + subject { described_class.new(project_runner, nil).execute } - context 'with multiple builds are in queue' do - let!(:other_build) { create(:ci_build, :pending, :queued, pipeline: pipeline) } + context 'with multiple builds are in queue' do + let!(:other_build) { create(:ci_build, :pending, :queued, pipeline: pipeline) } - before do - allow_any_instance_of(::Ci::Queue::BuildQueueService) - .to receive(:execute) - .and_return(Ci::Build.where(id: [pending_job, other_build]).pluck(:id)) - end + before do + allow_any_instance_of(::Ci::Queue::BuildQueueService) + .to receive(:execute) + .and_return(Ci::Build.where(id: [pending_job, other_build]).pluck(:id)) + end - it "receives second build from the queue" do - expect(subject).to be_valid - expect(subject.build).to eq(other_build) - end + it "receives second build from the queue" do + expect(subject).to be_valid + expect(subject.build).to eq(other_build) end + end - context 'when single build is in queue' do - before do - allow_any_instance_of(::Ci::Queue::BuildQueueService) - .to receive(:execute) - .and_return(Ci::Build.where(id: pending_job).pluck(:id)) - end + context 'when single build is in queue' do + before do + allow_any_instance_of(::Ci::Queue::BuildQueueService) + .to receive(:execute) + .and_return(Ci::Build.where(id: pending_job).pluck(:id)) + end - it "does not receive any valid result" do - expect(subject).not_to be_valid - end + it "does not receive any valid result" do + expect(subject).not_to be_valid end + end - context 'when there is no build in queue' do - before do - allow_any_instance_of(::Ci::Queue::BuildQueueService) - .to receive(:execute) - .and_return([]) - end + context 'when there is no build in queue' do + before do + allow_any_instance_of(::Ci::Queue::BuildQueueService) + .to receive(:execute) + .and_return([]) + end - it "does not receive builds but result is valid" do - expect(subject).to be_valid - expect(subject.build).to be_nil - end + it "does not receive builds but result is valid" do + expect(subject).to be_valid + expect(subject.build).to be_nil end end + end - context 'when access_level of runner is not_protected' do - let!(:project_runner) { create(:ci_runner, :project, projects: [project]) } + context 'when access_level of runner is not_protected' do + let!(:project_runner) { create(:ci_runner, :project, projects: [project]) } - context 'when a job is protected' do - let!(:pending_job) { create(:ci_build, :pending, :queued, :protected, pipeline: pipeline) } + context 'when a job is protected' do + let!(:pending_job) { create(:ci_build, :pending, :queued, :protected, pipeline: pipeline) } - it 'picks the job' do - expect(build_on(project_runner)).to eq(pending_job) - end + it 'picks the job' do + expect(build_on(project_runner)).to eq(pending_job) end + end - context 'when a job is unprotected' do - let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } + context 'when a job is unprotected' do + let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } - it 'picks the job' do - expect(build_on(project_runner)).to eq(pending_job) - end + it 'picks the job' do + expect(build_on(project_runner)).to eq(pending_job) end + end - context 'when protected attribute of a job is nil' do - let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } + context 'when protected attribute of a job is nil' do + let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } - before do - pending_job.update_attribute(:protected, nil) - end + before do + pending_job.update_attribute(:protected, nil) + end - it 'picks the job' do - expect(build_on(project_runner)).to eq(pending_job) - end + it 'picks the job' do + expect(build_on(project_runner)).to eq(pending_job) end end + end - context 'when access_level of runner is ref_protected' do - let!(:project_runner) { create(:ci_runner, :project, :ref_protected, projects: [project]) } + context 'when access_level of runner is ref_protected' do + let!(:project_runner) { create(:ci_runner, :project, :ref_protected, projects: [project]) } - context 'when a job is protected' do - let!(:pending_job) { create(:ci_build, :pending, :queued, :protected, pipeline: pipeline) } + context 'when a job is protected' do + let!(:pending_job) { create(:ci_build, :pending, :queued, :protected, pipeline: pipeline) } - it 'picks the job' do - expect(build_on(project_runner)).to eq(pending_job) - end + it 'picks the job' do + expect(build_on(project_runner)).to eq(pending_job) end + end - context 'when a job is unprotected' do - let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } + context 'when a job is unprotected' do + let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } - it 'does not pick the job' do - expect(build_on(project_runner)).to be_nil - end + it 'does not pick the job' do + expect(build_on(project_runner)).to be_nil end + end - context 'when protected attribute of a job is nil' do - let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } + context 'when protected attribute of a job is nil' do + let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } - before do - pending_job.update_attribute(:protected, nil) - end + before do + pending_job.update_attribute(:protected, nil) + end - it 'does not pick the job' do - expect(build_on(project_runner)).to be_nil - end + it 'does not pick the job' do + expect(build_on(project_runner)).to be_nil end end + end - context 'runner feature set is verified' do - let(:options) { { artifacts: { reports: { junit: "junit.xml" } } } } - let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, options: options) } + context 'runner feature set is verified' do + let(:options) { { artifacts: { reports: { junit: "junit.xml" } } } } + let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, options: options) } - subject { build_on(project_runner, params: params) } + subject { build_on(project_runner, params: params) } - context 'when feature is missing by runner' do - let(:params) { {} } + context 'when feature is missing by runner' do + let(:params) { {} } - it 'does not pick the build and drops the build' do - expect(subject).to be_nil - expect(pending_job.reload).to be_failed - expect(pending_job).to be_runner_unsupported - end + it 'does not pick the build and drops the build' do + expect(subject).to be_nil + expect(pending_job.reload).to be_failed + expect(pending_job).to be_runner_unsupported end + end - context 'when feature is supported by runner' do - let(:params) do - { info: { features: { upload_multiple_artifacts: true } } } - end + context 'when feature is supported by runner' do + let(:params) do + { info: { features: { upload_multiple_artifacts: true } } } + end - it 'does pick job' do - expect(subject).not_to be_nil - end + it 'does pick job' do + expect(subject).not_to be_nil end end + end - context 'when "dependencies" keyword is specified' do - let!(:pre_stage_job) do - create(:ci_build, :success, :artifacts, pipeline: pipeline, name: 'test', stage_idx: 0) - end + context 'when "dependencies" keyword is specified' do + let!(:pre_stage_job) do + create(:ci_build, :success, :artifacts, pipeline: pipeline, name: 'test', stage_idx: 0) + end - let!(:pending_job) do - create(:ci_build, :pending, :queued, - pipeline: pipeline, stage_idx: 1, - options: { script: ["bash"], dependencies: dependencies }) - end + let!(:pending_job) do + create(:ci_build, :pending, :queued, + pipeline: pipeline, stage_idx: 1, + options: { script: ["bash"], dependencies: dependencies }) + end - let(:dependencies) { %w[test] } + let(:dependencies) { %w[test] } - subject { build_on(project_runner) } + subject { build_on(project_runner) } - it 'picks a build with a dependency' do - picked_build = build_on(project_runner) + it 'picks a build with a dependency' do + picked_build = build_on(project_runner) - expect(picked_build).to be_present + expect(picked_build).to be_present + end + + context 'when there are multiple dependencies with artifacts' do + let!(:pre_stage_job_second) do + create(:ci_build, :success, :artifacts, pipeline: pipeline, name: 'deploy', stage_idx: 0) end - context 'when there are multiple dependencies with artifacts' do - let!(:pre_stage_job_second) do - create(:ci_build, :success, :artifacts, pipeline: pipeline, name: 'deploy', stage_idx: 0) - end + let(:dependencies) { %w[test deploy] } - let(:dependencies) { %w[test deploy] } + it 'logs build artifacts size' do + build_on(project_runner) - it 'logs build artifacts size' do - build_on(project_runner) + artifacts_size = [pre_stage_job, pre_stage_job_second].sum do |job| + job.job_artifacts_archive.size + end - artifacts_size = [pre_stage_job, pre_stage_job_second].sum do |job| - job.job_artifacts_archive.size - end + expect(artifacts_size).to eq 107464 * 2 + expect(Gitlab::ApplicationContext.current).to include({ + 'meta.artifacts_dependencies_size' => artifacts_size, + 'meta.artifacts_dependencies_count' => 2 + }) + end + end - expect(artifacts_size).to eq 107464 * 2 - expect(Gitlab::ApplicationContext.current).to include({ - 'meta.artifacts_dependencies_size' => artifacts_size, - 'meta.artifacts_dependencies_count' => 2 - }) - end + shared_examples 'not pick' do + it 'does not pick the build and drops the build' do + expect(subject).to be_nil + expect(pending_job.reload).to be_failed + expect(pending_job).to be_missing_dependency_failure end + end - shared_examples 'not pick' do - it 'does not pick the build and drops the build' do - expect(subject).to be_nil - expect(pending_job.reload).to be_failed - expect(pending_job).to be_missing_dependency_failure + shared_examples 'validation is active' do + context 'when depended job has not been completed yet' do + let!(:pre_stage_job) do + create(:ci_build, :pending, :queued, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) end - end - shared_examples 'validation is active' do - context 'when depended job has not been completed yet' do - let!(:pre_stage_job) do - create(:ci_build, :pending, :queued, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) - end + it { is_expected.to eq(pending_job) } + end - it { is_expected.to eq(pending_job) } + context 'when artifacts of depended job has been expired' do + let!(:pre_stage_job) do + create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) end - context 'when artifacts of depended job has been expired' do - let!(:pre_stage_job) do - create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) + context 'when the pipeline is locked' do + before do + pipeline.artifacts_locked! end - context 'when the pipeline is locked' do - before do - pipeline.artifacts_locked! - end + it { is_expected.to eq(pending_job) } + end - it { is_expected.to eq(pending_job) } + context 'when the pipeline is unlocked' do + before do + pipeline.unlocked! end - context 'when the pipeline is unlocked' do - before do - pipeline.unlocked! - end + it_behaves_like 'not pick' + end + end - it_behaves_like 'not pick' - end + context 'when artifacts of depended job has been erased' do + let!(:pre_stage_job) do + create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) end - context 'when artifacts of depended job has been erased' do - let!(:pre_stage_job) do - create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) - end + it_behaves_like 'not pick' + end - it_behaves_like 'not pick' + context 'when job object is staled' do + let!(:pre_stage_job) do + create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) end - context 'when job object is staled' do - let!(:pre_stage_job) do - create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) - end - - before do - pipeline.unlocked! + before do + pipeline.unlocked! - allow_next_instance_of(Ci::Build) do |build| - expect(build).to receive(:drop!) - .and_raise(ActiveRecord::StaleObjectError.new(pending_job, :drop!)) - end + allow_next_instance_of(Ci::Build) do |build| + expect(build).to receive(:drop!) + .and_raise(ActiveRecord::StaleObjectError.new(pending_job, :drop!)) end + end - it 'does not drop nor pick' do - expect(subject).to be_nil - end + it 'does not drop nor pick' do + expect(subject).to be_nil end end + end - shared_examples 'validation is not active' do - context 'when depended job has not been completed yet' do - let!(:pre_stage_job) do - create(:ci_build, :pending, :queued, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) - end - - it { expect(subject).to eq(pending_job) } + shared_examples 'validation is not active' do + context 'when depended job has not been completed yet' do + let!(:pre_stage_job) do + create(:ci_build, :pending, :queued, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) end - context 'when artifacts of depended job has been expired' do - let!(:pre_stage_job) do - create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) - end + it { expect(subject).to eq(pending_job) } + end - it { expect(subject).to eq(pending_job) } + context 'when artifacts of depended job has been expired' do + let!(:pre_stage_job) do + create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) end - context 'when artifacts of depended job has been erased' do - let!(:pre_stage_job) do - create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) - end + it { expect(subject).to eq(pending_job) } + end - it { expect(subject).to eq(pending_job) } + context 'when artifacts of depended job has been erased' do + let!(:pre_stage_job) do + create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) end - end - it_behaves_like 'validation is active' + it { expect(subject).to eq(pending_job) } + end end - context 'when build is degenerated' do - let!(:pending_job) { create(:ci_build, :pending, :queued, :degenerated, pipeline: pipeline) } + it_behaves_like 'validation is active' + end - subject { build_on(project_runner) } + context 'when build is degenerated' do + let!(:pending_job) { create(:ci_build, :pending, :queued, :degenerated, pipeline: pipeline) } - it 'does not pick the build and drops the build' do - expect(subject).to be_nil + subject { build_on(project_runner) } - pending_job.reload - expect(pending_job).to be_failed - expect(pending_job).to be_archived_failure - end + it 'does not pick the build and drops the build' do + expect(subject).to be_nil + + pending_job.reload + expect(pending_job).to be_failed + expect(pending_job).to be_archived_failure end + end - context 'when build has data integrity problem' do - let!(:pending_job) do - create(:ci_build, :pending, :queued, pipeline: pipeline) - end + context 'when build has data integrity problem' do + let!(:pending_job) do + create(:ci_build, :pending, :queued, pipeline: pipeline) + end - before do - pending_job.update_columns(options: "string") - end + before do + pending_job.update_columns(options: "string") + end - subject { build_on(project_runner) } + subject { build_on(project_runner) } - it 'does drop the build and logs both failures' do - expect(Gitlab::ErrorTracking).to receive(:track_exception) - .with(anything, a_hash_including(build_id: pending_job.id)) - .twice - .and_call_original + it 'does drop the build and logs both failures' do + expect(Gitlab::ErrorTracking).to receive(:track_exception) + .with(anything, a_hash_including(build_id: pending_job.id)) + .twice + .and_call_original - expect(subject).to be_nil + expect(subject).to be_nil - pending_job.reload - expect(pending_job).to be_failed - expect(pending_job).to be_data_integrity_failure - end + pending_job.reload + expect(pending_job).to be_failed + expect(pending_job).to be_data_integrity_failure end + end - context 'when build fails to be run!' do - let!(:pending_job) do - create(:ci_build, :pending, :queued, pipeline: pipeline) - end + context 'when build fails to be run!' do + let!(:pending_job) do + create(:ci_build, :pending, :queued, pipeline: pipeline) + end - before do - expect_any_instance_of(Ci::Build).to receive(:run!) - .and_raise(RuntimeError, 'scheduler error') - end + before do + expect_any_instance_of(Ci::Build).to receive(:run!) + .and_raise(RuntimeError, 'scheduler error') + end - subject { build_on(project_runner) } + subject { build_on(project_runner) } - it 'does drop the build and logs failure' do - expect(Gitlab::ErrorTracking).to receive(:track_exception) - .with(anything, a_hash_including(build_id: pending_job.id)) - .once - .and_call_original + it 'does drop the build and logs failure' do + expect(Gitlab::ErrorTracking).to receive(:track_exception) + .with(anything, a_hash_including(build_id: pending_job.id)) + .once + .and_call_original - expect(subject).to be_nil + expect(subject).to be_nil - pending_job.reload - expect(pending_job).to be_failed - expect(pending_job).to be_scheduler_failure - end + pending_job.reload + expect(pending_job).to be_failed + expect(pending_job).to be_scheduler_failure end + end - context 'when an exception is raised during a persistent ref creation' do - before do - allow_any_instance_of(Ci::PersistentRef).to receive(:exist?) { false } - allow_any_instance_of(Ci::PersistentRef).to receive(:create_ref) { raise ArgumentError } - end + context 'when an exception is raised during a persistent ref creation' do + before do + allow_any_instance_of(Ci::PersistentRef).to receive(:exist?) { false } + allow_any_instance_of(Ci::PersistentRef).to receive(:create_ref) { raise ArgumentError } + end - subject { build_on(project_runner) } + subject { build_on(project_runner) } - it 'picks the build' do - expect(subject).to eq(pending_job) + it 'picks the build' do + expect(subject).to eq(pending_job) - pending_job.reload - expect(pending_job).to be_running - end + pending_job.reload + expect(pending_job).to be_running end + end - context 'when only some builds can be matched by runner' do - let!(:project_runner) { create(:ci_runner, :project, projects: [project], tag_list: %w[matching]) } - let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, tag_list: %w[matching]) } + context 'when only some builds can be matched by runner' do + let!(:project_runner) { create(:ci_runner, :project, projects: [project], tag_list: %w[matching]) } + let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, tag_list: %w[matching]) } - before do - # create additional matching and non-matching jobs - create_list(:ci_build, 2, :pending, :queued, pipeline: pipeline, tag_list: %w[matching]) - create(:ci_build, :pending, :queued, pipeline: pipeline, tag_list: %w[non-matching]) - end + before do + # create additional matching and non-matching jobs + create_list(:ci_build, 2, :pending, :queued, pipeline: pipeline, tag_list: %w[matching]) + create(:ci_build, :pending, :queued, pipeline: pipeline, tag_list: %w[non-matching]) + end - it 'observes queue size of only matching jobs' do - # pending_job + 2 x matching ones - expect(Gitlab::Ci::Queue::Metrics.queue_size_total).to receive(:observe) - .with({ runner_type: project_runner.runner_type }, 3) + it 'observes queue size of only matching jobs' do + # pending_job + 2 x matching ones + expect(Gitlab::Ci::Queue::Metrics.queue_size_total).to receive(:observe) + .with({ runner_type: project_runner.runner_type }, 3) - expect(build_on(project_runner)).to eq(pending_job) - end + expect(build_on(project_runner)).to eq(pending_job) + end - it 'observes queue processing time by the runner type' do - expect(Gitlab::Ci::Queue::Metrics.queue_iteration_duration_seconds) - .to receive(:observe) - .with({ runner_type: project_runner.runner_type }, anything) + it 'observes queue processing time by the runner type' do + expect(Gitlab::Ci::Queue::Metrics.queue_iteration_duration_seconds) + .to receive(:observe) + .with({ runner_type: project_runner.runner_type }, anything) - expect(Gitlab::Ci::Queue::Metrics.queue_retrieval_duration_seconds) - .to receive(:observe) - .with({ runner_type: project_runner.runner_type }, anything) + expect(Gitlab::Ci::Queue::Metrics.queue_retrieval_duration_seconds) + .to receive(:observe) + .with({ runner_type: project_runner.runner_type }, anything) - expect(build_on(project_runner)).to eq(pending_job) - end + expect(build_on(project_runner)).to eq(pending_job) end + end - context 'when ci_register_job_temporary_lock is enabled' do - before do - stub_feature_flags(ci_register_job_temporary_lock: true) + context 'when ci_register_job_temporary_lock is enabled' do + before do + stub_feature_flags(ci_register_job_temporary_lock: true) + + allow(Gitlab::Ci::Queue::Metrics.queue_operations_total).to receive(:increment) + end - allow(Gitlab::Ci::Queue::Metrics.queue_operations_total).to receive(:increment) + context 'when a build is temporarily locked' do + let(:service) { described_class.new(project_runner, nil) } + + before do + service.send(:acquire_temporary_lock, pending_job.id) end - context 'when a build is temporarily locked' do - let(:service) { described_class.new(project_runner, nil) } + it 'skips this build and marks queue as invalid' do + expect(Gitlab::Ci::Queue::Metrics.queue_operations_total).to receive(:increment) + .with(operation: :queue_iteration) + expect(Gitlab::Ci::Queue::Metrics.queue_operations_total).to receive(:increment) + .with(operation: :build_temporary_locked) - before do - service.send(:acquire_temporary_lock, pending_job.id) - end + expect(service.execute).not_to be_valid + end - it 'skips this build and marks queue as invalid' do + context 'when there is another build in queue' do + let!(:next_pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } + + it 'skips this build and picks another build' do expect(Gitlab::Ci::Queue::Metrics.queue_operations_total).to receive(:increment) - .with(operation: :queue_iteration) + .with(operation: :queue_iteration).twice expect(Gitlab::Ci::Queue::Metrics.queue_operations_total).to receive(:increment) .with(operation: :build_temporary_locked) - expect(service.execute).not_to be_valid - end - - context 'when there is another build in queue' do - let!(:next_pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline) } - - it 'skips this build and picks another build' do - expect(Gitlab::Ci::Queue::Metrics.queue_operations_total).to receive(:increment) - .with(operation: :queue_iteration).twice - expect(Gitlab::Ci::Queue::Metrics.queue_operations_total).to receive(:increment) - .with(operation: :build_temporary_locked) + result = service.execute - result = service.execute - - expect(result.build).to eq(next_pending_job) - expect(result).to be_valid - end + expect(result.build).to eq(next_pending_job) + expect(result).to be_valid end end end end + end - context 'when using pending builds table' do - include_examples 'handles runner assignment' + context 'when using pending builds table' do + let!(:runner) { create(:ci_runner, :project, projects: [project], tag_list: %w[conflict]) } - context 'when a conflicting data is stored in denormalized table' do - let!(:runner) { create(:ci_runner, :project, projects: [project], tag_list: %w[conflict]) } - let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, tag_list: %w[conflict]) } + include_examples 'handles runner assignment' - before do - pending_job.update_column(:status, :running) - end + context 'when a conflicting data is stored in denormalized table' do + let!(:pending_job) { create(:ci_build, :pending, :queued, pipeline: pipeline, tag_list: %w[conflict]) } - it 'removes queuing entry upon build assignment attempt' do - expect(pending_job.reload).to be_running - expect(pending_job.queuing_entry).to be_present + before do + pending_job.update_column(:status, :running) + end - expect(execute).not_to be_valid - expect(pending_job.reload.queuing_entry).not_to be_present - end + it 'removes queuing entry upon build assignment attempt' do + expect(pending_job.reload).to be_running + expect(pending_job.queuing_entry).to be_present + + expect(execute).not_to be_valid + expect(pending_job.reload.queuing_entry).not_to be_present end end end @@ -997,8 +1037,8 @@ module Ci end end - def build_on(runner, runner_machine: nil, params: {}) - described_class.new(runner, runner_machine).execute(params).build + def build_on(runner, runner_manager: nil, params: {}) + described_class.new(runner, runner_manager).execute(params).build end end end diff --git a/spec/services/ci/reset_skipped_jobs_service_spec.rb b/spec/services/ci/reset_skipped_jobs_service_spec.rb index 712a21e665b..ba6a4a4e822 100644 --- a/spec/services/ci/reset_skipped_jobs_service_spec.rb +++ b/spec/services/ci/reset_skipped_jobs_service_spec.rb @@ -6,13 +6,22 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : let_it_be(:project) { create(:project, :empty_repo) } let_it_be(:user) { project.first_owner } + let(:pipeline) do + Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + end + + let(:a1) { find_job('a1') } + let(:a2) { find_job('a2') } + let(:b1) { find_job('b1') } + let(:input_processables) { a1 } # This is the input used when running service.execute() + before_all do project.repository.create_file(user, 'init', 'init', message: 'init', branch_name: 'master') end subject(:service) { described_class.new(project, user) } - context 'with a stage-dag mixed pipeline' do + shared_examples 'with a stage-dag mixed pipeline' do let(:config) do <<-YAML stages: [a, b, c] @@ -52,13 +61,6 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : YAML end - let(:pipeline) do - Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload - end - - let(:a1) { find_job('a1') } - let(:b1) { find_job('b1') } - before do stub_ci_pipeline_yaml_file(config) check_jobs_statuses( @@ -107,7 +109,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end it 'marks subsequent skipped jobs as processable' do - execute_after_requeue_service(a1) + service.execute(input_processables) check_jobs_statuses( a1: 'pending', @@ -135,7 +137,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : { 'name' => 'c2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => [] } ) - execute_after_requeue_service(a1) + service.execute(input_processables) expect(jobs_name_status_owner_needs).to contain_exactly( { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] }, @@ -150,7 +152,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end end - context 'with stage-dag mixed pipeline with some same-stage needs' do + shared_examples 'with stage-dag mixed pipeline with some same-stage needs' do let(:config) do <<-YAML stages: [a, b, c] @@ -184,12 +186,6 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : YAML end - let(:pipeline) do - Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload - end - - let(:a1) { find_job('a1') } - before do stub_ci_pipeline_yaml_file(config) check_jobs_statuses( @@ -224,7 +220,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end it 'marks subsequent skipped jobs as processable' do - execute_after_requeue_service(a1) + service.execute(input_processables) check_jobs_statuses( a1: 'pending', @@ -237,61 +233,465 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end end - context 'with same-stage needs' do + shared_examples 'with same-stage needs' do let(:config) do <<-YAML - a: + a1: script: exit $(($RANDOM % 2)) - b: + b1: script: exit 0 - needs: [a] + needs: [a1] - c: + c1: script: exit 0 - needs: [b] + needs: [b1] YAML end - let(:pipeline) do - Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + before do + stub_ci_pipeline_yaml_file(config) + check_jobs_statuses( + a1: 'pending', + b1: 'created', + c1: 'created' + ) + + a1.drop! + check_jobs_statuses( + a1: 'failed', + b1: 'skipped', + c1: 'skipped' + ) + + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) + new_a1.enqueue! + check_jobs_statuses( + a1: 'pending', + b1: 'skipped', + c1: 'skipped' + ) end - let(:a) { find_job('a') } + it 'marks subsequent skipped jobs as processable' do + service.execute(input_processables) + + check_jobs_statuses( + a1: 'pending', + b1: 'created', + c1: 'created' + ) + end + end + + context 'with same-stage needs where the parent jobs do not share the same descendants' do + let(:config) do + <<-YAML + a1: + script: exit $(($RANDOM % 2)) + + a2: + script: exit $(($RANDOM % 2)) + + b1: + script: exit 0 + needs: [a1] + + b2: + script: exit 0 + needs: [a2] + + c1: + script: exit 0 + needs: [b1] + + c2: + script: exit 0 + needs: [b2] + YAML + end before do stub_ci_pipeline_yaml_file(config) check_jobs_statuses( - a: 'pending', - b: 'created', - c: 'created' + a1: 'pending', + a2: 'pending', + b1: 'created', + b2: 'created', + c1: 'created', + c2: 'created' ) - a.drop! + a1.drop! + a2.drop! + check_jobs_statuses( - a: 'failed', - b: 'skipped', - c: 'skipped' + a1: 'failed', + a2: 'failed', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) + new_a1.enqueue! + + check_jobs_statuses( + a1: 'pending', + a2: 'failed', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' ) - new_a = Ci::RetryJobService.new(project, user).clone!(a) - new_a.enqueue! + new_a2 = Ci::RetryJobService.new(project, user).clone!(a2) + new_a2.enqueue! + check_jobs_statuses( - a: 'pending', - b: 'skipped', - c: 'skipped' + a1: 'pending', + a2: 'pending', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' ) end + # This demonstrates that when only a1 is inputted, only the *1 subsequent jobs are reset. + # This is in contrast to the following example when both a1 and a2 are inputted. it 'marks subsequent skipped jobs as processable' do - execute_after_requeue_service(a) + service.execute(input_processables) check_jobs_statuses( - a: 'pending', - b: 'created', - c: 'created' + a1: 'pending', + a2: 'pending', + b1: 'created', + b2: 'skipped', + c1: 'created', + c2: 'skipped' ) end + + context 'when multiple processables are inputted' do + # When both a1 and a2 are inputted, all subsequent jobs are reset. + it 'marks subsequent skipped jobs as processable' do + input_processables = [a1, a2] + service.execute(input_processables) + + check_jobs_statuses( + a1: 'pending', + a2: 'pending', + b1: 'created', + b2: 'created', + c1: 'created', + c2: 'created' + ) + end + end + end + + context 'when a single processable is inputted' do + it_behaves_like 'with a stage-dag mixed pipeline' + it_behaves_like 'with stage-dag mixed pipeline with some same-stage needs' + it_behaves_like 'with same-stage needs' + end + + context 'when multiple processables are inputted' do + let(:input_processables) { [a1, b1] } + + it_behaves_like 'with a stage-dag mixed pipeline' + it_behaves_like 'with stage-dag mixed pipeline with some same-stage needs' + it_behaves_like 'with same-stage needs' + end + + context 'when FF is `ci_support_reset_skipped_jobs_for_multiple_jobs` disabled' do + before do + stub_feature_flags(ci_support_reset_skipped_jobs_for_multiple_jobs: false) + end + + context 'with a stage-dag mixed pipeline' do + let(:config) do + <<-YAML + stages: [a, b, c] + + a1: + stage: a + script: exit $(($RANDOM % 2)) + + a2: + stage: a + script: exit 0 + needs: [a1] + + a3: + stage: a + script: exit 0 + needs: [a2] + + b1: + stage: b + script: exit 0 + needs: [] + + b2: + stage: b + script: exit 0 + needs: [a2] + + c1: + stage: c + script: exit 0 + needs: [b2] + + c2: + stage: c + script: exit 0 + YAML + end + + let(:pipeline) do + Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + end + + let(:a1) { find_job('a1') } + let(:b1) { find_job('b1') } + + before do + stub_ci_pipeline_yaml_file(config) + check_jobs_statuses( + a1: 'pending', + a2: 'created', + a3: 'created', + b1: 'pending', + b2: 'created', + c1: 'created', + c2: 'created' + ) + + b1.success! + check_jobs_statuses( + a1: 'pending', + a2: 'created', + a3: 'created', + b1: 'success', + b2: 'created', + c1: 'created', + c2: 'created' + ) + + a1.drop! + check_jobs_statuses( + a1: 'failed', + a2: 'skipped', + a3: 'skipped', + b1: 'success', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) + new_a1.enqueue! + check_jobs_statuses( + a1: 'pending', + a2: 'skipped', + a3: 'skipped', + b1: 'success', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + end + + it 'marks subsequent skipped jobs as processable' do + execute_after_requeue_service(a1) + + check_jobs_statuses( + a1: 'pending', + a2: 'created', + a3: 'created', + b1: 'success', + b2: 'created', + c1: 'created', + c2: 'created' + ) + end + + context 'when executed by a different user than the original owner' do + let(:retryer) { create(:user).tap { |u| project.add_maintainer(u) } } + let(:service) { described_class.new(project, retryer) } + + it 'reassigns jobs with updated statuses to the retryer' do + expect(jobs_name_status_owner_needs).to contain_exactly( + { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'a2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a1'] }, + { 'name' => 'a3', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] }, + { 'name' => 'b1', 'status' => 'success', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'b2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['a2'] }, + { 'name' => 'c1', 'status' => 'skipped', 'user_id' => user.id, 'needs' => ['b2'] }, + { 'name' => 'c2', 'status' => 'skipped', 'user_id' => user.id, 'needs' => [] } + ) + + execute_after_requeue_service(a1) + + expect(jobs_name_status_owner_needs).to contain_exactly( + { 'name' => 'a1', 'status' => 'pending', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'a2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a1'] }, + { 'name' => 'a3', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a2'] }, + { 'name' => 'b1', 'status' => 'success', 'user_id' => user.id, 'needs' => [] }, + { 'name' => 'b2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['a2'] }, + { 'name' => 'c1', 'status' => 'created', 'user_id' => retryer.id, 'needs' => ['b2'] }, + { 'name' => 'c2', 'status' => 'created', 'user_id' => retryer.id, 'needs' => [] } + ) + end + end + end + + context 'with stage-dag mixed pipeline with some same-stage needs' do + let(:config) do + <<-YAML + stages: [a, b, c] + + a1: + stage: a + script: exit $(($RANDOM % 2)) + + a2: + stage: a + script: exit 0 + needs: [a1] + + b1: + stage: b + script: exit 0 + needs: [b2] + + b2: + stage: b + script: exit 0 + + c1: + stage: c + script: exit 0 + needs: [b2] + + c2: + stage: c + script: exit 0 + YAML + end + + let(:pipeline) do + Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + end + + let(:a1) { find_job('a1') } + + before do + stub_ci_pipeline_yaml_file(config) + check_jobs_statuses( + a1: 'pending', + a2: 'created', + b1: 'created', + b2: 'created', + c1: 'created', + c2: 'created' + ) + + a1.drop! + check_jobs_statuses( + a1: 'failed', + a2: 'skipped', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + + new_a1 = Ci::RetryJobService.new(project, user).clone!(a1) + new_a1.enqueue! + check_jobs_statuses( + a1: 'pending', + a2: 'skipped', + b1: 'skipped', + b2: 'skipped', + c1: 'skipped', + c2: 'skipped' + ) + end + + it 'marks subsequent skipped jobs as processable' do + execute_after_requeue_service(a1) + + check_jobs_statuses( + a1: 'pending', + a2: 'created', + b1: 'created', + b2: 'created', + c1: 'created', + c2: 'created' + ) + end + end + + context 'with same-stage needs' do + let(:config) do + <<-YAML + a: + script: exit $(($RANDOM % 2)) + + b: + script: exit 0 + needs: [a] + + c: + script: exit 0 + needs: [b] + YAML + end + + let(:pipeline) do + Ci::CreatePipelineService.new(project, user, { ref: 'master' }).execute(:push).payload + end + + let(:a) { find_job('a') } + + before do + stub_ci_pipeline_yaml_file(config) + check_jobs_statuses( + a: 'pending', + b: 'created', + c: 'created' + ) + + a.drop! + check_jobs_statuses( + a: 'failed', + b: 'skipped', + c: 'skipped' + ) + + new_a = Ci::RetryJobService.new(project, user).clone!(a) + new_a.enqueue! + check_jobs_statuses( + a: 'pending', + b: 'skipped', + c: 'skipped' + ) + end + + it 'marks subsequent skipped jobs as processable' do + execute_after_requeue_service(a) + + check_jobs_statuses( + a: 'pending', + b: 'created', + c: 'created' + ) + end + end end private @@ -314,6 +714,7 @@ RSpec.describe Ci::ResetSkippedJobsService, :sidekiq_inline, feature_category: : end end + # Remove this method when FF is `ci_support_reset_skipped_jobs_for_multiple_jobs` is removed def execute_after_requeue_service(processable) service.execute(processable) end diff --git a/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb index 3d1abe290bc..ea15e3ea2c0 100644 --- a/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb +++ b/spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupService do +RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupService, feature_category: :continuous_integration do include ConcurrentHelpers let_it_be(:project) { create(:project) } diff --git a/spec/services/ci/retry_job_service_spec.rb b/spec/services/ci/retry_job_service_spec.rb index fed66bc535d..f15f4a16d4f 100644 --- a/spec/services/ci/retry_job_service_spec.rb +++ b/spec/services/ci/retry_job_service_spec.rb @@ -51,11 +51,13 @@ RSpec.describe Ci::RetryJobService, feature_category: :continuous_integration do let_it_be(:another_pipeline) { create(:ci_empty_pipeline, project: project) } let_it_be(:job_to_clone) do - create(:ci_build, :failed, :picked, :expired, :erased, :queued, :coverage, :tags, - :allowed_to_fail, :on_tag, :triggered, :teardown_environment, :resource_group, - description: 'my-job', ci_stage: stage, - pipeline: pipeline, auto_canceled_by: another_pipeline, - scheduled_at: 10.seconds.since) + create( + :ci_build, :failed, :picked, :expired, :erased, :queued, :coverage, :tags, + :allowed_to_fail, :on_tag, :triggered, :teardown_environment, :resource_group, + description: 'my-job', ci_stage: stage, + pipeline: pipeline, auto_canceled_by: another_pipeline, + scheduled_at: 10.seconds.since + ) end before do @@ -236,8 +238,7 @@ RSpec.describe Ci::RetryJobService, feature_category: :continuous_integration do context 'when a build with a deployment is retried' do let!(:job) do - create(:ci_build, :with_deployment, :deploy_to_production, - pipeline: pipeline, ci_stage: stage) + create(:ci_build, :with_deployment, :deploy_to_production, pipeline: pipeline, ci_stage: stage) end it 'creates a new deployment' do diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index 07518c35fab..fc2c66e7f73 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::RetryPipelineService, '#execute' do +RSpec.describe Ci::RetryPipelineService, '#execute', feature_category: :continuous_integration do include ProjectForksHelper let_it_be_with_refind(:user) { create(:user) } @@ -19,8 +19,7 @@ RSpec.describe Ci::RetryPipelineService, '#execute' do before do project.add_developer(user) - create(:protected_branch, :developers_can_merge, - name: pipeline.ref, project: project) + create(:protected_branch, :developers_can_merge, name: pipeline.ref, project: project) end context 'when there are already retried jobs present' do @@ -408,8 +407,7 @@ RSpec.describe Ci::RetryPipelineService, '#execute' do context 'when user is not allowed to trigger manual action' do before do project.add_developer(user) - create(:protected_branch, :maintainers_can_push, - name: pipeline.ref, project: project) + create(:protected_branch, :maintainers_can_push, name: pipeline.ref, project: project) end context 'when there is a failed manual action present' do @@ -490,11 +488,15 @@ RSpec.describe Ci::RetryPipelineService, '#execute' do end def create_processable(type, name, status, stage, **opts) - create(type, name: name, - status: status, - ci_stage: stage, - stage_idx: stage.position, - pipeline: pipeline, **opts) do |_job| + create( + type, + name: name, + status: status, + ci_stage: stage, + stage_idx: stage.position, + pipeline: pipeline, + **opts + ) do |_job| ::Ci::ProcessPipelineService.new(pipeline).execute end end diff --git a/spec/services/ci/run_scheduled_build_service_spec.rb b/spec/services/ci/run_scheduled_build_service_spec.rb index 27d25e88944..33f9efcb89f 100644 --- a/spec/services/ci/run_scheduled_build_service_spec.rb +++ b/spec/services/ci/run_scheduled_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::RunScheduledBuildService do +RSpec.describe Ci::RunScheduledBuildService, feature_category: :continuous_integration do let(:user) { create(:user) } let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } @@ -13,8 +13,7 @@ RSpec.describe Ci::RunScheduledBuildService do before do project.add_developer(user) - create(:protected_branch, :developers_can_merge, - name: pipeline.ref, project: project) + create(:protected_branch, :developers_can_merge, name: pipeline.ref, project: project) end context 'when build is scheduled' do diff --git a/spec/services/ci/runners/create_runner_service_spec.rb b/spec/services/ci/runners/create_runner_service_spec.rb index 673bf3ef90e..db337b0b005 100644 --- a/spec/services/ci/runners/create_runner_service_spec.rb +++ b/spec/services/ci/runners/create_runner_service_spec.rb @@ -3,24 +3,20 @@ require 'spec_helper' RSpec.describe ::Ci::Runners::CreateRunnerService, "#execute", feature_category: :runner_fleet do - subject(:execute) { described_class.new(user: current_user, type: type, params: params).execute } + subject(:execute) { described_class.new(user: current_user, params: params).execute } let(:runner) { execute.payload[:runner] } let_it_be(:admin) { create(:admin) } let_it_be(:non_admin_user) { create(:user) } let_it_be(:anonymous) { nil } + let_it_be(:group_owner) { create(:user) } - shared_context 'when admin user' do - let(:current_user) { admin } - - before do - allow(current_user).to receive(:can?).with(:create_instance_runners).and_return true - end - end + let_it_be(:group) { create(:group) } shared_examples 'it can create a runner' do - it 'creates a runner of the specified type' do + it 'creates a runner of the specified type', :aggregate_failures do + is_expected.to be_success expect(runner.runner_type).to eq expected_type end @@ -42,7 +38,7 @@ RSpec.describe ::Ci::Runners::CreateRunnerService, "#execute", feature_category: expect(runner.active).to be true expect(runner.creator).to be current_user expect(runner.authenticated_user_registration_type?).to be_truthy - expect(runner.runner_type).to eq 'instance_type' + expect(runner.runner_type).to eq expected_type end end @@ -81,7 +77,50 @@ RSpec.describe ::Ci::Runners::CreateRunnerService, "#execute", feature_category: expect(runner.maximum_timeout).to eq args[:maximum_timeout] expect(runner.authenticated_user_registration_type?).to be_truthy - expect(runner.runner_type).to eq 'instance_type' + expect(runner.runner_type).to eq expected_type + end + + context 'with a nil paused value' do + let(:args) do + { + paused: nil, + description: 'some description', + maintenance_note: 'a note', + tag_list: %w[tag1 tag2], + access_level: 'ref_protected', + locked: true, + maximum_timeout: 600, + run_untagged: false + } + end + + it { is_expected.to be_success } + + it 'creates runner with active set to true' do + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.active).to eq true + end + end + + context 'with no paused value given' do + let(:args) do + { + description: 'some description', + maintenance_note: 'a note', + tag_list: %w[tag1 tag2], + access_level: 'ref_protected', + locked: true, + maximum_timeout: 600, + run_untagged: false + } + end + + it { is_expected.to be_success } + + it 'creates runner with active set to true' do + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.active).to eq true + end end end end @@ -95,7 +134,6 @@ RSpec.describe ::Ci::Runners::CreateRunnerService, "#execute", feature_category: end shared_examples 'it can return an error' do - let(:group) { create(:group) } let(:runner_double) { Ci::Runner.new } context 'when the runner fails to save' do @@ -111,25 +149,148 @@ RSpec.describe ::Ci::Runners::CreateRunnerService, "#execute", feature_category: end end - context 'with type param set to nil' do + context 'with :runner_type param set to instance_type' do let(:expected_type) { 'instance_type' } - let(:type) { nil } - let(:params) { {} } + let(:params) { { runner_type: 'instance_type' } } - it_behaves_like 'it cannot create a runner' do + context 'when anonymous user' do let(:current_user) { anonymous } + + it_behaves_like 'it cannot create a runner' end - it_behaves_like 'it cannot create a runner' do + context 'when non-admin user' do let(:current_user) { non_admin_user } + + it_behaves_like 'it cannot create a runner' end - it_behaves_like 'it can create a runner' do - include_context 'when admin user' + context 'when admin user' do + let(:current_user) { admin } + + it_behaves_like 'it cannot create a runner' + + context 'when admin mode is enabled', :enable_admin_mode do + it_behaves_like 'it can create a runner' + it_behaves_like 'it can return an error' + + context 'with unexpected scope param specified' do + let(:params) { { runner_type: 'instance_type', scope: group } } + + it_behaves_like 'it cannot create a runner' + end + + context 'when model validation fails' do + let(:params) { { runner_type: 'instance_type', run_untagged: false, tag_list: [] } } + + it_behaves_like 'it cannot create a runner' + + it 'returns error message and reason', :aggregate_failures do + expect(execute.reason).to eq(:save_error) + expect(execute.message).to contain_exactly(a_string_including('Tags list can not be empty')) + end + end + end + end + end + + context 'with :runner_type param set to group_type' do + let(:expected_type) { 'group_type' } + let(:params) { { runner_type: 'group_type', scope: group } } + + before do + group.add_developer(non_admin_user) + group.add_owner(group_owner) + end + + context 'when anonymous user' do + let(:current_user) { anonymous } + + it_behaves_like 'it cannot create a runner' + end + + context 'when non-admin user' do + let(:current_user) { non_admin_user } + + it_behaves_like 'it cannot create a runner' end - it_behaves_like 'it can return an error' do - include_context 'when admin user' + context 'when group owner' do + let(:current_user) { group_owner } + + it_behaves_like 'it can create a runner' + + context 'with missing scope param' do + let(:params) { { runner_type: 'group_type' } } + + it_behaves_like 'it cannot create a runner' + end + end + + context 'when admin user' do + let(:current_user) { admin } + + it_behaves_like 'it cannot create a runner' + + context 'when admin mode is enabled', :enable_admin_mode do + it_behaves_like 'it can create a runner' + it_behaves_like 'it can return an error' + end + end + end + + context 'with :runner_type param set to project_type' do + let_it_be(:project) { create(:project, namespace: group) } + + let(:expected_type) { 'project_type' } + let(:params) { { runner_type: 'project_type', scope: project } } + + before do + group.add_developer(non_admin_user) + group.add_owner(group_owner) + end + + context 'when anonymous user' do + let(:current_user) { anonymous } + + it_behaves_like 'it cannot create a runner' + end + + context 'when group owner' do + let(:current_user) { group_owner } + + it_behaves_like 'it can create a runner' + + context 'with missing scope param' do + let(:params) { { runner_type: 'project_type' } } + + it_behaves_like 'it cannot create a runner' + end + end + + context 'when non-admin user' do + let(:current_user) { non_admin_user } + + it_behaves_like 'it cannot create a runner' + + context 'with project permissions to create runner' do + before do + project.add_maintainer(current_user) + end + + it_behaves_like 'it can create a runner' + end + end + + context 'when admin user' do + let(:current_user) { admin } + + it_behaves_like 'it cannot create a runner' + + context 'when admin mode is enabled', :enable_admin_mode do + it_behaves_like 'it can create a runner' + it_behaves_like 'it can return an error' + end end end end diff --git a/spec/services/ci/runners/process_runner_version_update_service_spec.rb b/spec/services/ci/runners/process_runner_version_update_service_spec.rb index e62cb1ec3e3..f8b7aa281af 100644 --- a/spec/services/ci/runners/process_runner_version_update_service_spec.rb +++ b/spec/services/ci/runners/process_runner_version_update_service_spec.rb @@ -29,6 +29,19 @@ RSpec.describe Ci::Runners::ProcessRunnerVersionUpdateService, feature_category: end end + context 'when fetching runner releases is disabled' do + before do + stub_application_setting(update_runner_versions_enabled: false) + end + + it 'does not update ci_runner_versions records', :aggregate_failures do + expect do + expect(execute).to be_error + expect(execute.message).to eq 'version update disabled' + end.not_to change(Ci::RunnerVersion, :count).from(0) + end + end + context 'with successful result from upgrade check' do before do url = ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url diff --git a/spec/services/ci/runners/register_runner_service_spec.rb b/spec/services/ci/runners/register_runner_service_spec.rb index c67040e45eb..b5921773364 100644 --- a/spec/services/ci/runners/register_runner_service_spec.rb +++ b/spec/services/ci/runners/register_runner_service_spec.rb @@ -7,13 +7,23 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute', feature_categor let(:token) {} let(:args) { {} } let(:runner) { execute.payload[:runner] } + let(:allow_runner_registration_token) { true } before do stub_application_setting(runners_registration_token: registration_token) stub_application_setting(valid_runner_registrars: ApplicationSetting::VALID_RUNNER_REGISTRAR_TYPES) + stub_application_setting(allow_runner_registration_token: allow_runner_registration_token) end - subject(:execute) { described_class.new.execute(token, args) } + subject(:execute) { described_class.new(token, args).execute } + + shared_examples 'runner registration is disallowed' do + it 'returns error response with runner_registration_disallowed reason' do + expect(execute).to be_error + expect(execute.message).to eq 'runner registration disallowed' + expect(execute.reason).to eq :runner_registration_disallowed + end + end context 'when no token is provided' do let(:token) { '' } @@ -36,7 +46,7 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute', feature_categor end context 'when valid token is provided' do - context 'with a registration token' do + context 'when instance registration token is used' do let(:token) { registration_token } it 'creates runner with default values' do @@ -51,6 +61,12 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute', feature_categor expect(runner).to be_instance_type end + context 'when registering instance runners is disallowed' do + let(:allow_runner_registration_token) { false } + + it_behaves_like 'runner registration is disallowed' + end + context 'with non-default arguments' do let(:args) do { @@ -112,9 +128,15 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute', feature_categor end end - context 'when project token is used' do - let(:project) { create(:project) } + context 'when project registration token is used' do + let_it_be(:project) { create(:project, :with_namespace_settings) } + let(:token) { project.runners_token } + let(:allow_group_runner_registration_token) { true } + + before do + project.namespace.update!(allow_runner_registration_token: allow_group_runner_registration_token) + end it 'creates project runner' do expect(execute).to be_success @@ -127,6 +149,18 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute', feature_categor expect(runner).to be_project_type end + context 'with runner registration disabled at instance level' do + let(:allow_runner_registration_token) { false } + + it_behaves_like 'runner registration is disallowed' + end + + context 'with runner registration disabled at group level' do + let(:allow_group_runner_registration_token) { false } + + it_behaves_like 'runner registration is disallowed' + end + context 'when it exceeds the application limits' do before do create(:ci_runner, runner_type: :project_type, projects: [project], contacted_at: 1.second.ago) @@ -173,9 +207,15 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute', feature_categor end end - context 'when group token is used' do - let(:group) { create(:group) } + context 'when group registration token is used' do + let_it_be_with_refind(:group) { create(:group) } + let(:token) { group.runners_token } + let(:allow_group_runner_registration_token) { true } + + before do + group.update!(allow_runner_registration_token: allow_group_runner_registration_token) + end it 'creates a group runner' do expect(execute).to be_success @@ -188,6 +228,18 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute', feature_categor expect(runner).to be_group_type end + context 'with runner registration disabled at instance level' do + let(:allow_runner_registration_token) { false } + + it_behaves_like 'runner registration is disallowed' + end + + context 'with runner registration disabled at group level' do + let(:allow_group_runner_registration_token) { false } + + it_behaves_like 'runner registration is disallowed' + end + context 'when it exceeds the application limits' do before do create(:ci_runner, runner_type: :group_type, groups: [group], contacted_at: nil, created_at: 1.month.ago) diff --git a/spec/services/ci/runners/stale_machines_cleanup_service_spec.rb b/spec/services/ci/runners/stale_managers_cleanup_service_spec.rb index 456dbcebb84..a78506ca5f7 100644 --- a/spec/services/ci/runners/stale_machines_cleanup_service_spec.rb +++ b/spec/services/ci/runners/stale_managers_cleanup_service_spec.rb @@ -2,22 +2,22 @@ require 'spec_helper' -RSpec.describe Ci::Runners::StaleMachinesCleanupService, feature_category: :runner_fleet do +RSpec.describe Ci::Runners::StaleManagersCleanupService, feature_category: :runner_fleet do let(:service) { described_class.new } - let!(:runner_machine3) { create(:ci_runner_machine, created_at: 6.months.ago, contacted_at: Time.current) } + let!(:runner_manager3) { create(:ci_runner_machine, created_at: 6.months.ago, contacted_at: Time.current) } subject(:response) { service.execute } - context 'with no stale runner machines' do - it 'does not clean any runner machines and returns :success status' do + context 'with no stale runner managers' do + it 'does not clean any runner managers and returns :success status' do expect do expect(response).to be_success - expect(response.payload).to match({ deleted_machines: false }) - end.not_to change { Ci::RunnerMachine.count }.from(1) + expect(response.payload).to match({ deleted_managers: false }) + end.not_to change { Ci::RunnerManager.count }.from(1) end end - context 'with some stale runner machines' do + context 'with some stale runner managers' do before do create(:ci_runner_machine, :stale) create(:ci_runner_machine, :stale, contacted_at: nil) @@ -25,8 +25,8 @@ RSpec.describe Ci::Runners::StaleMachinesCleanupService, feature_category: :runn it 'only leaves non-stale runners' do expect(response).to be_success - expect(response.payload).to match({ deleted_machines: true }) - expect(Ci::RunnerMachine.all).to contain_exactly(runner_machine3) + expect(response.payload).to match({ deleted_managers: true }) + expect(Ci::RunnerManager.all).to contain_exactly(runner_manager3) end context 'with more stale runners than MAX_DELETIONS' do @@ -37,8 +37,8 @@ RSpec.describe Ci::Runners::StaleMachinesCleanupService, feature_category: :runn it 'only leaves non-stale runners' do expect do expect(response).to be_success - expect(response.payload).to match({ deleted_machines: true }) - end.to change { Ci::RunnerMachine.count }.by(-Ci::Runners::StaleMachinesCleanupService::MAX_DELETIONS) + expect(response.payload).to match({ deleted_managers: true }) + end.to change { Ci::RunnerManager.count }.by(-Ci::Runners::StaleManagersCleanupService::MAX_DELETIONS) end end end diff --git a/spec/services/ci/runners/unregister_runner_manager_service_spec.rb b/spec/services/ci/runners/unregister_runner_manager_service_spec.rb new file mode 100644 index 00000000000..8bfda8e2083 --- /dev/null +++ b/spec/services/ci/runners/unregister_runner_manager_service_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Ci::Runners::UnregisterRunnerManagerService, '#execute', feature_category: :runner_fleet do + subject(:execute) { described_class.new(runner, 'some_token', system_id: system_id).execute } + + context 'with runner registered with registration token' do + let!(:runner) { create(:ci_runner, registration_type: :registration_token) } + let(:system_id) { nil } + + it 'does not destroy runner or runner managers' do + expect do + expect(execute).to be_error + end.to not_change { Ci::Runner.count } + .and not_change { Ci::RunnerManager.count } + expect(runner[:errors]).to be_nil + end + end + + context 'with runner created in UI' do + let!(:runner_manager1) { create(:ci_runner_machine, runner: runner, system_xid: 'system_id_1') } + let!(:runner_manager2) { create(:ci_runner_machine, runner: runner, system_xid: 'system_id_2') } + let!(:runner) { create(:ci_runner, registration_type: :authenticated_user) } + + context 'with system_id specified' do + let(:system_id) { runner_manager1.system_xid } + + it 'destroys runner_manager1 and leaves runner', :aggregate_failures do + expect do + expect(execute).to be_success + end.to change { Ci::RunnerManager.count }.by(-1) + .and not_change { Ci::Runner.count } + expect(runner[:errors]).to be_nil + expect(runner.runner_managers).to contain_exactly(runner_manager2) + end + end + + context 'with unknown system_id' do + let(:system_id) { 'unknown_system_id' } + + it 'raises RecordNotFound error', :aggregate_failures do + expect do + execute + end.to raise_error(ActiveRecord::RecordNotFound) + .and not_change { Ci::Runner.count } + .and not_change { Ci::RunnerManager.count } + end + end + + context 'with system_id missing' do + let(:system_id) { nil } + + it 'returns error and leaves runner_manager1', :aggregate_failures do + expect do + expect(execute).to be_error + expect(execute.message).to eq('`system_id` needs to be specified for runners created in the UI.') + end.to not_change { Ci::Runner.count } + .and not_change { Ci::RunnerManager.count } + end + end + end +end diff --git a/spec/services/ci/stuck_builds/drop_pending_service_spec.rb b/spec/services/ci/stuck_builds/drop_pending_service_spec.rb index a452a65829a..6d91f5098eb 100644 --- a/spec/services/ci/stuck_builds/drop_pending_service_spec.rb +++ b/spec/services/ci/stuck_builds/drop_pending_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::StuckBuilds::DropPendingService do +RSpec.describe Ci::StuckBuilds::DropPendingService, feature_category: :runner_fleet do let_it_be(:runner) { create(:ci_runner) } let_it_be(:pipeline) { create(:ci_empty_pipeline) } let_it_be_with_reload(:job) do diff --git a/spec/services/ci/stuck_builds/drop_running_service_spec.rb b/spec/services/ci/stuck_builds/drop_running_service_spec.rb index c1c92c2b8e2..deb807753c2 100644 --- a/spec/services/ci/stuck_builds/drop_running_service_spec.rb +++ b/spec/services/ci/stuck_builds/drop_running_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::StuckBuilds::DropRunningService do +RSpec.describe Ci::StuckBuilds::DropRunningService, feature_category: :runner_fleet do let!(:runner) { create :ci_runner } let!(:job) { create(:ci_build, runner: runner, created_at: created_at, updated_at: updated_at, status: status) } diff --git a/spec/services/ci/stuck_builds/drop_scheduled_service_spec.rb b/spec/services/ci/stuck_builds/drop_scheduled_service_spec.rb index a4f9f97fffc..f2e658c3ae3 100644 --- a/spec/services/ci/stuck_builds/drop_scheduled_service_spec.rb +++ b/spec/services/ci/stuck_builds/drop_scheduled_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::StuckBuilds::DropScheduledService do +RSpec.describe Ci::StuckBuilds::DropScheduledService, feature_category: :runner_fleet do let_it_be(:runner) { create :ci_runner } let!(:job) { create :ci_build, :scheduled, scheduled_at: scheduled_at, runner: runner } diff --git a/spec/services/ci/test_failure_history_service_spec.rb b/spec/services/ci/test_failure_history_service_spec.rb index 10f6c6f5007..e77c6533483 100644 --- a/spec/services/ci/test_failure_history_service_spec.rb +++ b/spec/services/ci/test_failure_history_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do +RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures, feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be_with_reload(:pipeline) do diff --git a/spec/services/ci/track_failed_build_service_spec.rb b/spec/services/ci/track_failed_build_service_spec.rb index 676769d2fc7..23e7cee731d 100644 --- a/spec/services/ci/track_failed_build_service_spec.rb +++ b/spec/services/ci/track_failed_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::TrackFailedBuildService do +RSpec.describe Ci::TrackFailedBuildService, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public) } let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) } diff --git a/spec/services/ci/unlock_artifacts_service_spec.rb b/spec/services/ci/unlock_artifacts_service_spec.rb index c15e1cb2b5d..0d6ac333587 100644 --- a/spec/services/ci/unlock_artifacts_service_spec.rb +++ b/spec/services/ci/unlock_artifacts_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::UnlockArtifactsService do +RSpec.describe Ci::UnlockArtifactsService, feature_category: :continuous_integration do using RSpec::Parameterized::TableSyntax where(:tag) do @@ -24,7 +24,7 @@ RSpec.describe Ci::UnlockArtifactsService do let!(:older_ambiguous_pipeline) { create(:ci_pipeline, :with_persisted_artifacts, ref: ref, tag: !tag, project: project, locked: :artifacts_locked) } let!(:code_coverage_pipeline) { create(:ci_pipeline, :with_coverage_report_artifact, ref: ref, tag: tag, project: project, locked: :artifacts_locked) } let!(:pipeline) { create(:ci_pipeline, :with_persisted_artifacts, ref: ref, tag: tag, project: project, locked: :artifacts_locked) } - let!(:child_pipeline) { create(:ci_pipeline, :with_persisted_artifacts, ref: ref, tag: tag, project: project, locked: :artifacts_locked) } + let!(:child_pipeline) { create(:ci_pipeline, :with_persisted_artifacts, ref: ref, tag: tag, child_of: pipeline, project: project, locked: :artifacts_locked) } let!(:newer_pipeline) { create(:ci_pipeline, :with_persisted_artifacts, ref: ref, tag: tag, project: project, locked: :artifacts_locked) } let!(:other_ref_pipeline) { create(:ci_pipeline, :with_persisted_artifacts, ref: 'other_ref', tag: tag, project: project, locked: :artifacts_locked) } let!(:sources_pipeline) { create(:ci_sources_pipeline, source_job: source_job, source_project: project, pipeline: child_pipeline, project: project) } @@ -120,6 +120,12 @@ RSpec.describe Ci::UnlockArtifactsService do let(:before_pipeline) { pipeline } it 'produces the expected SQL string' do + # To be removed when the ignored column id_convert_to_bigint for ci_pipelines is removed + # see https://gitlab.com/gitlab-org/gitlab/-/issues/397000 + selected_columns = + Ci::Pipeline.column_names.map do |field| + Ci::Pipeline.connection.quote_table_name("#{Ci::Pipeline.table_name}.#{field}") + end.join(', ') expect(subject.squish).to eq <<~SQL.squish UPDATE "ci_pipelines" @@ -140,14 +146,14 @@ RSpec.describe Ci::UnlockArtifactsService do "base_and_descendants" AS ((SELECT - "ci_pipelines".* + #{selected_columns} FROM "ci_pipelines" WHERE "ci_pipelines"."id" = #{before_pipeline.id}) UNION (SELECT - "ci_pipelines".* + #{selected_columns} FROM "ci_pipelines", "base_and_descendants", @@ -201,8 +207,7 @@ RSpec.describe Ci::UnlockArtifactsService do describe '#unlock_job_artifacts_query' do subject { described_class.new(pipeline.project, pipeline.user).unlock_job_artifacts_query(pipeline_ids) } - context 'when running on a ref before a pipeline' do - let(:before_pipeline) { pipeline } + context 'when given a single pipeline ID' do let(:pipeline_ids) { [older_pipeline.id] } it 'produces the expected SQL string' do @@ -226,8 +231,7 @@ RSpec.describe Ci::UnlockArtifactsService do end end - context 'when running on just the ref' do - let(:before_pipeline) { nil } + context 'when given multiple pipeline IDs' do let(:pipeline_ids) { [older_pipeline.id, newer_pipeline.id, pipeline.id] } it 'produces the expected SQL string' do diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb index dd26339831c..4fd4492278d 100644 --- a/spec/services/ci/update_build_queue_service_spec.rb +++ b/spec/services/ci/update_build_queue_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::UpdateBuildQueueService do +RSpec.describe Ci::UpdateBuildQueueService, feature_category: :continuous_integration do let(:project) { create(:project, :repository) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } diff --git a/spec/services/ci/update_instance_variables_service_spec.rb b/spec/services/ci/update_instance_variables_service_spec.rb index f235d006e34..889f49eca5a 100644 --- a/spec/services/ci/update_instance_variables_service_spec.rb +++ b/spec/services/ci/update_instance_variables_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::UpdateInstanceVariablesService do +RSpec.describe Ci::UpdateInstanceVariablesService, feature_category: :secrets_management do let(:params) { { variables_attributes: variables_attributes } } subject { described_class.new(params) } diff --git a/spec/services/ci/update_pending_build_service_spec.rb b/spec/services/ci/update_pending_build_service_spec.rb index e49b22299f0..abf31dd5184 100644 --- a/spec/services/ci/update_pending_build_service_spec.rb +++ b/spec/services/ci/update_pending_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::UpdatePendingBuildService do +RSpec.describe Ci::UpdatePendingBuildService, feature_category: :continuous_integration do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, namespace: group) } let_it_be_with_reload(:pending_build_1) { create(:ci_pending_build, project: project, instance_runners_enabled: false) } diff --git a/spec/services/clusters/agent_tokens/create_service_spec.rb b/spec/services/clusters/agent_tokens/create_service_spec.rb index dc7abd1504b..803bd947629 100644 --- a/spec/services/clusters/agent_tokens/create_service_spec.rb +++ b/spec/services/clusters/agent_tokens/create_service_spec.rb @@ -2,14 +2,14 @@ require 'spec_helper' -RSpec.describe Clusters::AgentTokens::CreateService do - subject(:service) { described_class.new(container: project, current_user: user, params: params) } +RSpec.describe Clusters::AgentTokens::CreateService, feature_category: :deployment_management do + subject(:service) { described_class.new(agent: cluster_agent, current_user: user, params: params) } let_it_be(:user) { create(:user) } let(:cluster_agent) { create(:cluster_agent) } let(:project) { cluster_agent.project } - let(:params) { { agent_id: cluster_agent.id, description: 'token description', name: 'token name' } } + let(:params) { { description: 'token description', name: 'token name' } } describe '#execute' do subject { service.execute } @@ -75,7 +75,7 @@ RSpec.describe Clusters::AgentTokens::CreateService do it 'returns validation errors', :aggregate_failures do expect(subject.status).to eq(:error) - expect(subject.message).to eq(["Agent must exist", "Name can't be blank"]) + expect(subject.message).to eq(["Name can't be blank"]) end end end diff --git a/spec/services/clusters/agent_tokens/revoke_service_spec.rb b/spec/services/clusters/agent_tokens/revoke_service_spec.rb new file mode 100644 index 00000000000..a1537658723 --- /dev/null +++ b/spec/services/clusters/agent_tokens/revoke_service_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Clusters::AgentTokens::RevokeService, feature_category: :deployment_management do + describe '#execute' do + subject { described_class.new(token: agent_token, current_user: user).execute } + + let(:agent) { create(:cluster_agent) } + let(:agent_token) { create(:cluster_agent_token, agent: agent) } + let(:project) { agent.project } + let(:user) { agent.created_by_user } + + before do + project.add_maintainer(user) + end + + context 'when user is authorized' do + before do + project.add_maintainer(user) + end + + context 'when user revokes agent token' do + it 'succeeds' do + subject + + expect(agent_token.revoked?).to be true + end + + it 'creates an activity event' do + expect { subject }.to change { ::Clusters::Agents::ActivityEvent.count }.by(1) + + event = agent.activity_events.last + + expect(event).to have_attributes( + kind: 'token_revoked', + level: 'info', + recorded_at: agent_token.reload.updated_at, + user: user, + agent_token: agent_token + ) + end + end + + context 'when there is a validation failure' do + before do + agent_token.name = '' # make the record invalid, as we require a name to be present + end + + it 'fails without raising an error', :aggregate_failures do + expect(subject[:status]).to eq(:error) + expect(subject[:message]).to eq(["Name can't be blank"]) + end + + it 'does not create an activity event' do + expect { subject }.not_to change { ::Clusters::Agents::ActivityEvent.count } + end + end + end + + context 'when user is not authorized' do + let(:user) { create(:user) } + + before do + project.add_guest(user) + end + + context 'when user attempts to revoke agent token' do + it 'fails' do + subject + + expect(agent_token.revoked?).to be false + end + end + end + end +end diff --git a/spec/services/clusters/agent_tokens/track_usage_service_spec.rb b/spec/services/clusters/agent_tokens/track_usage_service_spec.rb index 3350b15a5ce..6bea8afcc80 100644 --- a/spec/services/clusters/agent_tokens/track_usage_service_spec.rb +++ b/spec/services/clusters/agent_tokens/track_usage_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::AgentTokens::TrackUsageService do +RSpec.describe Clusters::AgentTokens::TrackUsageService, feature_category: :deployment_management do let_it_be(:agent) { create(:cluster_agent) } describe '#execute', :clean_gitlab_redis_cache do diff --git a/spec/services/clusters/agents/filter_authorizations_service_spec.rb b/spec/services/clusters/agents/authorizations/ci_access/filter_service_spec.rb index 62cff405d0c..45443cfd887 100644 --- a/spec/services/clusters/agents/filter_authorizations_service_spec.rb +++ b/spec/services/clusters/agents/authorizations/ci_access/filter_service_spec.rb @@ -2,16 +2,16 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::FilterAuthorizationsService, feature_category: :continuous_integration do +RSpec.describe Clusters::Agents::Authorizations::CiAccess::FilterService, feature_category: :continuous_integration do describe '#execute' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } let(:agent_authorizations_without_env) do [ - build(:agent_project_authorization, project: project, agent: build(:cluster_agent, project: project)), - build(:agent_group_authorization, group: group, agent: build(:cluster_agent, project: project)), - ::Clusters::Agents::ImplicitAuthorization.new(agent: build(:cluster_agent, project: project)) + build(:agent_ci_access_project_authorization, project: project, agent: build(:cluster_agent, project: project)), + build(:agent_ci_access_group_authorization, group: group, agent: build(:cluster_agent, project: project)), + ::Clusters::Agents::Authorizations::CiAccess::ImplicitAuthorization.new(agent: build(:cluster_agent, project: project)) ] end @@ -31,13 +31,13 @@ RSpec.describe Clusters::Agents::FilterAuthorizationsService, feature_category: let(:agent_authorizations_with_env) do [ build( - :agent_project_authorization, + :agent_ci_access_project_authorization, project: project, agent: build(:cluster_agent, project: project), environments: ['staging', 'review/*', 'production'] ), build( - :agent_group_authorization, + :agent_ci_access_group_authorization, group: group, agent: build(:cluster_agent, project: project), environments: ['staging', 'review/*', 'production'] @@ -48,13 +48,13 @@ RSpec.describe Clusters::Agents::FilterAuthorizationsService, feature_category: let(:agent_authorizations_with_different_env) do [ build( - :agent_project_authorization, + :agent_ci_access_project_authorization, project: project, agent: build(:cluster_agent, project: project), environments: ['staging'] ), build( - :agent_group_authorization, + :agent_ci_access_group_authorization, group: group, agent: build(:cluster_agent, project: project), environments: ['staging'] diff --git a/spec/services/clusters/agents/refresh_authorization_service_spec.rb b/spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb index 51c054ddc98..c12592cc071 100644 --- a/spec/services/clusters/agents/refresh_authorization_service_spec.rb +++ b/spec/services/clusters/agents/authorizations/ci_access/refresh_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: :kubernetes_management do +RSpec.describe Clusters::Agents::Authorizations::CiAccess::RefreshService, feature_category: :deployment_management do describe '#execute' do let_it_be(:root_ancestor) { create(:group) } @@ -39,11 +39,11 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: before do default_config = { default_namespace: 'default' } - agent.group_authorizations.create!(group: removed_group, config: default_config) - agent.group_authorizations.create!(group: modified_group, config: default_config) + agent.ci_access_group_authorizations.create!(group: removed_group, config: default_config) + agent.ci_access_group_authorizations.create!(group: modified_group, config: default_config) - agent.project_authorizations.create!(project: removed_project, config: default_config) - agent.project_authorizations.create!(project: modified_project, config: default_config) + agent.ci_access_project_authorizations.create!(project: removed_project, config: default_config) + agent.ci_access_project_authorizations.create!(project: modified_project, config: default_config) end shared_examples 'removing authorization' do @@ -78,12 +78,12 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: describe 'group authorization' do it 'refreshes authorizations for the agent' do expect(subject).to be_truthy - expect(agent.authorized_groups).to contain_exactly(added_group, modified_group) + expect(agent.ci_access_authorized_groups).to contain_exactly(added_group, modified_group) - added_authorization = agent.group_authorizations.find_by(group: added_group) + added_authorization = agent.ci_access_group_authorizations.find_by(group: added_group) expect(added_authorization.config).to eq({ 'default_namespace' => 'default' }) - modified_authorization = agent.group_authorizations.find_by(group: modified_group) + modified_authorization = agent.ci_access_group_authorizations.find_by(group: modified_group) expect(modified_authorization.config).to eq({ 'default_namespace' => 'new-namespace' }) end @@ -94,24 +94,24 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: it 'authorizes groups up to the limit' do expect(subject).to be_truthy - expect(agent.authorized_groups).to contain_exactly(added_group) + expect(agent.ci_access_authorized_groups).to contain_exactly(added_group) end end include_examples 'removing authorization' do - let(:authorizations) { agent.authorized_groups } + let(:authorizations) { agent.ci_access_authorized_groups } end end describe 'project authorization' do it 'refreshes authorizations for the agent' do expect(subject).to be_truthy - expect(agent.authorized_projects).to contain_exactly(added_project, modified_project) + expect(agent.ci_access_authorized_projects).to contain_exactly(added_project, modified_project) - added_authorization = agent.project_authorizations.find_by(project: added_project) + added_authorization = agent.ci_access_project_authorizations.find_by(project: added_project) expect(added_authorization.config).to eq({ 'default_namespace' => 'default' }) - modified_authorization = agent.project_authorizations.find_by(project: modified_project) + modified_authorization = agent.ci_access_project_authorizations.find_by(project: modified_project) expect(modified_authorization.config).to eq({ 'default_namespace' => 'new-namespace' }) end @@ -121,7 +121,7 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: it 'creates an authorization record for the project' do expect(subject).to be_truthy - expect(agent.authorized_projects).to contain_exactly(added_project) + expect(agent.ci_access_authorized_projects).to contain_exactly(added_project) end end @@ -131,7 +131,7 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: it 'creates an authorization record for the project' do expect(subject).to be_truthy - expect(agent.authorized_projects).to contain_exactly(added_project) + expect(agent.ci_access_authorized_projects).to contain_exactly(added_project) end end @@ -142,12 +142,12 @@ RSpec.describe Clusters::Agents::RefreshAuthorizationService, feature_category: it 'authorizes projects up to the limit' do expect(subject).to be_truthy - expect(agent.authorized_projects).to contain_exactly(added_project) + expect(agent.ci_access_authorized_projects).to contain_exactly(added_project) end end include_examples 'removing authorization' do - let(:authorizations) { agent.authorized_projects } + let(:authorizations) { agent.ci_access_authorized_projects } end end end diff --git a/spec/services/clusters/agents/authorizations/user_access/refresh_service_spec.rb b/spec/services/clusters/agents/authorizations/user_access/refresh_service_spec.rb new file mode 100644 index 00000000000..da546ca44a9 --- /dev/null +++ b/spec/services/clusters/agents/authorizations/user_access/refresh_service_spec.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Clusters::Agents::Authorizations::UserAccess::RefreshService, feature_category: :deployment_management do + describe '#execute' do + let_it_be(:root_ancestor) { create(:group) } + let_it_be(:agent_management_project) { create(:project, namespace: root_ancestor) } + let_it_be(:group_1) { create(:group, path: 'group-path-with-UPPERCASE', parent: root_ancestor) } + let_it_be(:group_2) { create(:group, parent: root_ancestor) } + let_it_be(:project_1) { create(:project, path: 'project-path-with-UPPERCASE', namespace: root_ancestor) } + let_it_be(:project_2) { create(:project, namespace: root_ancestor) } + + let(:agent) { create(:cluster_agent, project: agent_management_project) } + + let(:config) do + { + user_access: { + groups: [ + { id: group_2.full_path } + ], + projects: [ + { id: project_2.full_path } + ] + } + }.deep_merge(extra_config).deep_stringify_keys + end + + let(:extra_config) { {} } + + subject { described_class.new(agent, config: config).execute } + + before do + agent.user_access_group_authorizations.create!(group: group_1, config: {}) + agent.user_access_project_authorizations.create!(project: project_1, config: {}) + end + + shared_examples 'removing authorization' do + context 'when config contains no groups or projects' do + let(:config) { {} } + + it 'removes all authorizations' do + expect(subject).to be_truthy + expect(authorizations).to be_empty + end + end + + context 'when config contains groups or projects outside of the configuration project hierarchy' do + let_it_be(:agent_management_project) { create(:project, namespace: create(:group)) } + + it 'removes all authorizations' do + expect(subject).to be_truthy + expect(authorizations).to be_empty + end + end + + context 'when configuration project does not belong to a group' do + let_it_be(:agent_management_project) { create(:project) } + + it 'removes all authorizations' do + expect(subject).to be_truthy + expect(authorizations).to be_empty + end + end + end + + describe 'group authorization' do + it 'refreshes authorizations for the agent' do + expect(subject).to be_truthy + expect(agent.user_access_authorized_groups).to contain_exactly(group_2) + + added_authorization = agent.user_access_group_authorizations.find_by(group: group_2) + expect(added_authorization.config).to eq({}) + end + + context 'when config contains "access_as" keyword' do + let(:extra_config) do + { + user_access: { + access_as: { + agent: {} + } + } + } + end + + it 'refreshes authorizations for the agent' do + expect(subject).to be_truthy + expect(agent.user_access_authorized_groups).to contain_exactly(group_2) + + added_authorization = agent.user_access_group_authorizations.find_by(group: group_2) + expect(added_authorization.config).to eq({ 'access_as' => { 'agent' => {} } }) + end + end + + context 'when config contains too many groups' do + before do + stub_const("#{described_class}::AUTHORIZED_ENTITY_LIMIT", 0) + end + + it 'authorizes groups up to the limit' do + expect(subject).to be_truthy + expect(agent.user_access_authorized_groups).to be_empty + end + end + + include_examples 'removing authorization' do + let(:authorizations) { agent.user_access_authorized_groups } + end + end + + describe 'project authorization' do + it 'refreshes authorizations for the agent' do + expect(subject).to be_truthy + expect(agent.user_access_authorized_projects).to contain_exactly(project_2) + + added_authorization = agent.user_access_project_authorizations.find_by(project: project_2) + expect(added_authorization.config).to eq({}) + end + + context 'when config contains "access_as" keyword' do + let(:extra_config) do + { + user_access: { + access_as: { + agent: {} + } + } + } + end + + it 'refreshes authorizations for the agent' do + expect(subject).to be_truthy + expect(agent.user_access_authorized_projects).to contain_exactly(project_2) + + added_authorization = agent.user_access_project_authorizations.find_by(project: project_2) + expect(added_authorization.config).to eq({ 'access_as' => { 'agent' => {} } }) + end + end + + context 'when project belongs to a user namespace, and is in the same namespace as the agent' do + let_it_be(:root_ancestor) { create(:namespace) } + let_it_be(:agent_management_project) { create(:project, namespace: root_ancestor) } + let_it_be(:project_1) { create(:project, path: 'project-path-with-UPPERCASE', namespace: root_ancestor) } + let_it_be(:project_2) { create(:project, namespace: root_ancestor) } + + it 'creates an authorization record for the project' do + expect(subject).to be_truthy + expect(agent.user_access_authorized_projects).to contain_exactly(project_2) + end + end + + context 'when project belongs to a user namespace, and is authorizing itself' do + let_it_be(:root_ancestor) { create(:namespace) } + let_it_be(:agent_management_project) { create(:project, namespace: root_ancestor) } + let_it_be(:project_1) { create(:project, path: 'project-path-with-UPPERCASE', namespace: root_ancestor) } + let_it_be(:project_2) { agent_management_project } + + it 'creates an authorization record for the project' do + expect(subject).to be_truthy + expect(agent.user_access_authorized_projects).to contain_exactly(project_2) + end + end + + context 'when config contains too many projects' do + before do + stub_const("#{described_class}::AUTHORIZED_ENTITY_LIMIT", 0) + end + + it 'authorizes projects up to the limit' do + expect(subject).to be_truthy + expect(agent.user_access_authorized_projects).to be_empty + end + end + + include_examples 'removing authorization' do + let(:authorizations) { agent.user_access_authorized_projects } + 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 new file mode 100644 index 00000000000..2d6c79c5cb3 --- /dev/null +++ b/spec/services/clusters/agents/authorize_proxy_user_service_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Clusters::Agents::AuthorizeProxyUserService, feature_category: :deployment_management do + subject(:service_response) { service.execute } + + let(:service) { described_class.new(user, agent) } + let(:user) { create(:user) } + + let_it_be(:organization) { create(:group) } + let_it_be(:configuration_project) { create(:project, group: organization) } + let_it_be(:agent) { create(:cluster_agent, name: 'the-agent', project: configuration_project) } + let_it_be(:deployment_project) { create(:project, group: organization) } + let_it_be(:deployment_group) { create(:group, parent: organization) } + + let(:user_access_config) do + { + 'user_access' => { + 'access_as' => { 'agent' => {} }, + 'projects' => [{ 'id' => deployment_project.full_path }], + 'groups' => [{ 'id' => deployment_group.full_path }] + } + } + end + + before do + Clusters::Agents::Authorizations::UserAccess::RefreshService.new(agent, config: user_access_config).execute + end + + 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 + end + + context 'when user is member of an authorized group' do + it 'authorizes developers', :aggregate_failures do + deployment_group.add_member(user, :developer) + expect(service_response).to be_success + expect(service_response.payload[:user]).to include(id: user.id, username: user.username) + expect(service_response.payload[:agent]).to include(id: agent.id, config_project: { id: agent.project.id }) + end + + it 'does not authorize reporters', :aggregate_failures do + deployment_group.add_member(user, :reporter) + expect(service_response).to be_error + expect(service_response.reason).to eq :forbidden + end + end + + context 'when user is member of an authorized project' do + it 'authorizes developers', :aggregate_failures do + deployment_project.add_member(user, :developer) + expect(service_response).to be_success + expect(service_response.payload[:user]).to include(id: user.id, username: user.username) + expect(service_response.payload[:agent]).to include(id: agent.id, config_project: { id: agent.project.id }) + end + + it 'does not authorize reporters', :aggregate_failures do + deployment_project.add_member(user, :reporter) + expect(service_response).to be_error + expect(service_response.reason).to eq :forbidden + end + end +end diff --git a/spec/services/clusters/agents/create_activity_event_service_spec.rb b/spec/services/clusters/agents/create_activity_event_service_spec.rb index 7a8f0e16d60..0d784bb69c7 100644 --- a/spec/services/clusters/agents/create_activity_event_service_spec.rb +++ b/spec/services/clusters/agents/create_activity_event_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::CreateActivityEventService do +RSpec.describe Clusters::Agents::CreateActivityEventService, feature_category: :deployment_management do let_it_be(:agent) { create(:cluster_agent) } let_it_be(:token) { create(:cluster_agent_token, agent: agent) } let_it_be(:user) { create(:user) } @@ -40,5 +40,16 @@ RSpec.describe Clusters::Agents::CreateActivityEventService do subject end + + context 'when activity event creation fails' do + let(:params) { {} } + + it 'tracks the exception without raising' do + expect(Gitlab::ErrorTracking).to receive(:track_exception) + .with(instance_of(ActiveRecord::RecordInvalid), agent_id: agent.id) + + subject + end + end end end diff --git a/spec/services/clusters/agents/create_service_spec.rb b/spec/services/clusters/agents/create_service_spec.rb index 2b3bbcae13c..85607fcdf3a 100644 --- a/spec/services/clusters/agents/create_service_spec.rb +++ b/spec/services/clusters/agents/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::CreateService do +RSpec.describe Clusters::Agents::CreateService, feature_category: :deployment_management do subject(:service) { described_class.new(project, user) } let(:project) { create(:project, :public, :repository) } diff --git a/spec/services/clusters/agents/delete_expired_events_service_spec.rb b/spec/services/clusters/agents/delete_expired_events_service_spec.rb index 3dc166f54eb..7dc9c280ab4 100644 --- a/spec/services/clusters/agents/delete_expired_events_service_spec.rb +++ b/spec/services/clusters/agents/delete_expired_events_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::DeleteExpiredEventsService do +RSpec.describe Clusters::Agents::DeleteExpiredEventsService, feature_category: :deployment_management do let_it_be(:agent) { create(:cluster_agent) } describe '#execute' do diff --git a/spec/services/clusters/agents/delete_service_spec.rb b/spec/services/clusters/agents/delete_service_spec.rb index abe1bdaab27..febbb7ba5c8 100644 --- a/spec/services/clusters/agents/delete_service_spec.rb +++ b/spec/services/clusters/agents/delete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::DeleteService do +RSpec.describe Clusters::Agents::DeleteService, feature_category: :deployment_management do subject(:service) { described_class.new(container: project, current_user: user) } let(:cluster_agent) { create(:cluster_agent) } diff --git a/spec/services/clusters/build_kubernetes_namespace_service_spec.rb b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb index 4ee933374f6..fea17495914 100644 --- a/spec/services/clusters/build_kubernetes_namespace_service_spec.rb +++ b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::BuildKubernetesNamespaceService do +RSpec.describe Clusters::BuildKubernetesNamespaceService, feature_category: :deployment_management do let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:environment) { create(:environment) } let(:project) { environment.project } diff --git a/spec/services/clusters/build_service_spec.rb b/spec/services/clusters/build_service_spec.rb index c7a64435d3b..909d3f58c48 100644 --- a/spec/services/clusters/build_service_spec.rb +++ b/spec/services/clusters/build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::BuildService do +RSpec.describe Clusters::BuildService, feature_category: :deployment_management do describe '#execute' do subject { described_class.new(cluster_subject).execute } diff --git a/spec/services/clusters/cleanup/project_namespace_service_spec.rb b/spec/services/clusters/cleanup/project_namespace_service_spec.rb index 8d3ae217a9f..34311d6e830 100644 --- a/spec/services/clusters/cleanup/project_namespace_service_spec.rb +++ b/spec/services/clusters/cleanup/project_namespace_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Cleanup::ProjectNamespaceService do +RSpec.describe Clusters::Cleanup::ProjectNamespaceService, feature_category: :deployment_management do describe '#execute' do subject { service.execute } diff --git a/spec/services/clusters/cleanup/service_account_service_spec.rb b/spec/services/clusters/cleanup/service_account_service_spec.rb index 769762237f9..f5a3c2e8eb1 100644 --- a/spec/services/clusters/cleanup/service_account_service_spec.rb +++ b/spec/services/clusters/cleanup/service_account_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Cleanup::ServiceAccountService do +RSpec.describe Clusters::Cleanup::ServiceAccountService, feature_category: :deployment_management do describe '#execute' do subject { service.execute } @@ -55,14 +55,16 @@ RSpec.describe Clusters::Cleanup::ServiceAccountService do context 'when there is a Kubeclient::HttpError' do ['Unauthorized', 'forbidden', 'Certificate verify Failed'].each do |message| - before do - allow(kubeclient_instance_double) - .to receive(:delete_service_account) - .and_raise(Kubeclient::HttpError.new(401, message, nil)) - end + context "with error:#{message}" do + before do + allow(kubeclient_instance_double) + .to receive(:delete_service_account) + .and_raise(Kubeclient::HttpError.new(401, message, nil)) + end - it 'destroys cluster' do - expect { subject }.to change { Clusters::Cluster.where(id: cluster.id).exists? }.from(true).to(false) + it 'destroys cluster' do + expect { subject }.to change { Clusters::Cluster.where(id: cluster.id).exists? }.from(true).to(false) + end end end end diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb index 95f10cdbd80..e130f713cb2 100644 --- a/spec/services/clusters/create_service_spec.rb +++ b/spec/services/clusters/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::CreateService do +RSpec.describe Clusters::CreateService, feature_category: :deployment_management do let(:access_token) { 'xxx' } let(:project) { create(:project) } let(:user) { create(:user) } @@ -50,7 +50,7 @@ RSpec.describe Clusters::CreateService do end context 'when project has a cluster' do - include_context 'valid cluster create params' + include_context 'with valid cluster create params' let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) } it 'creates another cluster' do diff --git a/spec/services/clusters/destroy_service_spec.rb b/spec/services/clusters/destroy_service_spec.rb index dc600c9e830..dd3e24d0e12 100644 --- a/spec/services/clusters/destroy_service_spec.rb +++ b/spec/services/clusters/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::DestroyService do +RSpec.describe Clusters::DestroyService, feature_category: :deployment_management do describe '#execute' do subject { described_class.new(cluster.user, params).execute(cluster) } diff --git a/spec/services/clusters/integrations/create_service_spec.rb b/spec/services/clusters/integrations/create_service_spec.rb index 9104e07504d..b716e4f4651 100644 --- a/spec/services/clusters/integrations/create_service_spec.rb +++ b/spec/services/clusters/integrations/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Integrations::CreateService, '#execute' do +RSpec.describe Clusters::Integrations::CreateService, '#execute', feature_category: :deployment_management do let_it_be(:project) { create(:project) } let_it_be_with_reload(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } diff --git a/spec/services/clusters/integrations/prometheus_health_check_service_spec.rb b/spec/services/clusters/integrations/prometheus_health_check_service_spec.rb index 526462931a6..9390d4b368b 100644 --- a/spec/services/clusters/integrations/prometheus_health_check_service_spec.rb +++ b/spec/services/clusters/integrations/prometheus_health_check_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Integrations::PrometheusHealthCheckService, '#execute' do +RSpec.describe Clusters::Integrations::PrometheusHealthCheckService, '#execute', feature_category: :deployment_management do let(:service) { described_class.new(cluster) } subject { service.execute } diff --git a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb index 90956e7b4ea..48941792c4b 100644 --- a/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb +++ b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do +RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute', feature_category: :deployment_management do include KubernetesHelpers let(:cluster) { create(:cluster, :project, :provided_by_gcp) } @@ -11,7 +11,7 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' let(:project) { cluster.project } let(:environment) { create(:environment, project: project) } let(:cluster_project) { cluster.cluster_project } - let(:namespace) { "#{project.name}-#{project.id}-#{environment.slug}" } + let(:namespace) { "#{project.path}-#{project.id}-#{environment.slug}" } subject do described_class.new( diff --git a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb index 37478a0bcd9..ab0c5691b06 100644 --- a/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb +++ b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do +RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService, feature_category: :deployment_management do include KubernetesHelpers let(:api_url) { 'http://111.111.111.111' } diff --git a/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb index 03c402fb066..439dc37e684 100644 --- a/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb +++ b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService do +RSpec.describe Clusters::Kubernetes::FetchKubernetesTokenService, feature_category: :deployment_management do include KubernetesHelpers describe '#execute' do diff --git a/spec/services/clusters/kubernetes_spec.rb b/spec/services/clusters/kubernetes_spec.rb index 12af63890fc..cd430f81a65 100644 --- a/spec/services/clusters/kubernetes_spec.rb +++ b/spec/services/clusters/kubernetes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Kubernetes do +RSpec.describe Clusters::Kubernetes, feature_category: :deployment_management do it { is_expected.to be_const_defined(:GITLAB_SERVICE_ACCOUNT_NAME) } it { is_expected.to be_const_defined(:GITLAB_SERVICE_ACCOUNT_NAMESPACE) } it { is_expected.to be_const_defined(:GITLAB_ADMIN_TOKEN_NAME) } diff --git a/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb b/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb index a21c378d3d1..46032de600d 100644 --- a/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb +++ b/spec/services/clusters/management/validate_management_project_permissions_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Management::ValidateManagementProjectPermissionsService do +RSpec.describe Clusters::Management::ValidateManagementProjectPermissionsService, feature_category: :deployment_management do describe '#execute' do subject { described_class.new(user).execute(cluster, management_project_id) } diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb index 9aead97f41c..cc759407376 100644 --- a/spec/services/clusters/update_service_spec.rb +++ b/spec/services/clusters/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::UpdateService do +RSpec.describe Clusters::UpdateService, feature_category: :deployment_management do include KubernetesHelpers describe '#execute' do diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb index dce8d4f80f2..ab53bcf8657 100644 --- a/spec/services/cohorts_service_spec.rb +++ b/spec/services/cohorts_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe CohortsService do +RSpec.describe CohortsService, feature_category: :shared do describe '#execute' do def month_start(months_ago) months_ago.months.ago.beginning_of_month.to_date diff --git a/spec/services/commits/cherry_pick_service_spec.rb b/spec/services/commits/cherry_pick_service_spec.rb index 2565e17ac90..880ebea1c09 100644 --- a/spec/services/commits/cherry_pick_service_spec.rb +++ b/spec/services/commits/cherry_pick_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Commits::CherryPickService do +RSpec.describe Commits::CherryPickService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } # * ddd0f15ae83993f5cb66a927a28673882e99100b (HEAD -> master, origin/master, origin/HEAD) Merge branch 'po-fix-test-en # |\ diff --git a/spec/services/commits/commit_patch_service_spec.rb b/spec/services/commits/commit_patch_service_spec.rb index edd0918e488..a9d61be23be 100644 --- a/spec/services/commits/commit_patch_service_spec.rb +++ b/spec/services/commits/commit_patch_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Commits::CommitPatchService do +RSpec.describe Commits::CommitPatchService, feature_category: :source_code_management do describe '#execute' do let(:patches) do patches_folder = Rails.root.join('spec/fixtures/patchfiles') diff --git a/spec/services/commits/tag_service_spec.rb b/spec/services/commits/tag_service_spec.rb index dd742ebe469..25aa84276c3 100644 --- a/spec/services/commits/tag_service_spec.rb +++ b/spec/services/commits/tag_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Commits::TagService do +RSpec.describe Commits::TagService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } let(:user) { create(:user) } diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb index e96a7f2f4f4..6757fbdf5d4 100644 --- a/spec/services/compare_service_spec.rb +++ b/spec/services/compare_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe CompareService do +RSpec.describe CompareService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:service) { described_class.new(project, 'feature') } diff --git a/spec/services/concerns/audit_event_save_type_spec.rb b/spec/services/concerns/audit_event_save_type_spec.rb index fbaebd9f85c..a89eb513d27 100644 --- a/spec/services/concerns/audit_event_save_type_spec.rb +++ b/spec/services/concerns/audit_event_save_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AuditEventSaveType do +RSpec.describe AuditEventSaveType, feature_category: :audit_events do subject(:target) { Object.new.extend(described_class) } describe '#should_save_database? and #should_save_stream?' do diff --git a/spec/services/concerns/exclusive_lease_guard_spec.rb b/spec/services/concerns/exclusive_lease_guard_spec.rb index 6a2aa0a377b..ca8bff4ecc4 100644 --- a/spec/services/concerns/exclusive_lease_guard_spec.rb +++ b/spec/services/concerns/exclusive_lease_guard_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ExclusiveLeaseGuard, :clean_gitlab_redis_shared_state do +RSpec.describe ExclusiveLeaseGuard, :clean_gitlab_redis_shared_state, feature_category: :shared do subject :subject_class do Class.new do include ExclusiveLeaseGuard @@ -49,11 +49,49 @@ RSpec.describe ExclusiveLeaseGuard, :clean_gitlab_redis_shared_state do subject.exclusive_lease.cancel end - it 'does not call internal_method but logs error', :aggregate_failures do - expect(subject).not_to receive(:internal_method) - expect(Gitlab::AppLogger).to receive(:error).with("Cannot obtain an exclusive lease for #{subject.lease_key}. There must be another instance already in execution.") + context 'when the class does not override lease_taken_log_level' do + it 'does not call internal_method but logs error', :aggregate_failures do + expect(subject).not_to receive(:internal_method) + expect(Gitlab::AppJsonLogger).to receive(:error).with({ message: "Cannot obtain an exclusive lease. There must be another instance already in execution.", lease_key: 'exclusive_lease_guard_test_class', class_name: 'ExclusiveLeaseGuardTestClass', lease_timeout: 1.second }) - subject.call + subject.call + end + end + + context 'when the class overrides lease_taken_log_level to return :info' do + subject :overwritten_subject_class do + Class.new(subject_class) do + def lease_taken_log_level + :info + end + end + end + + let(:subject) { overwritten_subject_class.new } + + it 'logs info', :aggregate_failures do + expect(Gitlab::AppJsonLogger).to receive(:info).with({ message: "Cannot obtain an exclusive lease. There must be another instance already in execution.", lease_key: 'exclusive_lease_guard_test_class', class_name: 'ExclusiveLeaseGuardTestClass', lease_timeout: 1.second }) + + subject.call + end + end + + context 'when the class overrides lease_taken_log_level to return :debug' do + subject :overwritten_subject_class do + Class.new(subject_class) do + def lease_taken_log_level + :debug + end + end + end + + let(:subject) { overwritten_subject_class.new } + + it 'logs debug', :aggregate_failures do + expect(Gitlab::AppJsonLogger).to receive(:debug).with({ message: "Cannot obtain an exclusive lease. There must be another instance already in execution.", lease_key: 'exclusive_lease_guard_test_class', class_name: 'ExclusiveLeaseGuardTestClass', lease_timeout: 1.second }) + + subject.call + end end end diff --git a/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb index 5b1e8fca31b..c6ee5b78c13 100644 --- a/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb +++ b/spec/services/concerns/merge_requests/assigns_merge_params_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::AssignsMergeParams do +RSpec.describe MergeRequests::AssignsMergeParams, feature_category: :code_review_workflow do it 'raises an error when used from an instance that does not respond to #current_user' do define_class = -> { Class.new { include MergeRequests::AssignsMergeParams }.new } diff --git a/spec/services/concerns/rate_limited_service_spec.rb b/spec/services/concerns/rate_limited_service_spec.rb index d913cd17067..2172c756ecf 100644 --- a/spec/services/concerns/rate_limited_service_spec.rb +++ b/spec/services/concerns/rate_limited_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe RateLimitedService do +RSpec.describe RateLimitedService, feature_category: :rate_limiting do let(:key) { :issues_create } let(:scope) { [:container, :current_user] } let(:opts) { { scope: scope, users_allowlist: -> { [User.support_bot.username] } } } diff --git a/spec/services/container_expiration_policies/cleanup_service_spec.rb b/spec/services/container_expiration_policies/cleanup_service_spec.rb index 6e1be7271e1..4663944f0b9 100644 --- a/spec/services/container_expiration_policies/cleanup_service_spec.rb +++ b/spec/services/container_expiration_policies/cleanup_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ContainerExpirationPolicies::CleanupService do +RSpec.describe ContainerExpirationPolicies::CleanupService, feature_category: :container_registry do let_it_be(:repository, reload: true) { create(:container_repository, expiration_policy_started_at: 30.minutes.ago) } let_it_be(:project) { repository.project } @@ -190,6 +190,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do context 'with only the current repository started_at before the policy next_run_at' do before do + repository.update!(expiration_policy_started_at: policy.next_run_at + 9.minutes) repository2.update!(expiration_policy_started_at: policy.next_run_at + 10.minutes) repository3.update!(expiration_policy_started_at: policy.next_run_at + 12.minutes) end diff --git a/spec/services/container_expiration_policies/update_service_spec.rb b/spec/services/container_expiration_policies/update_service_spec.rb index 7d949b77de7..992240201e0 100644 --- a/spec/services/container_expiration_policies/update_service_spec.rb +++ b/spec/services/container_expiration_policies/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ContainerExpirationPolicies::UpdateService do +RSpec.describe ContainerExpirationPolicies::UpdateService, feature_category: :container_registry do using RSpec::Parameterized::TableSyntax let_it_be(:project, reload: true) { create(:project) } diff --git a/spec/services/customer_relations/contacts/create_service_spec.rb b/spec/services/customer_relations/contacts/create_service_spec.rb index db6cce799fe..91aa51385e7 100644 --- a/spec/services/customer_relations/contacts/create_service_spec.rb +++ b/spec/services/customer_relations/contacts/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe CustomerRelations::Contacts::CreateService do +RSpec.describe CustomerRelations::Contacts::CreateService, feature_category: :service_desk do describe '#execute' do let_it_be(:user) { create(:user) } let_it_be(:not_found_or_does_not_belong) { 'The specified organization was not found or does not belong to this group' } @@ -50,8 +50,8 @@ RSpec.describe CustomerRelations::Contacts::CreateService do end it 'returns an error when the organization belongs to a different group' do - organization = create(:organization) - params[:organization_id] = organization.id + crm_organization = create(:crm_organization) + params[:organization_id] = crm_organization.id expect(response).to be_error expect(response.message).to match_array([not_found_or_does_not_belong]) diff --git a/spec/services/customer_relations/contacts/update_service_spec.rb b/spec/services/customer_relations/contacts/update_service_spec.rb index 729fdc2058b..105b5bad5f7 100644 --- a/spec/services/customer_relations/contacts/update_service_spec.rb +++ b/spec/services/customer_relations/contacts/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe CustomerRelations::Contacts::UpdateService do +RSpec.describe CustomerRelations::Contacts::UpdateService, feature_category: :service_desk do let_it_be(:user) { create(:user) } let(:contact) { create(:contact, first_name: 'Mark', group: group, state: 'active') } diff --git a/spec/services/customer_relations/organizations/create_service_spec.rb b/spec/services/customer_relations/organizations/create_service_spec.rb index 18eefdd716e..8748fe44763 100644 --- a/spec/services/customer_relations/organizations/create_service_spec.rb +++ b/spec/services/customer_relations/organizations/create_service_spec.rb @@ -2,16 +2,16 @@ require 'spec_helper' -RSpec.describe CustomerRelations::Organizations::CreateService do +RSpec.describe CustomerRelations::Organizations::CreateService, feature_category: :service_desk do describe '#execute' do let_it_be(:user) { create(:user) } let(:group) { create(:group, :crm_enabled) } - let(:params) { attributes_for(:organization, group: group) } + let(:params) { attributes_for(:crm_organization, group: group) } subject(:response) { described_class.new(group: group, current_user: user, params: params).execute } - it 'creates an organization' do + it 'creates a crm_organization' do group.add_developer(user) expect(response).to be_success @@ -24,7 +24,7 @@ RSpec.describe CustomerRelations::Organizations::CreateService do expect(response.message).to match_array(['You have insufficient permissions to create an organization for this group']) end - it 'returns an error when the organization is not persisted' do + it 'returns an error when the crm_organization is not persisted' do group.add_developer(user) params[:name] = nil diff --git a/spec/services/customer_relations/organizations/update_service_spec.rb b/spec/services/customer_relations/organizations/update_service_spec.rb index 4764ba85551..f11b99b101e 100644 --- a/spec/services/customer_relations/organizations/update_service_spec.rb +++ b/spec/services/customer_relations/organizations/update_service_spec.rb @@ -2,12 +2,12 @@ require 'spec_helper' -RSpec.describe CustomerRelations::Organizations::UpdateService do +RSpec.describe CustomerRelations::Organizations::UpdateService, feature_category: :service_desk do let_it_be(:user) { create(:user) } - let(:organization) { create(:organization, name: 'Test', group: group, state: 'active') } + let(:crm_organization) { create(:crm_organization, name: 'Test', group: group, state: 'active') } - subject(:update) { described_class.new(group: group, current_user: user, params: params).execute(organization) } + subject(:update) { described_class.new(group: group, current_user: user, params: params).execute(crm_organization) } describe '#execute' do context 'when the user has no permission' do @@ -33,7 +33,7 @@ RSpec.describe CustomerRelations::Organizations::UpdateService do context 'when name is changed' do let(:params) { { name: 'GitLab' } } - it 'updates the organization' do + it 'updates the crm_organization' do response = update expect(response).to be_success @@ -42,7 +42,7 @@ RSpec.describe CustomerRelations::Organizations::UpdateService do end context 'when activating' do - let(:organization) { create(:organization, state: 'inactive') } + let(:crm_organization) { create(:crm_organization, state: 'inactive') } let(:params) { { active: true } } it 'updates the contact' do @@ -56,7 +56,7 @@ RSpec.describe CustomerRelations::Organizations::UpdateService do context 'when deactivating' do let(:params) { { active: false } } - it 'updates the organization' do + it 'updates the crm_organization' do response = update expect(response).to be_success @@ -64,7 +64,7 @@ RSpec.describe CustomerRelations::Organizations::UpdateService do end end - context 'when the organization is invalid' do + context 'when the crm_organization is invalid' do let(:params) { { name: nil } } it 'returns an error' do diff --git a/spec/services/database/consistency_check_service_spec.rb b/spec/services/database/consistency_check_service_spec.rb index 6288fedfb59..8b7560f80ad 100644 --- a/spec/services/database/consistency_check_service_spec.rb +++ b/spec/services/database/consistency_check_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Database::ConsistencyCheckService, feature_category: :pods do +RSpec.describe Database::ConsistencyCheckService, feature_category: :cell do let(:batch_size) { 5 } let(:max_batches) { 2 } diff --git a/spec/services/database/consistency_fix_service_spec.rb b/spec/services/database/consistency_fix_service_spec.rb index 9a0fac2191c..ea0916e8d2b 100644 --- a/spec/services/database/consistency_fix_service_spec.rb +++ b/spec/services/database/consistency_fix_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Database::ConsistencyFixService do +RSpec.describe Database::ConsistencyFixService, feature_category: :cell do describe '#execute' do context 'fixing namespaces inconsistencies' do subject(:consistency_fix_service) do diff --git a/spec/services/dependency_proxy/auth_token_service_spec.rb b/spec/services/dependency_proxy/auth_token_service_spec.rb index c686f57c5cb..2612c5765a4 100644 --- a/spec/services/dependency_proxy/auth_token_service_spec.rb +++ b/spec/services/dependency_proxy/auth_token_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DependencyProxy::AuthTokenService do +RSpec.describe DependencyProxy::AuthTokenService, feature_category: :dependency_proxy do include DependencyProxyHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/dependency_proxy/find_cached_manifest_service_spec.rb b/spec/services/dependency_proxy/find_cached_manifest_service_spec.rb index 470c6eb9e03..13620b3dfc1 100644 --- a/spec/services/dependency_proxy/find_cached_manifest_service_spec.rb +++ b/spec/services/dependency_proxy/find_cached_manifest_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DependencyProxy::FindCachedManifestService do +RSpec.describe DependencyProxy::FindCachedManifestService, feature_category: :dependency_proxy do include DependencyProxyHelpers let_it_be(:image) { 'alpine' } diff --git a/spec/services/dependency_proxy/group_settings/update_service_spec.rb b/spec/services/dependency_proxy/group_settings/update_service_spec.rb index 4954d9ec267..38f837a828a 100644 --- a/spec/services/dependency_proxy/group_settings/update_service_spec.rb +++ b/spec/services/dependency_proxy/group_settings/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::DependencyProxy::GroupSettings::UpdateService do +RSpec.describe ::DependencyProxy::GroupSettings::UpdateService, feature_category: :dependency_proxy do using RSpec::Parameterized::TableSyntax let_it_be_with_reload(:group) { create(:group) } diff --git a/spec/services/dependency_proxy/head_manifest_service_spec.rb b/spec/services/dependency_proxy/head_manifest_service_spec.rb index 949a8eb3bee..a9646a185bc 100644 --- a/spec/services/dependency_proxy/head_manifest_service_spec.rb +++ b/spec/services/dependency_proxy/head_manifest_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DependencyProxy::HeadManifestService do +RSpec.describe DependencyProxy::HeadManifestService, feature_category: :dependency_proxy do include DependencyProxyHelpers let(:image) { 'alpine' } diff --git a/spec/services/dependency_proxy/image_ttl_group_policies/update_service_spec.rb b/spec/services/dependency_proxy/image_ttl_group_policies/update_service_spec.rb index 3a6ba2cca71..f58434222a5 100644 --- a/spec/services/dependency_proxy/image_ttl_group_policies/update_service_spec.rb +++ b/spec/services/dependency_proxy/image_ttl_group_policies/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::DependencyProxy::ImageTtlGroupPolicies::UpdateService do +RSpec.describe ::DependencyProxy::ImageTtlGroupPolicies::UpdateService, feature_category: :dependency_proxy do using RSpec::Parameterized::TableSyntax let_it_be_with_reload(:group) { create(:group) } diff --git a/spec/services/dependency_proxy/request_token_service_spec.rb b/spec/services/dependency_proxy/request_token_service_spec.rb index 8b3ba783b8d..0cc3695f0b0 100644 --- a/spec/services/dependency_proxy/request_token_service_spec.rb +++ b/spec/services/dependency_proxy/request_token_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DependencyProxy::RequestTokenService do +RSpec.describe DependencyProxy::RequestTokenService, feature_category: :dependency_proxy do include DependencyProxyHelpers let(:image) { 'alpine:3.9' } diff --git a/spec/services/deploy_keys/create_service_spec.rb b/spec/services/deploy_keys/create_service_spec.rb index 2e3318236f5..8bff80b2d11 100644 --- a/spec/services/deploy_keys/create_service_spec.rb +++ b/spec/services/deploy_keys/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe DeployKeys::CreateService do +RSpec.describe DeployKeys::CreateService, feature_category: :continuous_delivery do let(:user) { create(:user) } let(:params) { attributes_for(:deploy_key) } diff --git a/spec/services/deployments/archive_in_project_service_spec.rb b/spec/services/deployments/archive_in_project_service_spec.rb index a316c210d64..ed03ce06255 100644 --- a/spec/services/deployments/archive_in_project_service_spec.rb +++ b/spec/services/deployments/archive_in_project_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Deployments::ArchiveInProjectService do +RSpec.describe Deployments::ArchiveInProjectService, feature_category: :continuous_delivery do let_it_be(:project) { create(:project, :repository) } let(:service) { described_class.new(project, nil) } diff --git a/spec/services/deployments/create_for_build_service_spec.rb b/spec/services/deployments/create_for_build_service_spec.rb index 3748df87d99..c07fc07cfbf 100644 --- a/spec/services/deployments/create_for_build_service_spec.rb +++ b/spec/services/deployments/create_for_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Deployments::CreateForBuildService do +RSpec.describe Deployments::CreateForBuildService, feature_category: :continuous_delivery do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/services/deployments/create_service_spec.rb b/spec/services/deployments/create_service_spec.rb index 0f2a6ce32e1..2a70d450575 100644 --- a/spec/services/deployments/create_service_spec.rb +++ b/spec/services/deployments/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Deployments::CreateService do +RSpec.describe Deployments::CreateService, feature_category: :continuous_delivery do let(:user) { create(:user) } describe '#execute' do diff --git a/spec/services/deployments/link_merge_requests_service_spec.rb b/spec/services/deployments/link_merge_requests_service_spec.rb index a653cd2b48b..a468af90ffb 100644 --- a/spec/services/deployments/link_merge_requests_service_spec.rb +++ b/spec/services/deployments/link_merge_requests_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Deployments::LinkMergeRequestsService do +RSpec.describe Deployments::LinkMergeRequestsService, feature_category: :continuous_delivery do let(:project) { create(:project, :repository) } # * ddd0f15 Merge branch 'po-fix-test-env-path' into 'master' diff --git a/spec/services/deployments/older_deployments_drop_service_spec.rb b/spec/services/deployments/older_deployments_drop_service_spec.rb index d9a512a5dd2..7e3074a1688 100644 --- a/spec/services/deployments/older_deployments_drop_service_spec.rb +++ b/spec/services/deployments/older_deployments_drop_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Deployments::OlderDeploymentsDropService do +RSpec.describe Deployments::OlderDeploymentsDropService, feature_category: :continuous_delivery do let(:environment) { create(:environment) } let(:deployment) { create(:deployment, environment: environment) } let(:service) { described_class.new(deployment) } diff --git a/spec/services/deployments/update_environment_service_spec.rb b/spec/services/deployments/update_environment_service_spec.rb index 31a3abda8c7..33c9c9ed592 100644 --- a/spec/services/deployments/update_environment_service_spec.rb +++ b/spec/services/deployments/update_environment_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Deployments::UpdateEnvironmentService do +RSpec.describe Deployments::UpdateEnvironmentService, feature_category: :continuous_delivery do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:options) { { name: environment_name } } diff --git a/spec/services/deployments/update_service_spec.rb b/spec/services/deployments/update_service_spec.rb index d3840189ba4..0814091765c 100644 --- a/spec/services/deployments/update_service_spec.rb +++ b/spec/services/deployments/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Deployments::UpdateService do +RSpec.describe Deployments::UpdateService, feature_category: :continuous_delivery do let(:deploy) { create(:deployment) } describe '#execute' do diff --git a/spec/services/design_management/copy_design_collection/copy_service_spec.rb b/spec/services/design_management/copy_design_collection/copy_service_spec.rb index 89a78c9bf5f..048327792e0 100644 --- a/spec/services/design_management/copy_design_collection/copy_service_spec.rb +++ b/spec/services/design_management/copy_design_collection/copy_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DesignManagement::CopyDesignCollection::CopyService, :clean_gitlab_redis_shared_state do +RSpec.describe DesignManagement::CopyDesignCollection::CopyService, :clean_gitlab_redis_shared_state, feature_category: :portfolio_management do include DesignManagementTestHelpers let_it_be(:user) { create(:user) } @@ -117,6 +117,7 @@ RSpec.describe DesignManagement::CopyDesignCollection::CopyService, :clean_gitla new_designs.zip(old_designs).each do |new_design, old_design| expect(new_design).to have_attributes( filename: old_design.filename, + description: old_design.description, relative_position: old_design.relative_position, issue: target_issue, project: target_issue.project diff --git a/spec/services/design_management/copy_design_collection/queue_service_spec.rb b/spec/services/design_management/copy_design_collection/queue_service_spec.rb index 05a7b092ccf..e6809e65d8a 100644 --- a/spec/services/design_management/copy_design_collection/queue_service_spec.rb +++ b/spec/services/design_management/copy_design_collection/queue_service_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DesignManagement::CopyDesignCollection::QueueService, :clean_gitlab_redis_shared_state do +RSpec.describe DesignManagement::CopyDesignCollection::QueueService, :clean_gitlab_redis_shared_state, + feature_category: :design_management do include DesignManagementTestHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/design_management/delete_designs_service_spec.rb b/spec/services/design_management/delete_designs_service_spec.rb index 48e53a92758..22570a14443 100644 --- a/spec/services/design_management/delete_designs_service_spec.rb +++ b/spec/services/design_management/delete_designs_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DesignManagement::DeleteDesignsService do +RSpec.describe DesignManagement::DeleteDesignsService, feature_category: :design_management do include DesignManagementTestHelpers let_it_be(:project) { create(:project) } @@ -99,7 +99,7 @@ RSpec.describe DesignManagement::DeleteDesignsService do rescue StandardError nil end - .not_to change { redis_hll.unique_events(event_names: event, start_date: 1.day.ago, end_date: 1.day.from_now) } + .not_to change { redis_hll.unique_events(event_names: event, start_date: Date.today, end_date: 1.week.from_now) } begin run_service diff --git a/spec/services/design_management/design_user_notes_count_service_spec.rb b/spec/services/design_management/design_user_notes_count_service_spec.rb index 37806d3461c..1dbd055038c 100644 --- a/spec/services/design_management/design_user_notes_count_service_spec.rb +++ b/spec/services/design_management/design_user_notes_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_store_caching do +RSpec.describe DesignManagement::DesignUserNotesCountService, :use_clean_rails_memory_store_caching, feature_category: :design_management do let_it_be(:design) { create(:design, :with_file) } subject { described_class.new(design) } 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 5409ec12016..08442f221fa 100644 --- a/spec/services/design_management/generate_image_versions_service_spec.rb +++ b/spec/services/design_management/generate_image_versions_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe DesignManagement::GenerateImageVersionsService do +RSpec.describe DesignManagement::GenerateImageVersionsService, feature_category: :design_management do let_it_be(:project) { create(:project) } let_it_be(:issue) { create(:issue, project: project) } let_it_be(:version) { create(:design, :with_lfs_file, issue: issue).versions.first } diff --git a/spec/services/design_management/move_designs_service_spec.rb b/spec/services/design_management/move_designs_service_spec.rb index 519378a8dd4..8276d8d186a 100644 --- a/spec/services/design_management/move_designs_service_spec.rb +++ b/spec/services/design_management/move_designs_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DesignManagement::MoveDesignsService do +RSpec.describe DesignManagement::MoveDesignsService, feature_category: :design_management do include DesignManagementTestHelpers let_it_be(:issue) { create(:issue) } diff --git a/spec/services/design_management/save_designs_service_spec.rb b/spec/services/design_management/save_designs_service_spec.rb index a87494d87f7..ea53fcc3b12 100644 --- a/spec/services/design_management/save_designs_service_spec.rb +++ b/spec/services/design_management/save_designs_service_spec.rb @@ -11,7 +11,10 @@ RSpec.describe DesignManagement::SaveDesignsService, feature_category: :design_m let(:project) { issue.project } let(:user) { developer } let(:files) { [rails_sample] } - let(:design_repository) { ::Gitlab::GlRepository::DESIGN.repository_resolver.call(project) } + let(:design_repository) do + ::Gitlab::GlRepository::DESIGN.repository_resolver.call(project) + end + let(:rails_sample_name) { 'rails_sample.jpg' } let(:rails_sample) { sample_image(rails_sample_name) } let(:dk_png) { sample_image('dk.png') } diff --git a/spec/services/discussions/capture_diff_note_position_service_spec.rb b/spec/services/discussions/capture_diff_note_position_service_spec.rb index 11614ccfd55..313e828bf0a 100644 --- a/spec/services/discussions/capture_diff_note_position_service_spec.rb +++ b/spec/services/discussions/capture_diff_note_position_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Discussions::CaptureDiffNotePositionService do +RSpec.describe Discussions::CaptureDiffNotePositionService, feature_category: :code_review_workflow do subject { described_class.new(note.noteable, paths) } context 'image note on diff' do diff --git a/spec/services/discussions/capture_diff_note_positions_service_spec.rb b/spec/services/discussions/capture_diff_note_positions_service_spec.rb index 8ba54495d4c..96922535eb2 100644 --- a/spec/services/discussions/capture_diff_note_positions_service_spec.rb +++ b/spec/services/discussions/capture_diff_note_positions_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Discussions::CaptureDiffNotePositionsService do +RSpec.describe Discussions::CaptureDiffNotePositionsService, feature_category: :code_review_workflow do context 'when merge request has a discussion' do let(:source_branch) { 'compare-with-merge-head-source' } let(:target_branch) { 'compare-with-merge-head-target' } diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb index e7a3505bbd4..274430fdccb 100644 --- a/spec/services/discussions/update_diff_position_service_spec.rb +++ b/spec/services/discussions/update_diff_position_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Discussions::UpdateDiffPositionService do +RSpec.describe Discussions::UpdateDiffPositionService, feature_category: :code_review_workflow do let(:project) { create(:project, :repository) } let(:current_user) { project.first_owner } let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") } diff --git a/spec/services/draft_notes/create_service_spec.rb b/spec/services/draft_notes/create_service_spec.rb index 528c8717525..93731a80dcc 100644 --- a/spec/services/draft_notes/create_service_spec.rb +++ b/spec/services/draft_notes/create_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DraftNotes::CreateService do +RSpec.describe DraftNotes::CreateService, feature_category: :code_review_workflow do let(:merge_request) { create(:merge_request) } let(:project) { merge_request.target_project } let(:user) { merge_request.author } diff --git a/spec/services/draft_notes/destroy_service_spec.rb b/spec/services/draft_notes/destroy_service_spec.rb index 1f246a56eb3..f4cc9daa9e9 100644 --- a/spec/services/draft_notes/destroy_service_spec.rb +++ b/spec/services/draft_notes/destroy_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DraftNotes::DestroyService do +RSpec.describe DraftNotes::DestroyService, feature_category: :code_review_workflow do let(:merge_request) { create(:merge_request) } let(:project) { merge_request.target_project } let(:user) { merge_request.author } diff --git a/spec/services/draft_notes/publish_service_spec.rb b/spec/services/draft_notes/publish_service_spec.rb index 9e811eaa25e..dab06637c1a 100644 --- a/spec/services/draft_notes/publish_service_spec.rb +++ b/spec/services/draft_notes/publish_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe DraftNotes::PublishService do +RSpec.describe DraftNotes::PublishService, feature_category: :code_review_workflow do include RepoHelpers let(:merge_request) { create(:merge_request) } diff --git a/spec/services/emails/confirm_service_spec.rb b/spec/services/emails/confirm_service_spec.rb index e8d3c0d673b..43fca75a5ea 100644 --- a/spec/services/emails/confirm_service_spec.rb +++ b/spec/services/emails/confirm_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Emails::ConfirmService do +RSpec.describe Emails::ConfirmService, feature_category: :user_management do let_it_be(:user) { create(:user) } subject(:service) { described_class.new(user) } diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb index b13197f21b8..3ef67036483 100644 --- a/spec/services/emails/create_service_spec.rb +++ b/spec/services/emails/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Emails::CreateService do +RSpec.describe Emails::CreateService, feature_category: :user_management do let_it_be(:user) { create(:user) } let(:opts) { { email: 'new@email.com', user: user } } diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb index 7dcf367016e..9d5e2b45647 100644 --- a/spec/services/emails/destroy_service_spec.rb +++ b/spec/services/emails/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Emails::DestroyService do +RSpec.describe Emails::DestroyService, feature_category: :user_management do let!(:user) { create(:user) } let!(:email) { create(:email, user: user) } diff --git a/spec/services/environments/auto_stop_service_spec.rb b/spec/services/environments/auto_stop_service_spec.rb index d688690c376..57fb860a557 100644 --- a/spec/services/environments/auto_stop_service_spec.rb +++ b/spec/services/environments/auto_stop_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Environments::AutoStopService, :clean_gitlab_redis_shared_state, :sidekiq_inline do +RSpec.describe Environments::AutoStopService, :clean_gitlab_redis_shared_state, :sidekiq_inline, + feature_category: :continuous_delivery do include CreateEnvironmentsHelpers include ExclusiveLeaseHelpers diff --git a/spec/services/environments/canary_ingress/update_service_spec.rb b/spec/services/environments/canary_ingress/update_service_spec.rb index 531f7d68a9f..f7d446c13f9 100644 --- a/spec/services/environments/canary_ingress/update_service_spec.rb +++ b/spec/services/environments/canary_ingress/update_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Environments::CanaryIngress::UpdateService, :clean_gitlab_redis_cache do +RSpec.describe Environments::CanaryIngress::UpdateService, :clean_gitlab_redis_cache, + feature_category: :continuous_delivery do include KubernetesHelpers let_it_be(:project, refind: true) { create(:project) } diff --git a/spec/services/environments/create_for_build_service_spec.rb b/spec/services/environments/create_for_build_service_spec.rb index c7aadb20c01..223401a243d 100644 --- a/spec/services/environments/create_for_build_service_spec.rb +++ b/spec/services/environments/create_for_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Environments::CreateForBuildService do +RSpec.describe Environments::CreateForBuildService, feature_category: :continuous_delivery do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/services/environments/reset_auto_stop_service_spec.rb b/spec/services/environments/reset_auto_stop_service_spec.rb index 4a0b091c12d..a3b8b2e0aa1 100644 --- a/spec/services/environments/reset_auto_stop_service_spec.rb +++ b/spec/services/environments/reset_auto_stop_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Environments::ResetAutoStopService do +RSpec.describe Environments::ResetAutoStopService, feature_category: :continuous_delivery do let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } } let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } } diff --git a/spec/services/environments/schedule_to_delete_review_apps_service_spec.rb b/spec/services/environments/schedule_to_delete_review_apps_service_spec.rb index 401d6203b2c..3047f415815 100644 --- a/spec/services/environments/schedule_to_delete_review_apps_service_spec.rb +++ b/spec/services/environments/schedule_to_delete_review_apps_service_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe Environments::ScheduleToDeleteReviewAppsService do +RSpec.describe Environments::ScheduleToDeleteReviewAppsService, feature_category: :continuous_delivery do include ExclusiveLeaseHelpers let_it_be(:maintainer) { create(:user) } diff --git a/spec/services/environments/stop_service_spec.rb b/spec/services/environments/stop_service_spec.rb index 5f983a2151a..6e3b36b5636 100644 --- a/spec/services/environments/stop_service_spec.rb +++ b/spec/services/environments/stop_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Environments::StopService do +RSpec.describe Environments::StopService, feature_category: :continuous_delivery do include CreateEnvironmentsHelpers let(:project) { create(:project, :private, :repository) } diff --git a/spec/services/error_tracking/base_service_spec.rb b/spec/services/error_tracking/base_service_spec.rb index de3523cb847..ed9efd9f95a 100644 --- a/spec/services/error_tracking/base_service_spec.rb +++ b/spec/services/error_tracking/base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ErrorTracking::BaseService do +RSpec.describe ErrorTracking::BaseService, feature_category: :error_tracking do describe '#compose_response' do let(:project) { build_stubbed(:project) } let(:user) { build_stubbed(:user, id: non_existing_record_id) } diff --git a/spec/services/error_tracking/collect_error_service_spec.rb b/spec/services/error_tracking/collect_error_service_spec.rb index 159c070c683..3ff753e8c65 100644 --- a/spec/services/error_tracking/collect_error_service_spec.rb +++ b/spec/services/error_tracking/collect_error_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ErrorTracking::CollectErrorService do +RSpec.describe ErrorTracking::CollectErrorService, feature_category: :error_tracking do let_it_be(:project) { create(:project) } let(:parsed_event_file) { 'error_tracking/parsed_event.json' } diff --git a/spec/services/error_tracking/issue_details_service_spec.rb b/spec/services/error_tracking/issue_details_service_spec.rb index 29f8154a27c..7ac41ffead6 100644 --- a/spec/services/error_tracking/issue_details_service_spec.rb +++ b/spec/services/error_tracking/issue_details_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ErrorTracking::IssueDetailsService do +RSpec.describe ErrorTracking::IssueDetailsService, feature_category: :error_tracking do include_context 'sentry error tracking context' subject { described_class.new(project, user, params) } 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 aa2430ddffb..bfde14c7ef1 100644 --- a/spec/services/error_tracking/issue_latest_event_service_spec.rb +++ b/spec/services/error_tracking/issue_latest_event_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ErrorTracking::IssueLatestEventService do +RSpec.describe ErrorTracking::IssueLatestEventService, feature_category: :error_tracking do include_context 'sentry error tracking context' let(:params) { {} } diff --git a/spec/services/error_tracking/issue_update_service_spec.rb b/spec/services/error_tracking/issue_update_service_spec.rb index a06c3588264..4dae6cc2fa0 100644 --- a/spec/services/error_tracking/issue_update_service_spec.rb +++ b/spec/services/error_tracking/issue_update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ErrorTracking::IssueUpdateService do +RSpec.describe ErrorTracking::IssueUpdateService, feature_category: :error_tracking do include_context 'sentry error tracking context' let(:arguments) { { issue_id: non_existing_record_id, status: 'resolved' } } diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb index a7bd6c75df5..2c35c2b8acd 100644 --- a/spec/services/error_tracking/list_issues_service_spec.rb +++ b/spec/services/error_tracking/list_issues_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ErrorTracking::ListIssuesService do +RSpec.describe ErrorTracking::ListIssuesService, feature_category: :error_tracking do include_context 'sentry error tracking context' let(:params) { {} } diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index b969bd76053..6a4769d77d5 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -2,20 +2,31 @@ require 'spec_helper' -RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state do +RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state, feature_category: :service_ping do include SnowplowHelpers let(:service) { described_class.new } + let(:dates) { { start_date: Date.today.beginning_of_week, end_date: Date.today.next_week } } let_it_be(:user, reload: true) { create :user } let_it_be(:project) { create(:project) } shared_examples 'it records the event in the event counter' do specify do - tracking_params = { event_action: event_action, date_from: Date.yesterday, date_to: Date.today } + tracking_params = { event_names: event_action, **dates } expect { subject } - .to change { Gitlab::UsageDataCounters::TrackUniqueEvents.count_unique_events(**tracking_params) } + .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) } + .by(1) + end + end + + shared_examples 'it records a git write event' do + specify do + tracking_params = { event_names: 'git_write_action', **dates } + + expect { subject } + .to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) } .by(1) end end @@ -65,11 +76,10 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi end it_behaves_like "it records the event in the event counter" do - let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION } + let(:event_action) { :merge_request_action } end it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:category) { described_class.name } let(:action) { 'created' } let(:label) { described_class::MR_EVENT_LABEL } @@ -95,11 +105,10 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi end it_behaves_like "it records the event in the event counter" do - let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION } + let(:event_action) { :merge_request_action } end it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:category) { described_class.name } let(:action) { 'closed' } let(:label) { described_class::MR_EVENT_LABEL } @@ -125,11 +134,10 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi end it_behaves_like "it records the event in the event counter" do - let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION } + let(:event_action) { :merge_request_action } end it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:category) { described_class.name } let(:action) { 'merged' } let(:label) { described_class::MR_EVENT_LABEL } @@ -276,8 +284,10 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi end it_behaves_like "it records the event in the event counter" do - let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::WIKI_ACTION } + let(:event_action) { :wiki_action } end + + it_behaves_like "it records a git write event" end (Event.actions.keys - Event::WIKI_ACTIONS).each do |bad_action| @@ -312,9 +322,11 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi it_behaves_like 'service for creating a push event', PushEventPayloadService it_behaves_like "it records the event in the event counter" do - let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::PUSH_ACTION } + let(:event_action) { :project_action } end + it_behaves_like "it records a git write event" + it_behaves_like 'Snowplow event tracking with RedisHLL context' do let(:category) { described_class.to_s } let(:action) { :push } @@ -338,9 +350,11 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi it_behaves_like 'service for creating a push event', BulkPushEventPayloadService it_behaves_like "it records the event in the event counter" do - let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::PUSH_ACTION } + let(:event_action) { :project_action } end + it_behaves_like "it records a git write event" + it_behaves_like 'Snowplow event tracking with RedisHLL context' do let(:category) { described_class.to_s } let(:action) { :push } @@ -400,16 +414,17 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi end it_behaves_like "it records the event in the event counter" do - let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::DESIGN_ACTION } + let(:event_action) { :design_action } end + it_behaves_like "it records a git write event" + describe 'Snowplow tracking' do let(:project) { design.project } let(:namespace) { project.namespace } let(:category) { described_class.name } - let(:property) { Gitlab::UsageDataCounters::TrackUniqueEvents::DESIGN_ACTION.to_s } + let(:property) { :design_action.to_s } let(:label) { ::EventCreateService::DEGIGN_EVENT_LABEL } - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } context 'for create event' do it_behaves_like 'Snowplow event tracking with RedisHLL context' do @@ -448,9 +463,11 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi end it_behaves_like "it records the event in the event counter" do - let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::DESIGN_ACTION } + let(:event_action) { :design_action } end + it_behaves_like "it records a git write event" + it_behaves_like 'Snowplow event tracking with RedisHLL context' do subject(:design_service) { service.destroy_designs([design], author) } @@ -459,9 +476,8 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi let(:category) { described_class.name } let(:action) { 'destroy' } let(:user) { author } - let(:property) { Gitlab::UsageDataCounters::TrackUniqueEvents::DESIGN_ACTION.to_s } + let(:property) { :design_action.to_s } let(:label) { ::EventCreateService::DEGIGN_EVENT_LABEL } - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } end end end @@ -471,7 +487,7 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi let(:note) { create(:note) } let(:author) { create(:user) } - let(:event_action) { Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION } + let(:event_action) { :merge_request_action } it { expect(leave_note).to be_truthy } @@ -485,7 +501,6 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi it_behaves_like "it records the event in the event counter" it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:note) { create(:diff_note_on_merge_request) } let(:category) { described_class.name } let(:action) { 'commented' } @@ -502,10 +517,9 @@ RSpec.describe EventCreateService, :clean_gitlab_redis_cache, :clean_gitlab_redi context 'when it is not a diff note' do it 'does not change the unique action counter' do - counter_class = Gitlab::UsageDataCounters::TrackUniqueEvents - tracking_params = { event_action: event_action, date_from: Date.yesterday, date_to: Date.today } + tracking_params = { event_names: event_action, start_date: Date.yesterday, end_date: Date.today } - expect { subject }.not_to change { counter_class.count_unique_events(**tracking_params) } + expect { subject }.not_to change { Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**tracking_params) } end end end diff --git a/spec/services/events/destroy_service_spec.rb b/spec/services/events/destroy_service_spec.rb index 8b07852c040..e50fe247238 100644 --- a/spec/services/events/destroy_service_spec.rb +++ b/spec/services/events/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Events::DestroyService do +RSpec.describe Events::DestroyService, feature_category: :user_profile do subject(:service) { described_class.new(project) } let_it_be(:project, reload: true) { create(:project, :repository) } diff --git a/spec/services/events/render_service_spec.rb b/spec/services/events/render_service_spec.rb index 24a3b9abe14..2e8cd26781b 100644 --- a/spec/services/events/render_service_spec.rb +++ b/spec/services/events/render_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Events::RenderService do +RSpec.describe Events::RenderService, feature_category: :user_profile do describe '#execute' do let!(:note) { build(:note) } let!(:event) { build(:event, target: note, project: note.project) } diff --git a/spec/services/feature_flags/create_service_spec.rb b/spec/services/feature_flags/create_service_spec.rb index 1a32faad948..18c48714ccd 100644 --- a/spec/services/feature_flags/create_service_spec.rb +++ b/spec/services/feature_flags/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe FeatureFlags::CreateService do +RSpec.describe FeatureFlags::CreateService, feature_category: :feature_flags do let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user) } let_it_be(:reporter) { create(:user) } @@ -46,6 +46,8 @@ RSpec.describe FeatureFlags::CreateService do end context 'when feature flag is saved correctly' do + let(:audit_event_details) { AuditEvent.last.details } + let(:audit_event_message) { audit_event_details[:custom_message] } let(:params) do { name: 'feature_flag', @@ -88,9 +90,9 @@ RSpec.describe FeatureFlags::CreateService do it 'creates audit event', :with_license do expect { subject }.to change { AuditEvent.count }.by(1) - expect(AuditEvent.last.details[:custom_message]).to start_with('Created feature flag feature_flag with description "description".') - expect(AuditEvent.last.details[:custom_message]).to include('Created strategy "default" with scopes "*".') - expect(AuditEvent.last.details[:custom_message]).to include('Created strategy "default" with scopes "production".') + expect(audit_event_message).to start_with('Created feature flag feature_flag with description "description".') + expect(audit_event_message).to include('Created strategy "default" with scopes "*".') + expect(audit_event_message).to include('Created strategy "default" with scopes "production".') end context 'when user is reporter' do diff --git a/spec/services/feature_flags/destroy_service_spec.rb b/spec/services/feature_flags/destroy_service_spec.rb index b2793dc0560..1ec0ee6e68c 100644 --- a/spec/services/feature_flags/destroy_service_spec.rb +++ b/spec/services/feature_flags/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe FeatureFlags::DestroyService do +RSpec.describe FeatureFlags::DestroyService, feature_category: :feature_flags do include FeatureFlagHelpers let_it_be(:project) { create(:project) } @@ -20,7 +20,8 @@ RSpec.describe FeatureFlags::DestroyService do describe '#execute' do subject { described_class.new(project, user, params).execute(feature_flag) } - let(:audit_event_message) { AuditEvent.last.details[:custom_message] } + let(:audit_event_details) { AuditEvent.last.details } + let(:audit_event_message) { audit_event_details[:custom_message] } let(:params) { {} } it 'returns status success' do diff --git a/spec/services/feature_flags/hook_service_spec.rb b/spec/services/feature_flags/hook_service_spec.rb index f3edaca52a9..2a3ce9085b8 100644 --- a/spec/services/feature_flags/hook_service_spec.rb +++ b/spec/services/feature_flags/hook_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe FeatureFlags::HookService do +RSpec.describe FeatureFlags::HookService, feature_category: :feature_flags do describe '#execute_hooks' do let_it_be(:namespace) { create(:namespace) } let_it_be(:project) { create(:project, :repository, namespace: namespace) } diff --git a/spec/services/feature_flags/update_service_spec.rb b/spec/services/feature_flags/update_service_spec.rb index 1c5af71a50a..55c09f06f16 100644 --- a/spec/services/feature_flags/update_service_spec.rb +++ b/spec/services/feature_flags/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe FeatureFlags::UpdateService, :with_license do +RSpec.describe FeatureFlags::UpdateService, :with_license, feature_category: :feature_flags do let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user) } let_it_be(:reporter) { create(:user) } @@ -19,9 +19,8 @@ RSpec.describe FeatureFlags::UpdateService, :with_license do subject { described_class.new(project, user, params).execute(feature_flag) } let(:params) { { name: 'new_name' } } - let(:audit_event_message) do - AuditEvent.last.details[:custom_message] - end + let(:audit_event_details) { AuditEvent.last.details } + let(:audit_event_message) { audit_event_details[:custom_message] } it 'returns success status' do expect(subject[:status]).to eq(:success) diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb index 3b3dbd1fcfe..26f57f43120 100644 --- a/spec/services/files/create_service_spec.rb +++ b/spec/services/files/create_service_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe Files::CreateService do +RSpec.describe Files::CreateService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user, :commit_email) } diff --git a/spec/services/files/delete_service_spec.rb b/spec/services/files/delete_service_spec.rb index 3823d027812..dd99e5f9742 100644 --- a/spec/services/files/delete_service_spec.rb +++ b/spec/services/files/delete_service_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe Files::DeleteService do +RSpec.describe Files::DeleteService, feature_category: :source_code_management do subject { described_class.new(project, user, commit_params) } let(:project) { create(:project, :repository) } @@ -52,8 +52,8 @@ RSpec.describe Files::DeleteService do end describe "#execute" do - context "when the file's last commit sha does not match the supplied last_commit_sha" do - let(:last_commit_sha) { "foo" } + context "when the file's last commit is earlier than the latest commit for this branch" do + let(:last_commit_sha) { Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch, file_path).parent_id } it "returns a hash with the correct error message and a :error status" do expect { subject.execute } diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb index 6a5c7d2749d..7149fa77d6a 100644 --- a/spec/services/files/multi_service_spec.rb +++ b/spec/services/files/multi_service_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe Files::MultiService do +RSpec.describe Files::MultiService, feature_category: :source_code_management do subject { described_class.new(project, user, commit_params) } let(:project) { create(:project, :repository) } @@ -19,6 +19,10 @@ RSpec.describe Files::MultiService do Gitlab::Git::Commit.last_for_path(project.repository, branch_name, original_file_path).sha end + let(:branch_commit_id) do + Gitlab::Git::Commit.find(project.repository, branch_name).sha + end + let(:default_action) do { action: action, @@ -78,6 +82,16 @@ RSpec.describe Files::MultiService do end end + context 'when file not changed, but later commit id is used' do + let(:actions) { [default_action.merge(last_commit_id: branch_commit_id)] } + + it 'accepts the commit' do + results = subject.execute + + expect(results[:status]).to eq(:success) + end + end + context 'when the file have not been modified' do it 'accepts the commit' do results = subject.execute diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb index 6d7459e0b29..6a9f9d6b86f 100644 --- a/spec/services/files/update_service_spec.rb +++ b/spec/services/files/update_service_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe Files::UpdateService do +RSpec.describe Files::UpdateService, feature_category: :source_code_management do subject { described_class.new(project, user, commit_params) } let(:project) { create(:project, :repository) } @@ -31,8 +31,8 @@ RSpec.describe Files::UpdateService do end describe "#execute" do - context "when the file's last commit sha does not match the supplied last_commit_sha" do - let(:last_commit_sha) { "foo" } + context "when the file's last commit sha is earlier than the latest change for that branch" do + let(:last_commit_sha) { Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch, file_path).parent_id } it "returns a hash with the correct error message and a :error status" do expect { subject.execute } diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb index 5afd7b30ab0..8a686a19c4c 100644 --- a/spec/services/git/base_hooks_service_spec.rb +++ b/spec/services/git/base_hooks_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Git::BaseHooksService do +RSpec.describe Git::BaseHooksService, feature_category: :source_code_management do include RepoHelpers let_it_be(:user) { create(:user) } @@ -325,4 +325,40 @@ RSpec.describe Git::BaseHooksService do end end end + + describe 'notifying KAS' do + let(:kas_enabled) { true } + + before do + allow(Gitlab::Kas).to receive(:enabled?).and_return(kas_enabled) + end + + it 'enqueues the notification worker' do + expect(Clusters::Agents::NotifyGitPushWorker).to receive(:perform_async).with(project.id).once + + subject.execute + end + + context 'when KAS is disabled' do + let(:kas_enabled) { false } + + it do + expect(Clusters::Agents::NotifyGitPushWorker).not_to receive(:perform_async) + + subject.execute + end + end + + context 'when :notify_kas_on_git_push feature flag is disabled' do + before do + stub_feature_flags(notify_kas_on_git_push: false) + end + + it do + expect(Clusters::Agents::NotifyGitPushWorker).not_to receive(:perform_async) + + subject.execute + end + end + end end diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb index 973ead28462..e991b5bd842 100644 --- a/spec/services/git/branch_hooks_service_spec.rb +++ b/spec/services/git/branch_hooks_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Git::BranchHooksService, :clean_gitlab_redis_shared_state do +RSpec.describe Git::BranchHooksService, :clean_gitlab_redis_shared_state, feature_category: :source_code_management do include RepoHelpers include ProjectForksHelper diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb index a9f5b07fef4..aa534777f3e 100644 --- a/spec/services/git/branch_push_service_spec.rb +++ b/spec/services/git/branch_push_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Git::BranchPushService, :use_clean_rails_redis_caching, services: true do +RSpec.describe Git::BranchPushService, :use_clean_rails_redis_caching, services: true, feature_category: :source_code_management do include RepoHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/git/process_ref_changes_service_spec.rb b/spec/services/git/process_ref_changes_service_spec.rb index 8d2da4a899e..9ec13bc957b 100644 --- a/spec/services/git/process_ref_changes_service_spec.rb +++ b/spec/services/git/process_ref_changes_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Git::ProcessRefChangesService do +RSpec.describe Git::ProcessRefChangesService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } let(:user) { project.first_owner } let(:params) { { changes: git_changes } } diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb index 01a0d2e8600..73f6eff36ba 100644 --- a/spec/services/git/tag_hooks_service_spec.rb +++ b/spec/services/git/tag_hooks_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Git::TagHooksService, :service do +RSpec.describe Git::TagHooksService, :service, feature_category: :source_code_management do let(:user) { create(:user) } let(:project) { create(:project, :repository) } diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb index 597254d46fa..0d40c331d11 100644 --- a/spec/services/git/tag_push_service_spec.rb +++ b/spec/services/git/tag_push_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Git::TagPushService do +RSpec.describe Git::TagPushService, feature_category: :source_code_management do include RepoHelpers let(:user) { create(:user) } diff --git a/spec/services/git/wiki_push_service/change_spec.rb b/spec/services/git/wiki_push_service/change_spec.rb index 3616bf62b20..719e67666ce 100644 --- a/spec/services/git/wiki_push_service/change_spec.rb +++ b/spec/services/git/wiki_push_service/change_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Git::WikiPushService::Change do +RSpec.describe Git::WikiPushService::Change, feature_category: :source_code_management do subject { described_class.new(project_wiki, change, raw_change) } let(:project_wiki) { double('ProjectWiki') } @@ -60,11 +60,13 @@ RSpec.describe Git::WikiPushService::Change do end %i[added renamed modified].each do |op| - let(:operation) { op } - let(:slug) { new_path.chomp('.md') } - let(:revision) { change[:newrev] } + context "the operation is #{op}" do + let(:operation) { op } + let(:slug) { new_path.chomp('.md') } + let(:revision) { change[:newrev] } - it { is_expected.to have_attributes(page: wiki_page) } + it { is_expected.to have_attributes(page: wiki_page) } + end end end end diff --git a/spec/services/google_cloud/create_cloudsql_instance_service_spec.rb b/spec/services/google_cloud/create_cloudsql_instance_service_spec.rb index cd0dd75e576..4f2e0bea623 100644 --- a/spec/services/google_cloud/create_cloudsql_instance_service_spec.rb +++ b/spec/services/google_cloud/create_cloudsql_instance_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GoogleCloud::CreateCloudsqlInstanceService do +RSpec.describe GoogleCloud::CreateCloudsqlInstanceService, feature_category: :deployment_management do let(:project) { create(:project) } let(:user) { create(:user) } let(:gcp_project_id) { 'gcp_project_120' } diff --git a/spec/services/google_cloud/create_service_accounts_service_spec.rb b/spec/services/google_cloud/create_service_accounts_service_spec.rb index 3f500e7c235..3b57f2a9e5f 100644 --- a/spec/services/google_cloud/create_service_accounts_service_spec.rb +++ b/spec/services/google_cloud/create_service_accounts_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GoogleCloud::CreateServiceAccountsService do +RSpec.describe GoogleCloud::CreateServiceAccountsService, feature_category: :deployment_management do describe '#execute' do before do mock_google_oauth2_creds = Struct.new(:app_id, :app_secret) diff --git a/spec/services/google_cloud/enable_cloud_run_service_spec.rb b/spec/services/google_cloud/enable_cloud_run_service_spec.rb index 6d2b1f5cfd5..3de9e7fcd5c 100644 --- a/spec/services/google_cloud/enable_cloud_run_service_spec.rb +++ b/spec/services/google_cloud/enable_cloud_run_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GoogleCloud::EnableCloudRunService do +RSpec.describe GoogleCloud::EnableCloudRunService, feature_category: :deployment_management do describe 'when a project does not have any gcp projects' do let_it_be(:project) { create(:project) } diff --git a/spec/services/google_cloud/enable_cloudsql_service_spec.rb b/spec/services/google_cloud/enable_cloudsql_service_spec.rb index aa6d2402d7c..b14b827e8b8 100644 --- a/spec/services/google_cloud/enable_cloudsql_service_spec.rb +++ b/spec/services/google_cloud/enable_cloudsql_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GoogleCloud::EnableCloudsqlService do +RSpec.describe GoogleCloud::EnableCloudsqlService, feature_category: :deployment_management do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:params) do diff --git a/spec/services/google_cloud/gcp_region_add_or_replace_service_spec.rb b/spec/services/google_cloud/gcp_region_add_or_replace_service_spec.rb index b2cd5632be0..a748fed7134 100644 --- a/spec/services/google_cloud/gcp_region_add_or_replace_service_spec.rb +++ b/spec/services/google_cloud/gcp_region_add_or_replace_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GoogleCloud::GcpRegionAddOrReplaceService do +RSpec.describe GoogleCloud::GcpRegionAddOrReplaceService, feature_category: :deployment_management do it 'adds and replaces GCP region vars' do project = create(:project, :public) service = described_class.new(project) diff --git a/spec/services/google_cloud/generate_pipeline_service_spec.rb b/spec/services/google_cloud/generate_pipeline_service_spec.rb index a78d8ff6661..c18514884ca 100644 --- a/spec/services/google_cloud/generate_pipeline_service_spec.rb +++ b/spec/services/google_cloud/generate_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GoogleCloud::GeneratePipelineService do +RSpec.describe GoogleCloud::GeneratePipelineService, feature_category: :deployment_management do describe 'for cloud-run' do describe 'when there is no existing pipeline' do let_it_be(:project) { create(:project, :repository) } diff --git a/spec/services/google_cloud/get_cloudsql_instances_service_spec.rb b/spec/services/google_cloud/get_cloudsql_instances_service_spec.rb index 4587a5077c0..ed41d0fd487 100644 --- a/spec/services/google_cloud/get_cloudsql_instances_service_spec.rb +++ b/spec/services/google_cloud/get_cloudsql_instances_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GoogleCloud::GetCloudsqlInstancesService do +RSpec.describe GoogleCloud::GetCloudsqlInstancesService, feature_category: :deployment_management do let(:service) { described_class.new(project) } let(:project) { create(:project) } diff --git a/spec/services/google_cloud/service_accounts_service_spec.rb b/spec/services/google_cloud/service_accounts_service_spec.rb index 10e387126a3..c900bf7d300 100644 --- a/spec/services/google_cloud/service_accounts_service_spec.rb +++ b/spec/services/google_cloud/service_accounts_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GoogleCloud::ServiceAccountsService do +RSpec.describe GoogleCloud::ServiceAccountsService, feature_category: :deployment_management do let(:service) { described_class.new(project) } describe 'find_for_project' do diff --git a/spec/services/google_cloud/setup_cloudsql_instance_service_spec.rb b/spec/services/google_cloud/setup_cloudsql_instance_service_spec.rb index 0a0f05ab4be..5095277f61a 100644 --- a/spec/services/google_cloud/setup_cloudsql_instance_service_spec.rb +++ b/spec/services/google_cloud/setup_cloudsql_instance_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GoogleCloud::SetupCloudsqlInstanceService do +RSpec.describe GoogleCloud::SetupCloudsqlInstanceService, feature_category: :deployment_management do let(:random_user) { create(:user) } let(:project) { create(:project) } let(:list_databases_empty) { Google::Apis::SqladminV1beta4::ListDatabasesResponse.new(items: []) } diff --git a/spec/services/gpg_keys/create_service_spec.rb b/spec/services/gpg_keys/create_service_spec.rb index 9ac56355b4b..d603ce951ec 100644 --- a/spec/services/gpg_keys/create_service_spec.rb +++ b/spec/services/gpg_keys/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GpgKeys::CreateService do +RSpec.describe GpgKeys::CreateService, feature_category: :source_code_management do let(:user) { create(:user) } let(:params) { attributes_for(:gpg_key) } diff --git a/spec/services/grafana/proxy_service_spec.rb b/spec/services/grafana/proxy_service_spec.rb index 99120de3593..7029bab379a 100644 --- a/spec/services/grafana/proxy_service_spec.rb +++ b/spec/services/grafana/proxy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Grafana::ProxyService do +RSpec.describe Grafana::ProxyService, feature_category: :metrics do include ReactiveCachingHelpers let_it_be(:project) { create(:project) } diff --git a/spec/services/gravatar_service_spec.rb b/spec/services/gravatar_service_spec.rb index a6418b02f78..6ccb362cc5c 100644 --- a/spec/services/gravatar_service_spec.rb +++ b/spec/services/gravatar_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GravatarService do +RSpec.describe GravatarService, feature_category: :user_profile do describe '#execute' do let(:url) { 'http://example.com/avatar?hash=%{hash}&size=%{size}&email=%{email}&username=%{username}' } diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb index 486a99dd8df..0724e072dab 100644 --- a/spec/services/groups/auto_devops_service_spec.rb +++ b/spec/services/groups/auto_devops_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Groups::AutoDevopsService, '#execute' do +RSpec.describe Groups::AutoDevopsService, '#execute', feature_category: :auto_devops do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } diff --git a/spec/services/groups/autocomplete_service_spec.rb b/spec/services/groups/autocomplete_service_spec.rb index 00d0ad3b347..9f55322e72d 100644 --- a/spec/services/groups/autocomplete_service_spec.rb +++ b/spec/services/groups/autocomplete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::AutocompleteService do +RSpec.describe Groups::AutocompleteService, feature_category: :subgroups do let_it_be(:group, refind: true) { create(:group, :nested, :private, avatar: fixture_file_upload('spec/fixtures/dk.png')) } let_it_be(:sub_group) { create(:group, :private, parent: group) } diff --git a/spec/services/groups/deploy_tokens/create_service_spec.rb b/spec/services/groups/deploy_tokens/create_service_spec.rb index 0c28075f998..e408c2787d8 100644 --- a/spec/services/groups/deploy_tokens/create_service_spec.rb +++ b/spec/services/groups/deploy_tokens/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::DeployTokens::CreateService do +RSpec.describe Groups::DeployTokens::CreateService, feature_category: :deployment_management do it_behaves_like 'a deploy token creation service' do let(:entity) { create(:group) } let(:deploy_token_class) { GroupDeployToken } diff --git a/spec/services/groups/deploy_tokens/destroy_service_spec.rb b/spec/services/groups/deploy_tokens/destroy_service_spec.rb index 28e60b12993..c4694758b2f 100644 --- a/spec/services/groups/deploy_tokens/destroy_service_spec.rb +++ b/spec/services/groups/deploy_tokens/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::DeployTokens::DestroyService do +RSpec.describe Groups::DeployTokens::DestroyService, feature_category: :deployment_management do it_behaves_like 'a deploy token deletion service' do let_it_be(:entity) { create(:group) } let_it_be(:deploy_token_class) { GroupDeployToken } diff --git a/spec/services/groups/deploy_tokens/revoke_service_spec.rb b/spec/services/groups/deploy_tokens/revoke_service_spec.rb index fcf11bbb8e6..c302dd14e3b 100644 --- a/spec/services/groups/deploy_tokens/revoke_service_spec.rb +++ b/spec/services/groups/deploy_tokens/revoke_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::DeployTokens::RevokeService do +RSpec.describe Groups::DeployTokens::RevokeService, feature_category: :deployment_management do let_it_be(:entity) { create(:group) } let_it_be(:deploy_token) { create(:deploy_token, :group, groups: [entity]) } let_it_be(:user) { create(:user) } diff --git a/spec/services/groups/group_links/create_service_spec.rb b/spec/services/groups/group_links/create_service_spec.rb index bfbaedbd06f..ced87421858 100644 --- a/spec/services/groups/group_links/create_service_spec.rb +++ b/spec/services/groups/group_links/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::GroupLinks::CreateService, '#execute' do +RSpec.describe Groups::GroupLinks::CreateService, '#execute', feature_category: :subgroups do let_it_be(:shared_with_group_parent) { create(:group, :private) } let_it_be(:shared_with_group) { create(:group, :private, parent: shared_with_group_parent) } let_it_be(:shared_with_group_child) { create(:group, :private, parent: shared_with_group) } diff --git a/spec/services/groups/group_links/destroy_service_spec.rb b/spec/services/groups/group_links/destroy_service_spec.rb index a570c28cf8b..5821ec44192 100644 --- a/spec/services/groups/group_links/destroy_service_spec.rb +++ b/spec/services/groups/group_links/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::GroupLinks::DestroyService, '#execute' do +RSpec.describe Groups::GroupLinks::DestroyService, '#execute', feature_category: :subgroups do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :private) } let_it_be(:shared_group) { create(:group, :private) } diff --git a/spec/services/groups/group_links/update_service_spec.rb b/spec/services/groups/group_links/update_service_spec.rb index 31446c8e4bf..f17d2f50a02 100644 --- a/spec/services/groups/group_links/update_service_spec.rb +++ b/spec/services/groups/group_links/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do +RSpec.describe Groups::GroupLinks::UpdateService, '#execute', feature_category: :subgroups do let(:user) { create(:user) } let_it_be(:group) { create(:group, :private) } @@ -18,7 +18,7 @@ RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do expires_at: expiry_date } end - subject { described_class.new(link).execute(group_link_params) } + subject { described_class.new(link, user).execute(group_link_params) } before do group.add_developer(group_member_user) diff --git a/spec/services/groups/import_export/export_service_spec.rb b/spec/services/groups/import_export/export_service_spec.rb index ec42a728409..c44c2e3b911 100644 --- a/spec/services/groups/import_export/export_service_spec.rb +++ b/spec/services/groups/import_export/export_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::ImportExport::ExportService do +RSpec.describe Groups::ImportExport::ExportService, feature_category: :importers do describe '#async_execute' do let(:user) { create(:user) } let(:group) { create(:group) } diff --git a/spec/services/groups/import_export/import_service_spec.rb b/spec/services/groups/import_export/import_service_spec.rb index 972b12d7ee5..75db6e26cbf 100644 --- a/spec/services/groups/import_export/import_service_spec.rb +++ b/spec/services/groups/import_export/import_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::ImportExport::ImportService do +RSpec.describe Groups::ImportExport::ImportService, feature_category: :importers do describe '#async_execute' do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } diff --git a/spec/services/groups/merge_requests_count_service_spec.rb b/spec/services/groups/merge_requests_count_service_spec.rb index 8bd350d6f0e..32c4c618eda 100644 --- a/spec/services/groups/merge_requests_count_service_spec.rb +++ b/spec/services/groups/merge_requests_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::MergeRequestsCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Groups::MergeRequestsCountService, :use_clean_rails_memory_store_caching, feature_category: :subgroups do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :repository, namespace: group) } diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb index a43c1d8d9c3..476bc2aa23c 100644 --- a/spec/services/groups/nested_create_service_spec.rb +++ b/spec/services/groups/nested_create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::NestedCreateService do +RSpec.describe Groups::NestedCreateService, feature_category: :subgroups do let(:user) { create(:user) } subject(:service) { described_class.new(user, params) } diff --git a/spec/services/groups/open_issues_count_service_spec.rb b/spec/services/groups/open_issues_count_service_spec.rb index 923caa6c150..725b913bf15 100644 --- a/spec/services/groups/open_issues_count_service_spec.rb +++ b/spec/services/groups/open_issues_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::OpenIssuesCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Groups::OpenIssuesCountService, :use_clean_rails_memory_store_caching, feature_category: :subgroups do let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :public, namespace: group) } let_it_be(:user) { create(:user) } diff --git a/spec/services/groups/participants_service_spec.rb b/spec/services/groups/participants_service_spec.rb index 750aead277f..37966a523c2 100644 --- a/spec/services/groups/participants_service_spec.rb +++ b/spec/services/groups/participants_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::ParticipantsService do +RSpec.describe Groups::ParticipantsService, feature_category: :subgroups do describe '#group_members' do let(:user) { create(:user) } let(:parent_group) { create(:group) } diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb index 10399bed655..d6eb060ea7e 100644 --- a/spec/services/groups/transfer_service_spec.rb +++ b/spec/services/groups/transfer_service_spec.rb @@ -35,10 +35,10 @@ RSpec.describe Groups::TransferService, :sidekiq_inline, feature_category: :subg end context 'handling packages' do - let_it_be(:group) { create(:group, :public) } - let_it_be(:new_group) { create(:group, :public) } + let_it_be(:group) { create(:group) } + let_it_be(:new_group) { create(:group) } - let(:project) { create(:project, :public, namespace: group) } + let_it_be(:project) { create(:project, namespace: group) } before do group.add_owner(user) @@ -46,46 +46,63 @@ RSpec.describe Groups::TransferService, :sidekiq_inline, feature_category: :subg end context 'with an npm package' do - before do - create(:npm_package, project: project) - end + let_it_be(:npm_package) { create(:npm_package, project: project, name: "@testscope/test") } - shared_examples 'transfer not allowed' do - it 'does not allow transfer when there is a root namespace change' do + shared_examples 'transfer allowed' do + it 'allows transfer' do transfer_service.execute(new_group) - expect(transfer_service.error).to eq('Transfer failed: Group contains projects with NPM packages.') - expect(group.parent).not_to eq(new_group) + expect(transfer_service.error).to be nil + expect(group.parent).to eq(new_group) end end - it_behaves_like 'transfer not allowed' + it_behaves_like 'transfer allowed' context 'with a project within subgroup' do let_it_be(:root_group) { create(:group) } let_it_be(:group) { create(:group, parent: root_group) } + let_it_be(:project) { create(:project, namespace: group) } before do root_group.add_owner(user) end - it_behaves_like 'transfer not allowed' + it_behaves_like 'transfer allowed' context 'without a root namespace change' do - let(:new_group) { create(:group, parent: root_group) } + let_it_be(:new_group) { create(:group, parent: root_group) } + + it_behaves_like 'transfer allowed' + end + + context 'with namespaced packages present' do + let_it_be(:package) { create(:npm_package, project: project, name: "@#{project.root_namespace.path}/test") } - it 'allows transfer' do + it 'does not allow transfer' do transfer_service.execute(new_group) - expect(transfer_service.error).to be nil - expect(group.parent).to eq(new_group) + expect(transfer_service.error).to eq('Transfer failed: Group contains projects with NPM packages scoped to the current root level group.') + expect(group.parent).not_to eq(new_group) + end + + context 'namespaced package is pending destruction' do + let!(:group) { create(:group) } + + before do + package.pending_destruction! + end + + it_behaves_like 'transfer allowed' end end context 'when transferring a group into a root group' do - let(:new_group) { nil } + let_it_be(:root_group) { create(:group) } + let_it_be(:group) { create(:group, parent: root_group) } + let_it_be(:new_group) { nil } - it_behaves_like 'transfer not allowed' + it_behaves_like 'transfer allowed' end end end @@ -458,7 +475,7 @@ RSpec.describe Groups::TransferService, :sidekiq_inline, feature_category: :subg it 'updates projects path' do new_parent_path = new_parent_group.path group.projects.each do |project| - expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.name}") + expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.path}") end end @@ -525,7 +542,7 @@ RSpec.describe Groups::TransferService, :sidekiq_inline, feature_category: :subg it 'updates projects path' do new_parent_path = new_parent_group.path group.projects.each do |project| - expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.name}") + expect(project.full_path).to eq("#{new_parent_path}/#{group.path}/#{project.path}") end end @@ -576,7 +593,7 @@ RSpec.describe Groups::TransferService, :sidekiq_inline, feature_category: :subg it 'updates projects path' do new_parent_path = "#{new_parent_group.path}/#{group.path}" subgroup1.projects.each do |project| - project_full_path = "#{new_parent_path}/#{project.namespace.path}/#{project.name}" + project_full_path = "#{new_parent_path}/#{project.namespace.path}/#{project.path}" expect(project.full_path).to eq(project_full_path) end end @@ -890,7 +907,7 @@ RSpec.describe Groups::TransferService, :sidekiq_inline, feature_category: :subg let(:subsub_project) { create(:project, group: subsubgroup) } let!(:contacts) { create_list(:contact, 4, group: root_group) } - let!(:organizations) { create_list(:organization, 2, group: root_group) } + let!(:organizations) { create_list(:crm_organization, 2, group: root_group) } before do create(:issue_customer_relations_contact, contact: contacts[0], issue: create(:issue, project: root_project)) diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index c758d3d5477..6baa8e5d6b6 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::UpdateService do +RSpec.describe Groups::UpdateService, feature_category: :subgroups do let!(:user) { create(:user) } let!(:private_group) { create(:group, :private) } let!(:internal_group) { create(:group, :internal) } diff --git a/spec/services/groups/update_shared_runners_service_spec.rb b/spec/services/groups/update_shared_runners_service_spec.rb index a29f73a71c2..48c81f109aa 100644 --- a/spec/services/groups/update_shared_runners_service_spec.rb +++ b/spec/services/groups/update_shared_runners_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::UpdateSharedRunnersService do +RSpec.describe Groups::UpdateSharedRunnersService, feature_category: :subgroups do let(:user) { create(:user) } let(:group) { create(:group) } let(:params) { {} } diff --git a/spec/services/groups/update_statistics_service_spec.rb b/spec/services/groups/update_statistics_service_spec.rb index 84b18b670a7..13a88839de0 100644 --- a/spec/services/groups/update_statistics_service_spec.rb +++ b/spec/services/groups/update_statistics_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::UpdateStatisticsService do +RSpec.describe Groups::UpdateStatisticsService, feature_category: :subgroups do let_it_be(:group, reload: true) { create(:group) } let(:statistics) { %w(wiki_size) } diff --git a/spec/services/ide/base_config_service_spec.rb b/spec/services/ide/base_config_service_spec.rb index ee57f2c18ec..ac57a13d7fc 100644 --- a/spec/services/ide/base_config_service_spec.rb +++ b/spec/services/ide/base_config_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ide::BaseConfigService do +RSpec.describe Ide::BaseConfigService, feature_category: :web_ide do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/services/ide/schemas_config_service_spec.rb b/spec/services/ide/schemas_config_service_spec.rb index f277b8e9954..b6f229edc78 100644 --- a/spec/services/ide/schemas_config_service_spec.rb +++ b/spec/services/ide/schemas_config_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ide::SchemasConfigService do +RSpec.describe Ide::SchemasConfigService, feature_category: :web_ide do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/services/ide/terminal_config_service_spec.rb b/spec/services/ide/terminal_config_service_spec.rb index 73614f28b06..76c8d9f2e6f 100644 --- a/spec/services/ide/terminal_config_service_spec.rb +++ b/spec/services/ide/terminal_config_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ide::TerminalConfigService do +RSpec.describe Ide::TerminalConfigService, feature_category: :web_ide do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/services/import/bitbucket_server_service_spec.rb b/spec/services/import/bitbucket_server_service_spec.rb index 555812ca9cf..ca554fb01c3 100644 --- a/spec/services/import/bitbucket_server_service_spec.rb +++ b/spec/services/import/bitbucket_server_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::BitbucketServerService do +RSpec.describe Import::BitbucketServerService, feature_category: :importers do let_it_be(:user) { create(:user) } let(:base_uri) { "https://test:7990" } @@ -93,7 +93,7 @@ RSpec.describe Import::BitbucketServerService do result = subject.execute(credentials) expect(result).to include( - message: "You don't have permissions to create this project", + message: "You don't have permissions to import this project", status: :error, http_status: :unauthorized ) diff --git a/spec/services/import/fogbugz_service_spec.rb b/spec/services/import/fogbugz_service_spec.rb index 027d0240a7a..e9c676dcd23 100644 --- a/spec/services/import/fogbugz_service_spec.rb +++ b/spec/services/import/fogbugz_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::FogbugzService do +RSpec.describe Import::FogbugzService, feature_category: :importers do let_it_be(:user) { create(:user) } let(:base_uri) { "https://test:7990" } @@ -18,6 +18,7 @@ RSpec.describe Import::FogbugzService do before do allow(subject).to receive(:authorized?).and_return(true) + stub_application_setting(import_sources: ['fogbugz']) end context 'when no repo is found' do @@ -61,7 +62,7 @@ RSpec.describe Import::FogbugzService do result = subject.execute(credentials) expect(result).to include( - message: "You don't have permissions to create this project", + message: "You don't have permissions to import this project", status: :error, http_status: :unauthorized ) diff --git a/spec/services/import/github/cancel_project_import_service_spec.rb b/spec/services/import/github/cancel_project_import_service_spec.rb index 77b8771ee65..d8ea303fa50 100644 --- a/spec/services/import/github/cancel_project_import_service_spec.rb +++ b/spec/services/import/github/cancel_project_import_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::Github::CancelProjectImportService do +RSpec.describe Import::Github::CancelProjectImportService, feature_category: :importers do subject(:import_cancel) { described_class.new(project, project.owner) } let_it_be(:user) { create(:user) } @@ -14,6 +14,18 @@ RSpec.describe Import::Github::CancelProjectImportService do it 'update import state to be canceled' do expect(import_cancel.execute).to eq({ status: :success, project: project }) end + + it 'tracks canceled imports' do + metrics_double = instance_double('Gitlab::Import::Metrics') + + expect(Gitlab::Import::Metrics) + .to receive(:new) + .with(:github_importer, project) + .and_return(metrics_double) + expect(metrics_double).to receive(:track_canceled_import) + + import_cancel.execute + end end context 'when import is finished' do diff --git a/spec/services/import/github/notes/create_service_spec.rb b/spec/services/import/github/notes/create_service_spec.rb index 57699def848..37cb903b66e 100644 --- a/spec/services/import/github/notes/create_service_spec.rb +++ b/spec/services/import/github/notes/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::Github::Notes::CreateService do +RSpec.describe Import::Github::Notes::CreateService, feature_category: :importers do it 'does not support quick actions' do project = create(:project, :repository) user = create(:user) diff --git a/spec/services/import/github_service_spec.rb b/spec/services/import/github_service_spec.rb index 293e247c140..fa8b2489599 100644 --- a/spec/services/import/github_service_spec.rb +++ b/spec/services/import/github_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::GithubService do +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' } } @@ -152,7 +152,8 @@ RSpec.describe Import::GithubService do { single_endpoint_issue_events_import: true, single_endpoint_notes_import: 'false', - attachments_import: false + attachments_import: false, + collaborators_import: true } end @@ -291,7 +292,7 @@ RSpec.describe Import::GithubService do { status: :error, http_status: :unprocessable_entity, - message: 'This namespace has already been taken. Choose a different one.' + message: 'You are not allowed to import projects in this namespace.' } end end diff --git a/spec/services/import/gitlab_projects/create_project_service_spec.rb b/spec/services/import/gitlab_projects/create_project_service_spec.rb index 59c384bad3c..a77e9bdfce1 100644 --- a/spec/services/import/gitlab_projects/create_project_service_spec.rb +++ b/spec/services/import/gitlab_projects/create_project_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Import::GitlabProjects::CreateProjectService, :aggregate_failures do +RSpec.describe ::Import::GitlabProjects::CreateProjectService, :aggregate_failures, feature_category: :importers do let(:fake_file_acquisition_strategy) do Class.new do attr_reader :errors @@ -35,6 +35,7 @@ RSpec.describe ::Import::GitlabProjects::CreateProjectService, :aggregate_failur before do stub_const('FakeStrategy', fake_file_acquisition_strategy) + stub_application_setting(import_sources: ['gitlab_project']) end describe 'validation' do @@ -127,7 +128,7 @@ RSpec.describe ::Import::GitlabProjects::CreateProjectService, :aggregate_failur expect(response.payload).to eq(other_errors: []) end - context 'when the project contains multilple errors' do + context 'when the project contains multiple errors' do it 'fails to create a project' do params.merge!(name: '_ an invalid name _', path: '_ an invalid path _') @@ -137,10 +138,13 @@ RSpec.describe ::Import::GitlabProjects::CreateProjectService, :aggregate_failur expect(response).to be_error expect(response.http_status).to eq(:bad_request) - expect(response.message) - .to eq(%{Project namespace path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'}) + expect(response.message).to eq( + 'Project namespace path must not start or end with a special character and must not contain consecutive ' \ + 'special characters.' + ) expect(response.payload).to eq( other_errors: [ + %{Project namespace path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'}, %{Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'}, %{Path must not start or end with a special character and must not contain consecutive special characters.} ]) diff --git a/spec/services/import/gitlab_projects/file_acquisition_strategies/file_upload_spec.rb b/spec/services/import/gitlab_projects/file_acquisition_strategies/file_upload_spec.rb index 3c788138157..3a94ed02dd5 100644 --- a/spec/services/import/gitlab_projects/file_acquisition_strategies/file_upload_spec.rb +++ b/spec/services/import/gitlab_projects/file_acquisition_strategies/file_upload_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Import::GitlabProjects::FileAcquisitionStrategies::FileUpload, :aggregate_failures do +RSpec.describe ::Import::GitlabProjects::FileAcquisitionStrategies::FileUpload, :aggregate_failures, feature_category: :importers do let(:file) { UploadedFile.new(File.join('spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz')) } describe 'validation' do diff --git a/spec/services/import/gitlab_projects/file_acquisition_strategies/remote_file_s3_spec.rb b/spec/services/import/gitlab_projects/file_acquisition_strategies/remote_file_s3_spec.rb index d9042e95149..411e2ec5286 100644 --- a/spec/services/import/gitlab_projects/file_acquisition_strategies/remote_file_s3_spec.rb +++ b/spec/services/import/gitlab_projects/file_acquisition_strategies/remote_file_s3_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Import::GitlabProjects::FileAcquisitionStrategies::RemoteFileS3, :aggregate_failures do +RSpec.describe ::Import::GitlabProjects::FileAcquisitionStrategies::RemoteFileS3, :aggregate_failures, feature_category: :importers do let(:region_name) { 'region_name' } let(:bucket_name) { 'bucket_name' } let(:file_key) { 'file_key' } diff --git a/spec/services/import/prepare_service_spec.rb b/spec/services/import/prepare_service_spec.rb index 0097198f7a9..fcb90575d96 100644 --- a/spec/services/import/prepare_service_spec.rb +++ b/spec/services/import/prepare_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::PrepareService do +RSpec.describe Import::PrepareService, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } diff --git a/spec/services/import/validate_remote_git_endpoint_service_spec.rb b/spec/services/import/validate_remote_git_endpoint_service_spec.rb index 221ac2cd73a..1d2b3975832 100644 --- a/spec/services/import/validate_remote_git_endpoint_service_spec.rb +++ b/spec/services/import/validate_remote_git_endpoint_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::ValidateRemoteGitEndpointService do +RSpec.describe Import::ValidateRemoteGitEndpointService, feature_category: :importers do include StubRequests let_it_be(:base_url) { 'http://demo.host/path' } @@ -35,6 +35,28 @@ RSpec.describe Import::ValidateRemoteGitEndpointService do end end + context 'when uri is using an invalid protocol' do + subject { described_class.new(url: 'ssh://demo.host/repo') } + + it 'reports error when invalid URL is provided' do + result = subject.execute + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + end + end + + context 'when uri is invalid' do + subject { described_class.new(url: 'http:example.com') } + + it 'reports error when invalid URL is provided' do + result = subject.execute + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + end + end + context 'when receiving HTTP response' do subject { described_class.new(url: base_url) } diff --git a/spec/services/import_csv/base_service_spec.rb b/spec/services/import_csv/base_service_spec.rb index 0c0ed40ff4d..93fff0d546a 100644 --- a/spec/services/import_csv/base_service_spec.rb +++ b/spec/services/import_csv/base_service_spec.rb @@ -24,40 +24,65 @@ RSpec.describe ImportCsv::BaseService, feature_category: :importers do it_behaves_like 'abstract method', :validate_headers_presence!, "any" it_behaves_like 'abstract method', :create_object_class - describe '#detect_col_sep' do - context 'when header contains invalid separators' do - it 'raises error' do - header = 'Name&email' + context 'when given a class' do + let(:importer_klass) do + Class.new(described_class) do + def attributes_for(row) + { title: row[:title] } + end - expect { subject.send(:detect_col_sep, header) }.to raise_error(CSV::MalformedCSVError) - end - end + def validate_headers_presence!(headers) + raise CSV::MalformedCSVError.new("Missing required headers", 1) unless headers.present? + end - context 'when header is valid' do - shared_examples 'header with valid separators' do - let(:header) { "Name#{separator}email" } + def create_object_class + Class.new + end - it 'returns separator value' do - expect(subject.send(:detect_col_sep, header)).to eq(separator) + def email_results_to_user + # no-op end end + end - context 'with ; as separator' do - let(:separator) { ';' } + let(:service) do + uploader = FileUploader.new(project) + uploader.store!(file) - it_behaves_like 'header with valid separators' - end + importer_klass.new(user, project, uploader) + end + + subject { service.execute } + + it_behaves_like 'correctly handles invalid files' + + describe '#detect_col_sep' do + using RSpec::Parameterized::TableSyntax - context 'with \t as separator' do - let(:separator) { "\t" } + let(:file) { double } - it_behaves_like 'header with valid separators' + before do + allow(service).to receive_message_chain('csv_data.lines.first').and_return(header) end - context 'with , as separator' do - let(:separator) { ',' } + where(:sep_character, :valid) do + '&' | false + '?' | false + ';' | true + ',' | true + "\t" | true + end + + with_them do + let(:header) { "Name#{sep_character}email" } - it_behaves_like 'header with valid separators' + it 'responds appropriately' do + if valid + expect(service.send(:detect_col_sep)).to eq sep_character + else + expect { service.send(:detect_col_sep) }.to raise_error(CSV::MalformedCSVError) + end + end end end end diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb index 2bcdfa6dd8f..7b638b4948b 100644 --- a/spec/services/import_export_clean_up_service_spec.rb +++ b/spec/services/import_export_clean_up_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ImportExportCleanUpService do +RSpec.describe ImportExportCleanUpService, feature_category: :importers do describe '#execute' do let(:service) { described_class.new } diff --git a/spec/services/incident_management/incidents/create_service_spec.rb b/spec/services/incident_management/incidents/create_service_spec.rb index 7db762b9c5b..e6ded379434 100644 --- a/spec/services/incident_management/incidents/create_service_spec.rb +++ b/spec/services/incident_management/incidents/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IncidentManagement::Incidents::CreateService do +RSpec.describe IncidentManagement::Incidents::CreateService, feature_category: :incident_management do let_it_be(:project) { create(:project) } let_it_be(:user) { User.alert_bot } 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 4b0c8d9113c..9b1994af1bb 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 @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe IncidentManagement::IssuableEscalationStatuses::AfterUpdateService do +RSpec.describe IncidentManagement::IssuableEscalationStatuses::AfterUpdateService, + feature_category: :incident_management do let_it_be(:current_user) { create(:user) } let_it_be(:escalation_status, reload: true) { create(:incident_management_issuable_escalation_status, :triggered) } let_it_be(:issue, reload: true) { escalation_status.issue } diff --git a/spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb index b5c5238d483..56a159f452c 100644 --- a/spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb +++ b/spec/services/incident_management/issuable_escalation_statuses/build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IncidentManagement::IssuableEscalationStatuses::BuildService do +RSpec.describe IncidentManagement::IssuableEscalationStatuses::BuildService, feature_category: :incident_management do let_it_be(:project) { create(:project) } let_it_be(:incident, reload: true) { create(:incident, project: project) } diff --git a/spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb index b6ae03a19fe..e6c63d63123 100644 --- a/spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb +++ b/spec/services/incident_management/issuable_escalation_statuses/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do +RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService, feature_category: :incident_management do let_it_be(:project) { create(:project) } let(:incident) { create(:incident, project: project) } diff --git a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb index e8208c410d5..3f3174d0112 100644 --- a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb +++ b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe IncidentManagement::IssuableEscalationStatuses::PrepareUpdateService, factory_default: :keep do +RSpec.describe IncidentManagement::IssuableEscalationStatuses::PrepareUpdateService, factory_default: :keep, + feature_category: :incident_management do let_it_be(:project) { create_default(:project) } let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, :triggered) } let_it_be(:user_with_permissions) { create(:user) } diff --git a/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb b/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb index 2fda789cf56..caa5ee495b7 100644 --- a/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb +++ b/spec/services/incident_management/pager_duty/create_incident_issue_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IncidentManagement::PagerDuty::CreateIncidentIssueService do +RSpec.describe IncidentManagement::PagerDuty::CreateIncidentIssueService, feature_category: :incident_management do let_it_be(:project, reload: true) { create(:project) } let_it_be(:user) { User.alert_bot } diff --git a/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb b/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb index e2aba0b61af..06f423bc63c 100644 --- a/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb +++ b/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IncidentManagement::PagerDuty::ProcessWebhookService do +RSpec.describe IncidentManagement::PagerDuty::ProcessWebhookService, feature_category: :incident_management do let_it_be(:project, reload: true) { create(:project) } describe '#execute' do diff --git a/spec/services/incident_management/timeline_event_tags/create_service_spec.rb b/spec/services/incident_management/timeline_event_tags/create_service_spec.rb index c1b993ce3d9..b21a116d5f9 100644 --- a/spec/services/incident_management/timeline_event_tags/create_service_spec.rb +++ b/spec/services/incident_management/timeline_event_tags/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IncidentManagement::TimelineEventTags::CreateService do +RSpec.describe IncidentManagement::TimelineEventTags::CreateService, feature_category: :incident_management do let_it_be(:user_with_permissions) { create(:user) } let_it_be(:user_without_permissions) { create(:user) } let_it_be_with_reload(:project) { create(:project) } diff --git a/spec/services/incident_management/timeline_events/create_service_spec.rb b/spec/services/incident_management/timeline_events/create_service_spec.rb index fa5f4c64a43..fff6241f083 100644 --- a/spec/services/incident_management/timeline_events/create_service_spec.rb +++ b/spec/services/incident_management/timeline_events/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IncidentManagement::TimelineEvents::CreateService do +RSpec.describe IncidentManagement::TimelineEvents::CreateService, feature_category: :incident_management do let_it_be(:user_with_permissions) { create(:user) } let_it_be(:user_without_permissions) { create(:user) } let_it_be(:project) { create(:project) } @@ -57,7 +57,6 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do it_behaves_like 'an incident management tracked event', :incident_management_timeline_event_created it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { project.namespace.reload } let(:category) { described_class.to_s } let(:user) { current_user } @@ -286,7 +285,6 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do it_behaves_like 'an incident management tracked event', :incident_management_timeline_event_created it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { project.namespace.reload } let(:category) { described_class.to_s } let(:user) { current_user } diff --git a/spec/services/incident_management/timeline_events/destroy_service_spec.rb b/spec/services/incident_management/timeline_events/destroy_service_spec.rb index f90ff72a2bf..78f6659beec 100644 --- a/spec/services/incident_management/timeline_events/destroy_service_spec.rb +++ b/spec/services/incident_management/timeline_events/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IncidentManagement::TimelineEvents::DestroyService do +RSpec.describe IncidentManagement::TimelineEvents::DestroyService, feature_category: :incident_management do let_it_be(:user_with_permissions) { create(:user) } let_it_be(:user_without_permissions) { create(:user) } let_it_be(:project) { create(:project) } @@ -67,7 +67,6 @@ RSpec.describe IncidentManagement::TimelineEvents::DestroyService do it_behaves_like 'an incident management tracked event', :incident_management_timeline_event_deleted it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { project.namespace.reload } let(:category) { described_class.to_s } let(:user) { current_user } diff --git a/spec/services/incident_management/timeline_events/update_service_spec.rb b/spec/services/incident_management/timeline_events/update_service_spec.rb index ebaa4dde7a2..c38126baa65 100644 --- a/spec/services/incident_management/timeline_events/update_service_spec.rb +++ b/spec/services/incident_management/timeline_events/update_service_spec.rb @@ -50,7 +50,6 @@ RSpec.describe IncidentManagement::TimelineEvents::UpdateService, feature_catego it_behaves_like 'an incident management tracked event', :incident_management_timeline_event_edited it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { project.namespace.reload } let(:category) { described_class.to_s } let(:action) { 'incident_management_timeline_event_edited' } diff --git a/spec/services/integrations/propagate_service_spec.rb b/spec/services/integrations/propagate_service_spec.rb index c971c4a0ad0..0267b1b0ed0 100644 --- a/spec/services/integrations/propagate_service_spec.rb +++ b/spec/services/integrations/propagate_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Integrations::PropagateService do +RSpec.describe Integrations::PropagateService, feature_category: :integrations do describe '.propagate' do include JiraIntegrationHelpers diff --git a/spec/services/integrations/slack_event_service_spec.rb b/spec/services/integrations/slack_event_service_spec.rb new file mode 100644 index 00000000000..17433aee329 --- /dev/null +++ b/spec/services/integrations/slack_event_service_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackEventService, feature_category: :integrations do + describe '#execute' do + subject(:execute) { described_class.new(params).execute } + + let(:params) do + { + type: 'event_callback', + event: { + type: 'app_home_opened', + foo: 'bar' + } + } + end + + it 'queues a worker and returns success response' do + expect(Integrations::SlackEventWorker).to receive(:perform_async) + .with( + { + slack_event: 'app_home_opened', + params: { + event: { + foo: 'bar' + } + } + } + ) + expect(execute.payload).to eq({}) + is_expected.to be_success + end + + context 'when event a url verification request' do + let(:params) { { type: 'url_verification', foo: 'bar' } } + + it 'executes the service instead of queueing a worker and returns success response' do + expect(Integrations::SlackEventWorker).not_to receive(:perform_async) + expect_next_instance_of(Integrations::SlackEvents::UrlVerificationService, { foo: 'bar' }) do |service| + expect(service).to receive(:execute).and_return({ baz: 'qux' }) + end + expect(execute.payload).to eq({ baz: 'qux' }) + is_expected.to be_success + end + end + + context 'when event is unknown' do + let(:params) { super().merge(event: { type: 'foo' }) } + + it 'raises an error' do + expect { execute }.to raise_error(described_class::UnknownEventError) + end + end + end +end diff --git a/spec/services/integrations/slack_events/app_home_opened_service_spec.rb b/spec/services/integrations/slack_events/app_home_opened_service_spec.rb new file mode 100644 index 00000000000..0eb4c019e0a --- /dev/null +++ b/spec/services/integrations/slack_events/app_home_opened_service_spec.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackEvents::AppHomeOpenedService, feature_category: :integrations do + describe '#execute' do + let_it_be(:slack_installation) { create(:slack_integration) } + + let(:slack_workspace_id) { slack_installation.team_id } + let(:slack_user_id) { 'U0123ABCDEF' } + let(:api_url) { "#{Slack::API::BASE_URL}/views.publish" } + let(:api_response) { { ok: true } } + let(:params) do + { + team_id: slack_workspace_id, + event: { user: slack_user_id }, + event_id: 'Ev03SA75UJKB' + } + end + + subject(:execute) { described_class.new(params).execute } + + before do + stub_request(:post, api_url) + .to_return( + status: 200, + body: api_response.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + end + + shared_examples 'there is no bot token' do + it 'does not call the Slack API, logs info, and returns a success response' do + expect(Gitlab::IntegrationsLogger).to receive(:info).with( + { + slack_user_id: slack_user_id, + slack_workspace_id: slack_workspace_id, + message: 'SlackInstallation record has no bot token' + } + ) + + is_expected.to be_success + end + end + + it 'calls the Slack API correctly and returns a success response' do + mock_view = { type: 'home', blocks: [] } + + expect_next_instance_of(Slack::BlockKit::AppHomeOpened) do |ui| + expect(ui).to receive(:build).and_return(mock_view) + end + + is_expected.to be_success + + expect(WebMock).to have_requested(:post, api_url).with( + body: { + user_id: slack_user_id, + view: mock_view + }, + headers: { + 'Authorization' => "Bearer #{slack_installation.bot_access_token}", + 'Content-Type' => 'application/json; charset=utf-8' + }) + end + + context 'when the slack installation is a legacy record' do + let_it_be(:slack_installation) { create(:slack_integration, :legacy) } + + it_behaves_like 'there is no bot token' + end + + context 'when the slack installation cannot be found' do + let(:slack_workspace_id) { non_existing_record_id } + + it_behaves_like 'there is no bot token' + end + + context 'when the Slack API call raises an HTTP exception' do + before do + allow(Gitlab::HTTP).to receive(:post).and_raise(Errno::ECONNREFUSED, 'error message') + end + + it 'tracks the exception and returns an error response' do + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + Errno::ECONNREFUSED.new('HTTP exception when calling Slack API'), + { + slack_user_id: slack_user_id, + slack_workspace_id: slack_workspace_id + } + ) + is_expected.to be_error + end + end + + context 'when the Slack API returns an error' do + let(:api_response) { { ok: false, foo: 'bar' } } + + it 'tracks the exception and returns an error response' do + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + StandardError.new('Slack API returned an error'), + { + slack_user_id: slack_user_id, + slack_workspace_id: slack_workspace_id, + response: api_response.with_indifferent_access + } + ) + is_expected.to be_error + end + end + end +end diff --git a/spec/services/integrations/slack_events/url_verification_service_spec.rb b/spec/services/integrations/slack_events/url_verification_service_spec.rb new file mode 100644 index 00000000000..0d668acafb9 --- /dev/null +++ b/spec/services/integrations/slack_events/url_verification_service_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackEvents::UrlVerificationService, feature_category: :integrations do + describe '#execute' do + it 'returns the challenge' do + expect(described_class.new({ challenge: 'foo' }).execute).to eq({ challenge: 'foo' }) + end + end +end diff --git a/spec/services/integrations/slack_interaction_service_spec.rb b/spec/services/integrations/slack_interaction_service_spec.rb new file mode 100644 index 00000000000..599320c7986 --- /dev/null +++ b/spec/services/integrations/slack_interaction_service_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackInteractionService, feature_category: :integrations do + describe '#execute' do + subject(:execute) { described_class.new(params).execute } + + let(:params) do + { + type: slack_interaction, + foo: 'bar' + } + end + + context 'when view is closed' do + let(:slack_interaction) { 'view_closed' } + + it 'executes the correct service' do + view_closed_service = described_class::INTERACTIONS['view_closed'] + + expect_next_instance_of(view_closed_service, { foo: 'bar' }) do |service| + expect(service).to receive(:execute).and_return(ServiceResponse.success) + end + + execute + end + end + + context 'when view is submitted' do + let(:slack_interaction) { 'view_submission' } + + it 'executes the submission service' do + view_submission_service = described_class::INTERACTIONS['view_submission'] + + expect_next_instance_of(view_submission_service, { foo: 'bar' }) do |service| + expect(service).to receive(:execute).and_return(ServiceResponse.success) + end + + execute + end + end + + context 'when block action service is submitted' do + let(:slack_interaction) { 'block_actions' } + + it 'executes the block actions service' do + block_action_service = described_class::INTERACTIONS['block_actions'] + + expect_next_instance_of(block_action_service, { foo: 'bar' }) do |service| + expect(service).to receive(:execute).and_return(ServiceResponse.success) + end + + execute + end + end + + context 'when slack_interaction is not known' do + let(:slack_interaction) { 'foo' } + + it 'raises an error and does not execute a service class' do + described_class::INTERACTIONS.each_value do |service_class| + expect(service_class).not_to receive(:new) + end + + expect { execute }.to raise_error(described_class::UnknownInteractionError) + end + end + end +end diff --git a/spec/services/integrations/slack_interactions/block_action_service_spec.rb b/spec/services/integrations/slack_interactions/block_action_service_spec.rb new file mode 100644 index 00000000000..9a188ddcfe4 --- /dev/null +++ b/spec/services/integrations/slack_interactions/block_action_service_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackInteractions::BlockActionService, feature_category: :integrations do + describe '#execute' do + let_it_be(:slack_installation) { create(:slack_integration) } + + let(:params) do + { + view: { + team_id: slack_installation.team_id + }, + actions: [{ + action_id: action_id + }] + } + end + + subject(:execute) { described_class.new(params).execute } + + context 'when action_id is incident_management_project' do + let(:action_id) { 'incident_management_project' } + + it 'executes the correct handler' do + project_handler = described_class::ALLOWED_UPDATES_HANDLERS['incident_management_project'] + + expect_next_instance_of(project_handler, params, params[:actions].first) do |handler| + expect(handler).to receive(:execute).and_return(ServiceResponse.success) + end + + execute + end + end + + context 'when action_id is not known' do + let(:action_id) { 'random' } + + it 'does not execute the handlers' do + described_class::ALLOWED_UPDATES_HANDLERS.each_value do |handler_class| + expect(handler_class).not_to receive(:new) + end + + execute + end + end + end +end diff --git a/spec/services/integrations/slack_interactions/incident_management/incident_modal_closed_service_spec.rb b/spec/services/integrations/slack_interactions/incident_management/incident_modal_closed_service_spec.rb new file mode 100644 index 00000000000..64cddf9a66b --- /dev/null +++ b/spec/services/integrations/slack_interactions/incident_management/incident_modal_closed_service_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackInteractions::IncidentManagement::IncidentModalClosedService, + feature_category: :integrations do + describe '#execute' do + let_it_be(:request_body) do + { + replace_original: 'true', + text: 'Incident creation cancelled.' + } + end + + let(:params) do + { + view: { + private_metadata: 'https://api.slack.com/id/1234' + } + } + end + + let(:service) { described_class.new(params) } + + before do + allow(Gitlab::HTTP).to receive(:post).and_return({ ok: true }) + end + + context 'when executed' do + it 'makes the POST call and closes the modal' do + expect(Gitlab::HTTP).to receive(:post).with( + 'https://api.slack.com/id/1234', + body: Gitlab::Json.dump(request_body), + headers: { 'Content-Type' => 'application/json' } + ) + + service.execute + end + end + + context 'when the POST call raises an HTTP exception' do + before do + allow(Gitlab::HTTP).to receive(:post).and_raise(Errno::ECONNREFUSED, 'error message') + end + + it 'tracks the exception and returns an error response' do + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + Errno::ECONNREFUSED.new('HTTP exception when calling Slack API'), + { + params: params + } + ) + + service.execute + end + end + + context 'when response is not ok' do + before do + allow(Gitlab::HTTP).to receive(:post).and_return({ ok: false }) + end + + it 'returns error response and tracks the exception' do + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + StandardError.new('Something went wrong while closing the incident form.'), + { + response: { ok: false }, + params: params + } + ) + + service.execute + end + end + end +end diff --git a/spec/services/integrations/slack_interactions/incident_management/incident_modal_opened_service_spec.rb b/spec/services/integrations/slack_interactions/incident_management/incident_modal_opened_service_spec.rb new file mode 100644 index 00000000000..2a1aa0aabec --- /dev/null +++ b/spec/services/integrations/slack_interactions/incident_management/incident_modal_opened_service_spec.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackInteractions::IncidentManagement::IncidentModalOpenedService, + feature_category: :incident_management do + describe '#execute' do + let_it_be(:slack_installation) { create(:slack_integration) } + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user, developer_projects: [project]) } + let_it_be(:trigger_id) { '12345.98765.abcd2358fdea' } + + let(:slack_workspace_id) { slack_installation.team_id } + let(:response_url) { 'https://api.slack.com/id/123' } + let(:api_url) { "#{Slack::API::BASE_URL}/views.open" } + let(:mock_modal) { { type: 'modal', blocks: [] } } + let(:params) do + { + team_id: slack_workspace_id, + response_url: response_url, + trigger_id: trigger_id + } + end + + before do + response = { + id: '123', + state: { + values: { + project_and_severity_selector: { + incident_management_project: { + selected_option: { + value: project.id.to_s + } + } + } + } + } + } + stub_request(:post, api_url) + .to_return( + status: 200, + body: Gitlab::Json.dump({ ok: true, view: response }), + headers: { 'Content-Type' => 'application/json' } + ) + end + + subject { described_class.new(slack_installation, user, params) } + + context 'when triggered' do + it 'opens the modal' do + expect_next_instance_of(Slack::BlockKit::IncidentManagement::IncidentModalOpened) do |ui| + expect(ui).to receive(:build).and_return(mock_modal) + end + + expect(Rails.cache).to receive(:write).with( + 'slack:incident_modal_opened:123', project.id.to_s, { expires_in: 5.minutes }) + + response = subject.execute + + expect(WebMock).to have_requested(:post, api_url).with( + body: { + trigger_id: trigger_id, + view: mock_modal + }, + headers: { + 'Authorization' => "Bearer #{slack_installation.bot_access_token}", + 'Content-Type' => 'application/json; charset=utf-8' + }) + + expect(response.message).to eq('Please complete the incident creation form.') + end + end + + context 'when there are no projects with slack integration' do + let(:params) do + { + team_id: 'some_random_id', + response_url: response_url, + trigger_id: trigger_id + } + end + + let(:user) { create(:user) } + + it 'does not open the modal' do + response = subject.execute + + expect(Rails.cache).not_to receive(:write) + expect(response.message).to be('You do not have access to any projects for creating incidents.') + end + end + + context 'when Slack API call raises an HTTP exception' do + before do + allow(Gitlab::HTTP).to receive(:post).and_raise(Errno::ECONNREFUSED, 'error message') + end + + it 'tracks the exception and returns an error response' do + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + Errno::ECONNREFUSED.new('HTTP exception when calling Slack API'), + { + slack_workspace_id: slack_workspace_id + } + ) + + expect(Rails.cache).not_to receive(:write) + expect(subject.execute).to be_error + end + end + + context 'when api returns an error' do + before do + stub_request(:post, api_url) + .to_return( + status: 404, + body: Gitlab::Json.dump({ ok: false }), + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns error when called' do + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + StandardError.new('Something went wrong while opening the incident form.'), + { + response: { "ok" => false }, + slack_workspace_id: slack_workspace_id, + slack_user_id: slack_installation.user_id + } + ) + + expect(Rails.cache).not_to receive(:write) + response = subject.execute + + expect(response.message).to eq('Something went wrong while opening the incident form.') + end + end + end +end diff --git a/spec/services/integrations/slack_interactions/incident_management/incident_modal_submit_service_spec.rb b/spec/services/integrations/slack_interactions/incident_management/incident_modal_submit_service_spec.rb new file mode 100644 index 00000000000..adaeadaa997 --- /dev/null +++ b/spec/services/integrations/slack_interactions/incident_management/incident_modal_submit_service_spec.rb @@ -0,0 +1,296 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackInteractions::IncidentManagement::IncidentModalSubmitService, + feature_category: :incident_management do + include Gitlab::Routing + + describe '#execute' do + let_it_be(:slack_installation) { create(:slack_integration) } + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:api_url) { 'https://api.slack.com/id/1234' } + + let_it_be(:chat_name) do + create(:chat_name, + user: user, + team_id: slack_installation.team_id, + chat_id: slack_installation.user_id + ) + end + + # Setting below params as they are optional, have added values wherever required in specs + let(:zoom_link) { '' } + let(:severity) { {} } + let(:status) { '' } + let(:assignee_id) { nil } + let(:selected_label_ids) { [] } + let(:label_ids) { { selected_options: selected_label_ids } } + let(:confidential_selected_options) { [] } + let(:confidential) { { selected_options: confidential_selected_options } } + let(:title) { 'Incident title' } + + let(:zoom) do + { + link: { + value: zoom_link + } + } + end + + let(:params) do + { + team: { + id: slack_installation.team_id + }, + user: { + id: slack_installation.user_id + }, + view: { + private_metadata: api_url, + state: { + values: { + title_input: { + title: { + value: title + } + }, + incident_description: { + description: { + value: 'Incident description' + } + }, + project_and_severity_selector: { + incident_management_project: { + selected_option: { + value: project.id.to_s + } + }, + severity: severity + }, + confidentiality: { + confidential: confidential + }, + zoom: zoom, + status_and_assignee_selector: { + status: { + selected_option: { + value: status + } + }, + assignee: { + selected_option: { + value: assignee_id + } + } + }, + label_selector: { + labels: label_ids + } + } + } + } + } + end + + subject(:execute_service) { described_class.new(params).execute } + + shared_examples 'error in creation' do |error_message| + it 'returns error and raises exception' do + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + described_class::IssueCreateError.new(error_message), + { + slack_workspace_id: slack_installation.team_id, + slack_user_id: slack_installation.user_id + } + ) + + expect(Gitlab::HTTP).to receive(:post) + .with( + api_url, + body: Gitlab::Json.dump( + { + replace_original: 'true', + text: 'There was a problem creating the incident. Please try again.' + } + ), + headers: { 'Content-Type' => 'application/json' } + ) + + response = execute_service + + expect(response).to be_error + expect(response.message).to eq(error_message) + end + end + + context 'when user has permissions to create incidents' do + let(:api_response) { '{"ok":true}' } + + before do + project.add_developer(user) + stub_request(:post, api_url) + .to_return(body: api_response, headers: { 'Content-Type' => 'application/json' }) + end + + context 'with markup string in title' do + let(:title) { '<a href="url">incident title</a>' } + let(:incident) { create(:incident, title: title, project: project) } + + before do + allow_next_instance_of(Issues::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.success(payload: { issue: incident, error: [] }) + ) + end + end + + it 'strips the markup and saves sends the title' do + expect(Gitlab::HTTP).to receive(:post) + .with( + api_url, + body: Gitlab::Json.dump( + { + replace_original: 'true', + text: "New incident has been created: " \ + "<#{issue_url(incident)}|#{incident.to_reference} - a href=\"url\"incident title/a>. " + } + ), + headers: { 'Content-Type' => 'application/json' } + ).and_return(api_response) + + execute_service + end + end + + context 'with non-optional params' do + it 'creates incident' do + response = execute_service + incident = response[:incident] + + expect(response).to be_success + expect(incident).not_to be_nil + expect(incident.description).to eq('Incident description') + expect(incident.author).to eq(user) + expect(incident.severity).to eq('unknown') + expect(incident.confidential).to be_falsey + expect(incident.escalation_status).to be_triggered + end + + it 'sends incident link to slack' do + execute_service + + expect(WebMock).to have_requested(:post, api_url) + end + end + + context 'with zoom_link' do + let(:zoom_link) { 'https://gitlab.zoom.us/j/1234' } + + it 'sets zoom link as quick action' do + incident = execute_service[:incident] + zoom_meeting = ZoomMeeting.find_by_issue_id(incident.id) + + expect(incident.description).to eq("Incident description") + expect(zoom_meeting.url).to eq(zoom_link) + end + end + + context 'with confidential and severity' do + let(:confidential_selected_options) { ['confidential'] } + let(:severity) do + { + selected_option: { + value: 'high' + } + } + end + + it 'sets confidential and severity' do + incident = execute_service[:incident] + + expect(incident.confidential).to be_truthy + expect(incident.severity).to eq('high') + end + end + + context 'with incident status' do + let(:status) { 'resolved' } + + it 'sets the incident status' do + incident = execute_service[:incident] + + expect(incident.escalation_status).to be_resolved + end + end + + context 'with assignee id' do + let(:assignee_id) { user.id.to_s } + + it 'assigns the incident to user' do + incident = execute_service[:incident] + + expect(incident.assignees).to contain_exactly(user) + end + + context 'when user is not a member of the project' do + let(:assignee_id) { create(:user).id.to_s } + + it 'does not assign the user' do + incident = execute_service[:incident] + + expect(incident.assignees).to be_empty + end + end + end + + context 'with label ids' do + let_it_be(:project_label1) { create(:label, project: project, title: 'Label 1') } + let_it_be(:project_label2) { create(:label, project: project, title: 'Label 2') } + + let(:selected_label_ids) do + [ + { value: project_label1.id.to_s }, + { value: project_label2.id.to_s } + ] + end + + it 'assigns the label to the incident' do + incident = execute_service[:incident] + + expect(incident.labels).to contain_exactly(project_label1, project_label2) + end + end + + context 'when response is not ok' do + let(:api_response) { '{"ok":false}' } + + it 'returns error response and tracks the exception' do + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + StandardError.new('Something went wrong when sending the incident link to Slack.'), + { + response: { 'ok' => false }, + slack_workspace_id: slack_installation.team_id, + slack_user_id: slack_installation.user_id + } + ) + + execute_service + end + end + + context 'when incident creation fails' do + let(:title) { '' } + + it_behaves_like 'error in creation', "Title can't be blank" + end + end + + context 'when user does not have permission to create incidents' do + it_behaves_like 'error in creation', 'Operation not allowed' + end + end +end diff --git a/spec/services/integrations/slack_interactions/slack_block_actions/incident_management/project_update_handler_spec.rb b/spec/services/integrations/slack_interactions/slack_block_actions/incident_management/project_update_handler_spec.rb new file mode 100644 index 00000000000..5edffc99977 --- /dev/null +++ b/spec/services/integrations/slack_interactions/slack_block_actions/incident_management/project_update_handler_spec.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackInteractions::SlackBlockActions::IncidentManagement::ProjectUpdateHandler, + feature_category: :incident_management do + describe '#execute' do + let_it_be(:slack_installation) { create(:slack_integration) } + let_it_be(:old_project) { create(:project) } + let_it_be(:new_project) { create(:project) } + let_it_be(:user) { create(:user, developer_projects: [old_project, new_project]) } + let_it_be(:chat_name) { create(:chat_name, user: user) } + let_it_be(:api_url) { "#{Slack::API::BASE_URL}/views.update" } + + let(:block) do + { + block_id: 'incident_description', + element: { + initial_value: '' + } + } + end + + let(:view) do + { + id: 'V04EQH1SP27', + team_id: slack_installation.team_id, + blocks: [block] + } + end + + let(:action) do + { + selected_option: { + value: new_project.id.to_s + } + } + end + + let(:params) do + { + view: view, + user: { + id: slack_installation.user_id + } + } + end + + before do + allow_next_instance_of(ChatNames::FindUserService) do |user_service| + allow(user_service).to receive(:execute).and_return(chat_name) + end + + stub_request(:post, api_url) + .to_return( + status: 200, + body: Gitlab::Json.dump({ ok: true }), + headers: { 'Content-Type' => 'application/json' } + ) + end + + shared_examples 'does not make api call' do + it 'does not make the api call and returns nil' do + expect(Rails.cache).to receive(:read).and_return(project.id.to_s) + expect(Rails.cache).not_to receive(:write) + + expect(execute).to be_nil + expect(WebMock).not_to have_requested(:post, api_url) + end + end + + subject(:execute) { described_class.new(params, action).execute } + + context 'when project is updated' do + it 'returns success response and updates cache' do + expect(Rails.cache).to receive(:read).and_return(old_project.id.to_s) + expect(Rails.cache).to receive(:write).with( + "slack:incident_modal_opened:#{view[:id]}", + new_project.id.to_s, + expires_in: 5.minutes + ) + + expect(execute.message).to eq('Modal updated') + + updated_block = block.dup + updated_block[:block_id] = new_project.id.to_s + view[:blocks] = [updated_block] + + expect(WebMock).to have_requested(:post, api_url).with( + body: { + view_id: view[:id], + view: view.except!(:team_id, :id) + }, + headers: { + 'Authorization' => "Bearer #{slack_installation.bot_access_token}", + 'Content-Type' => 'application/json; charset=utf-8' + }) + end + end + + context 'when project is unchanged' do + it_behaves_like 'does not make api call' do + let(:project) { new_project } + end + end + + context 'when user does not have permission to read a project' do + it_behaves_like 'does not make api call' do + let(:project) { create(:project) } + end + end + + context 'when api response is not ok' do + before do + stub_request(:post, api_url) + .to_return( + status: 404, + body: Gitlab::Json.dump({ ok: false }), + headers: { 'Content-Type' => 'application/json' } + ) + end + + it 'returns error response' do + expect(Rails.cache).to receive(:read).and_return(old_project.id.to_s) + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + StandardError.new('Something went wrong while updating the modal.'), + { + response: { "ok" => false }, + slack_workspace_id: slack_installation.team_id, + slack_user_id: slack_installation.user_id + } + ) + + expect(execute.message).to eq('Something went wrong while updating the modal.') + end + end + + context 'when Slack API call raises an HTTP exception' do + before do + allow(Gitlab::HTTP).to receive(:post).and_raise(Errno::ECONNREFUSED, 'error message') + end + + it 'tracks the exception and returns an error message' do + expect(Rails.cache).to receive(:read).and_return(old_project.id.to_s) + expect(::Gitlab::ErrorTracking).to receive(:track_exception) + .with( + Errno::ECONNREFUSED.new('HTTP exception when calling Slack API'), + { + slack_workspace_id: slack_installation.team_id + } + ) + + expect(execute).to be_error + end + end + end +end diff --git a/spec/services/integrations/slack_option_service_spec.rb b/spec/services/integrations/slack_option_service_spec.rb new file mode 100644 index 00000000000..2e114b932d2 --- /dev/null +++ b/spec/services/integrations/slack_option_service_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackOptionService, feature_category: :integrations do + describe '#execute' do + subject(:execute) { described_class.new(params).execute } + + let_it_be(:slack_installation) { create(:slack_integration) } + let_it_be(:user) { create(:user) } + + let_it_be(:chat_name) do + create(:chat_name, + user: user, + team_id: slack_installation.team_id, + chat_id: slack_installation.user_id + ) + end + + let(:params) do + { + action_id: action_id, + view: { + id: 'VHDFR54DSA' + }, + value: 'Search value', + team: { + id: slack_installation.team_id + }, + user: { + id: slack_installation.user_id + } + } + end + + context 'when action_id is assignee' do + let(:action_id) { 'assignee' } + + it 'executes the user search handler' do + user_search_handler = described_class::OPTIONS['assignee'] + + expect_next_instance_of(user_search_handler, chat_name, 'Search value', 'VHDFR54DSA') do |service| + expect(service).to receive(:execute).and_return(ServiceResponse.success) + end + + execute + end + end + + context 'when action_id is labels' do + let(:action_id) { 'labels' } + + it 'executes the label search handler' do + label_search_handler = described_class::OPTIONS['labels'] + + expect_next_instance_of(label_search_handler, chat_name, 'Search value', 'VHDFR54DSA') do |service| + expect(service).to receive(:execute).and_return(ServiceResponse.success) + end + + execute + end + end + + context 'when action_id is unknown' do + let(:action_id) { 'foo' } + + it 'raises an error and does not execute a service class' do + described_class::OPTIONS.each_value do |service_class| + expect(service_class).not_to receive(:new) + end + + expect { execute }.to raise_error(described_class::UnknownOptionError) + end + end + end +end diff --git a/spec/services/integrations/slack_options/label_search_handler_spec.rb b/spec/services/integrations/slack_options/label_search_handler_spec.rb new file mode 100644 index 00000000000..3b006061f1d --- /dev/null +++ b/spec/services/integrations/slack_options/label_search_handler_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackOptions::LabelSearchHandler, feature_category: :integrations do + describe '#execute' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :private, namespace: group) } + let_it_be(:current_user) { create(:user) } + let_it_be(:chat_name) { create(:chat_name, user: current_user) } + let_it_be(:project_label1) { create(:label, project: project, title: 'Label 1') } + let_it_be(:project_label2) { create(:label, project: project, title: 'Label 2') } + let_it_be(:group_label1) { create(:group_label, group: group, title: 'LabelG 1') } + let_it_be(:group_label2) { create(:group_label, group: group, title: 'glb 2') } + let_it_be(:view_id) { 'VXHD54DR' } + + let(:search_value) { 'Lab' } + + subject(:execute) { described_class.new(chat_name, search_value, view_id).execute } + + context 'when user has permission to read project and group labels' do + before do + allow(Rails.cache).to receive(:read).and_return(project.id) + project.add_developer(current_user) + end + + it 'returns the labels matching the search term' do + labels = execute.payload[:options] + label_names = labels.map { |label| label.dig(:text, :text) } + + expect(label_names).to contain_exactly( + project_label1.name, + project_label2.name, + group_label1.name + ) + end + end + + context 'when user does not have permissions to read project/group labels' do + it 'returns empty array' do + expect(LabelsFinder).not_to receive(:execute) + + expect(execute.payload).to be_empty + end + end + end +end diff --git a/spec/services/integrations/slack_options/user_search_handler_spec.rb b/spec/services/integrations/slack_options/user_search_handler_spec.rb new file mode 100644 index 00000000000..e827bf643d2 --- /dev/null +++ b/spec/services/integrations/slack_options/user_search_handler_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::SlackOptions::UserSearchHandler, feature_category: :integrations do + describe '#execute' do + let_it_be(:project) { create(:project, :private) } + let_it_be(:current_user) { create(:user) } + let_it_be(:chat_name) { create(:chat_name, user: current_user) } + let_it_be(:user1) { create(:user, name: 'Rajendra Kadam') } + let_it_be(:user2) { create(:user, name: 'Rajesh K') } + let_it_be(:user3) { create(:user) } + let_it_be(:view_id) { 'VXHD54DR' } + + let(:search_value) { 'Raj' } + + subject(:execute) { described_class.new(chat_name, search_value, view_id).execute } + + context 'when user has permissions to read project members' do + before do + project.add_developer(current_user) + project.add_guest(user1) + project.add_reporter(user2) + project.add_maintainer(user3) + end + + it 'returns the user matching the search term' do + expect(Rails.cache).to receive(:read).and_return(project.id) + + members = execute.payload[:options] + user_names = members.map { |member| member.dig(:text, :text) } + + expect(members.count).to eq(2) + expect(user_names).to contain_exactly( + "#{user1.name} - #{user1.username}", + "#{user2.name} - #{user2.username}" + ) + end + end + + context 'when user does not have permissions to read project members' do + it 'returns empty array' do + expect(Rails.cache).to receive(:read).and_return(project.id) + expect(MembersFinder).not_to receive(:execute) + + members = execute.payload + + expect(members).to be_empty + end + end + end +end diff --git a/spec/services/integrations/test/project_service_spec.rb b/spec/services/integrations/test/project_service_spec.rb index 74833686283..4f8f932fb45 100644 --- a/spec/services/integrations/test/project_service_spec.rb +++ b/spec/services/integrations/test/project_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Integrations::Test::ProjectService do +RSpec.describe Integrations::Test::ProjectService, feature_category: :integrations do include AfterNextHelpers describe '#execute' do diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index 7ba349ceeae..a76d575a1e0 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issuable::BulkUpdateService do +RSpec.describe Issuable::BulkUpdateService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :repository, namespace: user.namespace) } diff --git a/spec/services/issuable/callbacks/milestone_spec.rb b/spec/services/issuable/callbacks/milestone_spec.rb new file mode 100644 index 00000000000..085ed029a6c --- /dev/null +++ b/spec/services/issuable/callbacks/milestone_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Issuable::Callbacks::Milestone, feature_category: :team_planning do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :private, group: group) } + let_it_be(:project_milestone) { create(:milestone, project: project) } + let_it_be(:group_milestone) { create(:milestone, group: group) } + let_it_be(:reporter) do + create(:user).tap { |u| project.add_reporter(u) } + end + + let(:issuable) { build(:issue, project: project) } + let(:current_user) { reporter } + let(:params) { { milestone_id: project_milestone.id } } + let(:callback) { described_class.new(issuable: issuable, current_user: current_user, params: params) } + + describe '#after_initialize' do + it "sets the issuable's milestone" do + expect { callback.after_initialize }.to change { issuable.milestone }.from(nil).to(project_milestone) + end + + context 'when assigning a group milestone' do + let(:params) { { milestone_id: group_milestone.id } } + + it "sets the issuable's milestone" do + expect { callback.after_initialize }.to change { issuable.milestone }.from(nil).to(group_milestone) + end + end + + context 'when assigning a group milestone outside the project ancestors' do + let(:another_group_milestone) { create(:milestone, group: create(:group)) } + let(:params) { { milestone_id: another_group_milestone.id } } + + it "does not change the issuable's milestone" do + expect { callback.after_initialize }.not_to change { issuable.milestone } + end + end + + context 'when user is not allowed to set issuable metadata' do + let(:current_user) { create(:user) } + + it "does not change the issuable's milestone" do + expect { callback.after_initialize }.not_to change { issuable.milestone } + end + end + + context 'when unsetting a milestone' do + let(:issuable) { create(:issue, project: project, milestone: project_milestone) } + + context 'when milestone_id is nil' do + let(:params) { { milestone_id: nil } } + + it "unsets the issuable's milestone" do + expect { callback.after_initialize }.to change { issuable.milestone }.from(project_milestone).to(nil) + end + end + + context 'when milestone_id is an empty string' do + let(:params) { { milestone_id: '' } } + + it "unsets the issuable's milestone" do + expect { callback.after_initialize }.to change { issuable.milestone }.from(project_milestone).to(nil) + end + end + + context 'when milestone_id is 0' do + let(:params) { { milestone_id: '0' } } + + it "unsets the issuable's milestone" do + expect { callback.after_initialize }.to change { issuable.milestone }.from(project_milestone).to(nil) + end + end + + context "when milestone_id is '0'" do + let(:params) { { milestone_id: 0 } } + + it "unsets the issuable's milestone" do + expect { callback.after_initialize }.to change { issuable.milestone }.from(project_milestone).to(nil) + end + end + + context 'when milestone_id is not given' do + let(:params) { {} } + + it "does not unset the issuable's milestone" do + expect { callback.after_initialize }.not_to change { issuable.milestone } + end + end + + context 'when new type does not support milestones' do + let(:params) { { excluded_in_new_type: true } } + + it "unsets the issuable's milestone" do + expect { callback.after_initialize }.to change { issuable.milestone }.from(project_milestone).to(nil) + end + end + end + end +end diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb index 0d2b8a4ac3c..9306aeaac44 100644 --- a/spec/services/issuable/common_system_notes_service_spec.rb +++ b/spec/services/issuable/common_system_notes_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issuable::CommonSystemNotesService do +RSpec.describe Issuable::CommonSystemNotesService, feature_category: :team_planning do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } diff --git a/spec/services/issuable/destroy_label_links_service_spec.rb b/spec/services/issuable/destroy_label_links_service_spec.rb index bbc69e266c9..f0a92c201d2 100644 --- a/spec/services/issuable/destroy_label_links_service_spec.rb +++ b/spec/services/issuable/destroy_label_links_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issuable::DestroyLabelLinksService do +RSpec.describe Issuable::DestroyLabelLinksService, feature_category: :team_planning do describe '#execute' do context 'when target is an Issue' do let_it_be(:target) { create(:issue) } diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb index 29f548e1c47..1acaf01dce0 100644 --- a/spec/services/issuable/destroy_service_spec.rb +++ b/spec/services/issuable/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issuable::DestroyService do +RSpec.describe Issuable::DestroyService, feature_category: :team_planning do let(:user) { create(:user) } let(:group) { create(:group, :public) } let(:project) { create(:project, :public, group: group) } diff --git a/spec/services/issuable/discussions_list_service_spec.rb b/spec/services/issuable/discussions_list_service_spec.rb index a6f57088ad1..03b6a1b4556 100644 --- a/spec/services/issuable/discussions_list_service_spec.rb +++ b/spec/services/issuable/discussions_list_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issuable::DiscussionsListService do +RSpec.describe Issuable::DiscussionsListService, feature_category: :team_planning do let_it_be(:current_user) { create(:user) } let_it_be(:group) { create(:group, :private) } let_it_be(:project) { create(:project, :repository, :private, group: group) } diff --git a/spec/services/issuable/process_assignees_spec.rb b/spec/services/issuable/process_assignees_spec.rb index 9e909b68172..2c8d4c5e11d 100644 --- a/spec/services/issuable/process_assignees_spec.rb +++ b/spec/services/issuable/process_assignees_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issuable::ProcessAssignees do +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), diff --git a/spec/services/issue_links/create_service_spec.rb b/spec/services/issue_links/create_service_spec.rb index 0629b8b091b..71603da1c90 100644 --- a/spec/services/issue_links/create_service_spec.rb +++ b/spec/services/issue_links/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IssueLinks::CreateService do +RSpec.describe IssueLinks::CreateService, feature_category: :team_planning do describe '#execute' do let_it_be(:user) { create :user } let_it_be(:namespace) { create :namespace } @@ -43,7 +43,6 @@ RSpec.describe IssueLinks::CreateService do end it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { issue.namespace } let(:category) { described_class.to_s } let(:action) { 'incident_management_incident_relate' } diff --git a/spec/services/issue_links/destroy_service_spec.rb b/spec/services/issue_links/destroy_service_spec.rb index ecb53b5cd31..5c4814f5ad1 100644 --- a/spec/services/issue_links/destroy_service_spec.rb +++ b/spec/services/issue_links/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IssueLinks::DestroyService do +RSpec.describe IssueLinks::DestroyService, feature_category: :team_planning do describe '#execute' do let_it_be(:project) { create(:project_empty_repo, :private) } let_it_be(:user) { create(:user) } @@ -27,7 +27,6 @@ RSpec.describe IssueLinks::DestroyService do end it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { issue_b.namespace } let(:category) { described_class.to_s } let(:action) { 'incident_management_incident_unrelate' } diff --git a/spec/services/issue_links/list_service_spec.rb b/spec/services/issue_links/list_service_spec.rb index 7a3ba845c7c..bfb6127ed56 100644 --- a/spec/services/issue_links/list_service_spec.rb +++ b/spec/services/issue_links/list_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IssueLinks::ListService do +RSpec.describe IssueLinks::ListService, feature_category: :team_planning do let(:user) { create :user } let(:project) { create(:project_empty_repo, :private) } let(:issue) { create :issue, project: project } diff --git a/spec/services/issues/after_create_service_spec.rb b/spec/services/issues/after_create_service_spec.rb index 39a6799dbad..b59578b14a0 100644 --- a/spec/services/issues/after_create_service_spec.rb +++ b/spec/services/issues/after_create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::AfterCreateService do +RSpec.describe Issues::AfterCreateService, feature_category: :team_planning do include AfterNextHelpers let_it_be(:project) { create(:project) } @@ -28,13 +28,6 @@ RSpec.describe Issues::AfterCreateService do expect { after_create_service.execute(issue) }.to change { Todo.where(attributes).count }.by(1) end - it 'deletes milestone issues count cache' do - expect_next(Milestones::IssuesCountService, milestone) - .to receive(:delete_cache).and_call_original - - after_create_service.execute(issue) - end - context 'with a regular issue' do it_behaves_like 'does not track incident management event', :incident_management_incident_created do subject { after_create_service.execute(issue) } diff --git a/spec/services/issues/base_service_spec.rb b/spec/services/issues/base_service_spec.rb new file mode 100644 index 00000000000..94165d557d8 --- /dev/null +++ b/spec/services/issues/base_service_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Issues::BaseService, feature_category: :team_planning do + describe '#constructor_container_arg' do + it { expect(described_class.constructor_container_arg("some-value")).to eq({ container: "some-value" }) } + end +end diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index 2160c45d079..8368a34caf0 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::BuildService do +RSpec.describe Issues::BuildService, feature_category: :team_planning do using RSpec::Parameterized::TableSyntax let_it_be(:project) { create(:project, :repository) } @@ -161,8 +161,8 @@ RSpec.describe Issues::BuildService do end end - context 'when guest' do - let(:user) { guest } + context 'when user is not a project member' do + let(:user) { create(:user) } it 'cannot set milestone' do milestone = create(:milestone, project: project) @@ -175,31 +175,37 @@ RSpec.describe Issues::BuildService do describe 'setting issue type' do context 'with a corresponding WorkItems::Type' do + let_it_be(:type_task) { WorkItems::Type.default_by_type(:task) } + let_it_be(:type_task_id) { type_task.id } let_it_be(:type_issue_id) { WorkItems::Type.default_issue_type.id } let_it_be(:type_incident_id) { WorkItems::Type.default_by_type(:incident).id } - - where(:issue_type, :current_user, :work_item_type_id, :resulting_issue_type) do - nil | ref(:guest) | ref(:type_issue_id) | 'issue' - 'issue' | ref(:guest) | ref(:type_issue_id) | 'issue' - 'incident' | ref(:guest) | ref(:type_issue_id) | 'issue' - 'incident' | ref(:reporter) | ref(:type_incident_id) | 'incident' + let(:combined_params) { { work_item_type: type_task, issue_type: 'issue' } } + let(:work_item_params) { { work_item_type_id: type_task_id } } + + where(:issue_params, :current_user, :work_item_type_id, :resulting_issue_type) do + { issue_type: nil } | ref(:guest) | ref(:type_issue_id) | 'issue' + { issue_type: 'issue' } | ref(:guest) | ref(:type_issue_id) | 'issue' + { issue_type: 'incident' } | ref(:guest) | ref(:type_issue_id) | 'issue' + { issue_type: 'incident' } | ref(:reporter) | ref(:type_incident_id) | 'incident' + ref(:combined_params) | ref(:reporter) | ref(:type_task_id) | 'task' + ref(:work_item_params) | ref(:reporter) | ref(:type_task_id) | 'task' # update once support for test_case is enabled - 'test_case' | ref(:guest) | ref(:type_issue_id) | 'issue' + { issue_type: 'test_case' } | ref(:guest) | ref(:type_issue_id) | 'issue' # update once support for requirement is enabled - 'requirement' | ref(:guest) | ref(:type_issue_id) | 'issue' - 'invalid' | ref(:guest) | ref(:type_issue_id) | 'issue' + { issue_type: 'requirement' } | ref(:guest) | ref(:type_issue_id) | 'issue' + { issue_type: 'invalid' } | ref(:guest) | ref(:type_issue_id) | 'issue' # ensure that we don't set a value which has a permission check but is an invalid issue type - 'project' | ref(:guest) | ref(:type_issue_id) | 'issue' + { issue_type: 'project' } | ref(:guest) | ref(:type_issue_id) | 'issue' end with_them do let(:user) { current_user } it 'builds an issue' do - issue = build_issue(issue_type: issue_type) + issue = build_issue(**issue_params) - expect(issue.issue_type).to eq(resulting_issue_type) 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/clone_service_spec.rb b/spec/services/issues/clone_service_spec.rb index eafaea93015..2fb14d8ce8e 100644 --- a/spec/services/issues/clone_service_spec.rb +++ b/spec/services/issues/clone_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::CloneService do +RSpec.describe Issues::CloneService, feature_category: :team_planning do include DesignManagementTestHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 803808e667c..47925236a74 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe Issues::CloseService do +RSpec.describe Issues::CloseService, feature_category: :team_planning do let(:project) { create(:project, :repository) } + let(:delegated_project) { project.project_namespace.project } let(:user) { create(:user, email: "user@example.com") } let(:user2) { create(:user, email: "user2@example.com") } let(:guest) { create(:user) } @@ -100,7 +101,6 @@ RSpec.describe Issues::CloseService do it_behaves_like 'an incident management tracked event', :incident_management_incident_closed it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { issue.namespace } let(:category) { described_class.to_s } let(:action) { 'incident_management_incident_closed' } @@ -202,34 +202,17 @@ RSpec.describe Issues::CloseService do end it 'mentions closure via a merge request' do - close_issue - - email = ActionMailer::Base.deliveries.last + expect_next_instance_of(NotificationService::Async) do |service| + expect(service).to receive(:close_issue).with(issue, user, { closed_via: closing_merge_request }) + end - expect(email.to.first).to eq(user2.email) - expect(email.subject).to include(issue.title) - expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference)) + close_issue end it_behaves_like 'records an onboarding progress action', :issue_auto_closed do let(:namespace) { project.namespace } end - context 'when user cannot read merge request' do - it 'does not mention merge request' do - project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) - - close_issue - - email = ActionMailer::Base.deliveries.last - body_text = email.body.parts.map(&:body).join(" ") - - expect(email.to.first).to eq(user2.email) - expect(email.subject).to include(issue.title) - expect(body_text).not_to include(closing_merge_request.to_reference) - end - end - context 'updating `metrics.first_mentioned_in_commit_at`' do context 'when `metrics.first_mentioned_in_commit_at` is not set' do it 'uses the first commit authored timestamp' do @@ -265,31 +248,11 @@ RSpec.describe Issues::CloseService do context "closed by a commit", :sidekiq_might_not_need_inline do it 'mentions closure via a commit' do - perform_enqueued_jobs do - described_class.new(container: project, current_user: user).close_issue(issue, closed_via: closing_commit) + expect_next_instance_of(NotificationService::Async) do |service| + expect(service).to receive(:close_issue).with(issue, user, { closed_via: "commit #{closing_commit.id}" }) end - email = ActionMailer::Base.deliveries.last - - expect(email.to.first).to eq(user2.email) - expect(email.subject).to include(issue.title) - expect(email.body.parts.map(&:body)).to all(include(closing_commit.id)) - end - - context 'when user cannot read the commit' do - it 'does not mention the commit id' do - project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) - perform_enqueued_jobs do - described_class.new(container: project, current_user: user).close_issue(issue, closed_via: closing_commit) - end - - email = ActionMailer::Base.deliveries.last - body_text = email.body.parts.map(&:body).join(" ") - - expect(email.to.first).to eq(user2.email) - expect(email.subject).to include(issue.title) - expect(body_text).not_to include(closing_commit.id) - end + described_class.new(container: project, current_user: user).close_issue(issue, closed_via: closing_commit) end end @@ -321,12 +284,12 @@ RSpec.describe Issues::CloseService do expect(issue.reload.closed_by_id).to be(user.id) end - it 'sends email to user2 about assign of new issue', :sidekiq_might_not_need_inline do - close_issue + it 'sends notification', :sidekiq_might_not_need_inline do + expect_next_instance_of(NotificationService::Async) do |service| + expect(service).to receive(:close_issue).with(issue, user, { closed_via: nil }) + end - email = ActionMailer::Base.deliveries.last - expect(email.to.first).to eq(user2.email) - expect(email.subject).to include(issue.title) + close_issue end it 'creates resource state event about the issue being closed' do @@ -435,10 +398,10 @@ RSpec.describe Issues::CloseService do end it 'executes issue hooks' do - expect(project).to receive(:execute_hooks).with(expected_payload, :issue_hooks) - expect(project).to receive(:execute_integrations).with(expected_payload, :issue_hooks) + expect(delegated_project).to receive(:execute_hooks).with(expected_payload, :issue_hooks) + expect(delegated_project).to receive(:execute_integrations).with(expected_payload, :issue_hooks) - described_class.new(container: project, current_user: user).close_issue(issue) + described_class.new(container: delegated_project, current_user: user).close_issue(issue) end end @@ -446,8 +409,8 @@ RSpec.describe Issues::CloseService do it 'executes confidential issue hooks' do issue = create(:issue, :confidential, project: project) - expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks) - expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :confidential_issue_hooks) + expect(delegated_project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks) + expect(delegated_project).to receive(:execute_integrations).with(an_instance_of(Hash), :confidential_issue_hooks) described_class.new(container: project, current_user: user).close_issue(issue) end diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index ada5b300d7a..548d9455ebf 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::CreateService do +RSpec.describe Issues::CreateService, feature_category: :team_planning do include AfterNextHelpers let_it_be(:group) { create(:group, :crm_enabled) } @@ -124,6 +124,15 @@ RSpec.describe Issues::CreateService do expect(issue.issue_customer_relations_contacts).to be_empty end + context 'with milestone' do + it 'deletes milestone issues count cache' do + expect_next(Milestones::IssuesCountService, milestone) + .to receive(:delete_cache).and_call_original + + expect(result).to be_success + end + end + context 'when the work item type is not allowed to create' do before do allow_next_instance_of(::Issues::BuildService) do |instance| @@ -136,7 +145,6 @@ RSpec.describe Issues::CreateService do expect(issue).to be_persisted expect(issue).to be_a(::Issue) expect(issue.work_item_type.base_type).to eq('issue') - expect(issue.issue_type).to eq('issue') end end @@ -149,7 +157,7 @@ RSpec.describe Issues::CreateService do context 'when a build_service is provided' do let(:result) { described_class.new(container: project, current_user: user, params: opts, spam_params: spam_params, build_service: build_service).execute } - let(:issue_from_builder) { WorkItem.new(project: project, title: 'Issue from builder') } + let(:issue_from_builder) { build(:work_item, project: project, title: 'Issue from builder') } let(:build_service) { double(:build_service, execute: issue_from_builder) } it 'uses the provided service to build the issue' do @@ -372,6 +380,13 @@ RSpec.describe Issues::CreateService do expect(assignee.assigned_open_issues_count).to eq 1 end + + it 'records the assignee assignment event' do + result = described_class.new(container: project, current_user: user, params: opts, spam_params: spam_params).execute + + issue = result.payload[:issue] + expect(issue.assignment_events).to match([have_attributes(user_id: assignee.id, action: 'add')]) + end end context 'when duplicate label titles are given' do @@ -436,8 +451,8 @@ RSpec.describe Issues::CreateService do end it 'executes issue hooks' do - expect(project).to receive(:execute_hooks).with(expected_payload, :issue_hooks) - expect(project).to receive(:execute_integrations).with(expected_payload, :issue_hooks) + expect(project.project_namespace).to receive(:execute_hooks).with(expected_payload, :issue_hooks) + expect(project.project_namespace).to receive(:execute_integrations).with(expected_payload, :issue_hooks) described_class.new(container: project, current_user: user, params: opts, spam_params: spam_params).execute end @@ -459,8 +474,8 @@ RSpec.describe Issues::CreateService do end it 'executes confidential issue hooks' do - expect(project).to receive(:execute_hooks).with(expected_payload, :confidential_issue_hooks) - expect(project).to receive(:execute_integrations).with(expected_payload, :confidential_issue_hooks) + expect(project.project_namespace).to receive(:execute_hooks).with(expected_payload, :confidential_issue_hooks) + expect(project.project_namespace).to receive(:execute_integrations).with(expected_payload, :confidential_issue_hooks) described_class.new(container: project, current_user: user, params: opts, spam_params: spam_params).execute end @@ -493,7 +508,7 @@ RSpec.describe Issues::CreateService do end it 'schedules a namespace onboarding create action worker' do - expect(Onboarding::IssueCreatedWorker).to receive(:perform_async).with(project.namespace.id) + expect(Onboarding::IssueCreatedWorker).to receive(:perform_async).with(project.project_namespace_id) issue end @@ -565,36 +580,6 @@ RSpec.describe Issues::CreateService do end context 'Quick actions' do - context 'as work item' do - let(:opts) do - { - title: "My work item", - work_item_type: work_item_type, - description: "/shrug" - } - end - - context 'when work item type is not the default Issue' do - let(:work_item_type) { create(:work_item_type, namespace: project.namespace) } - - it 'saves the work item without applying the quick action' do - expect(result).to be_success - expect(issue).to be_persisted - expect(issue.description).to eq("/shrug") - end - end - - context 'when work item type is the default Issue' do - let(:work_item_type) { WorkItems::Type.default_by_type(:issue) } - - it 'saves the work item and applies the quick action' do - expect(result).to be_success - expect(issue).to be_persisted - expect(issue.description).to eq(" ¯\\_(ツ)_/¯") - end - end - end - context 'with assignee, milestone, and contact in params and command' do let_it_be(:contact) { create(:contact, group: group) } @@ -687,6 +672,22 @@ RSpec.describe Issues::CreateService do expect(issue.labels).to eq([label]) end end + + context 'when using promote_to_incident' do + let(:opts) { { title: 'Title', description: '/promote_to_incident' } } + + before do + project.add_developer(user) + end + + it 'creates an issue with the correct issue type' do + expect { result }.to change(Issue, :count).by(1) + + created_issue = Issue.last + + expect(created_issue.work_item_type).to eq(WorkItems::Type.default_by_type('incident')) + end + end end context 'resolving discussions' do diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb index f49bce70cd0..f9d8bf04ae9 100644 --- a/spec/services/issues/duplicate_service_spec.rb +++ b/spec/services/issues/duplicate_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::DuplicateService do +RSpec.describe Issues::DuplicateService, feature_category: :team_planning do let(:user) { create(:user) } let(:canonical_project) { create(:project) } let(:duplicate_project) { create(:project) } diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb index 90e360f9cf1..6a147782209 100644 --- a/spec/services/issues/import_csv_service_spec.rb +++ b/spec/services/issues/import_csv_service_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Issues::ImportCsvService, feature_category: :team_planning do let(:project) { create(:project) } let(:user) { create(:user) } let(:assignee) { create(:user, username: 'csv_assignee') } + let(:file) { fixture_file_upload('spec/fixtures/csv_complex.csv') } let(:service) do uploader = FileUploader.new(project) uploader.store!(file) @@ -19,8 +20,6 @@ RSpec.describe Issues::ImportCsvService, feature_category: :team_planning do end describe '#execute' do - let(:file) { fixture_file_upload('spec/fixtures/csv_complex.csv') } - subject { service.execute } it 'sets all issueable attributes and executes quick actions' do diff --git a/spec/services/issues/issuable_base_service_spec.rb b/spec/services/issues/issuable_base_service_spec.rb new file mode 100644 index 00000000000..e1680d5c908 --- /dev/null +++ b/spec/services/issues/issuable_base_service_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe IssuableBaseService, feature_category: :team_planning do + describe '#constructor_container_arg' do + it { expect(described_class.constructor_container_arg("some-value")).to eq({ container: "some-value" }) } + end +end diff --git a/spec/services/issues/prepare_import_csv_service_spec.rb b/spec/services/issues/prepare_import_csv_service_spec.rb index ded23ee43b9..d318d4fd25e 100644 --- a/spec/services/issues/prepare_import_csv_service_spec.rb +++ b/spec/services/issues/prepare_import_csv_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::PrepareImportCsvService do +RSpec.describe Issues::PrepareImportCsvService, feature_category: :team_planning do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } diff --git a/spec/services/issues/referenced_merge_requests_service_spec.rb b/spec/services/issues/referenced_merge_requests_service_spec.rb index aee3583b834..4781daf7688 100644 --- a/spec/services/issues/referenced_merge_requests_service_spec.rb +++ b/spec/services/issues/referenced_merge_requests_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::ReferencedMergeRequestsService do +RSpec.describe Issues::ReferencedMergeRequestsService, feature_category: :team_planning do def create_referencing_mr(attributes = {}) create(:merge_request, attributes).tap do |merge_request| create(:note, :system, project: project, noteable: issue, author: user, note: merge_request.to_reference(full: true)) diff --git a/spec/services/issues/related_branches_service_spec.rb b/spec/services/issues/related_branches_service_spec.rb index 05c61d0abfc..940d988668e 100644 --- a/spec/services/issues/related_branches_service_spec.rb +++ b/spec/services/issues/related_branches_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::RelatedBranchesService do +RSpec.describe Issues::RelatedBranchesService, feature_category: :team_planning do let_it_be(:project) { create(:project, :repository, :public, public_builds: false) } let_it_be(:developer) { create(:user) } let_it_be(:issue) { create(:issue, project: project) } diff --git a/spec/services/issues/relative_position_rebalancing_service_spec.rb b/spec/services/issues/relative_position_rebalancing_service_spec.rb index 27c0394ac8b..68f1af49b5f 100644 --- a/spec/services/issues/relative_position_rebalancing_service_spec.rb +++ b/spec/services/issues/relative_position_rebalancing_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::RelativePositionRebalancingService, :clean_gitlab_redis_shared_state do +RSpec.describe Issues::RelativePositionRebalancingService, :clean_gitlab_redis_shared_state, feature_category: :team_planning do let_it_be(:project, reload: true) { create(:project, :repository_disabled, skip_disk_validation: true) } let_it_be(:user) { project.creator } let_it_be(:start) { RelativePositioning::START_POSITION } diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb index 68015a2327e..bb1151dfac7 100644 --- a/spec/services/issues/reopen_service_spec.rb +++ b/spec/services/issues/reopen_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::ReopenService do +RSpec.describe Issues::ReopenService, feature_category: :team_planning do let(:project) { create(:project) } let(:issue) { create(:issue, :closed, project: project) } @@ -75,7 +75,6 @@ RSpec.describe Issues::ReopenService do it_behaves_like 'an incident management tracked event', :incident_management_incident_reopened it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { issue.namespace } let(:category) { described_class.to_s } let(:action) { 'incident_management_incident_reopened' } @@ -110,8 +109,8 @@ RSpec.describe Issues::ReopenService do end it 'executes issue hooks' do - expect(project).to receive(:execute_hooks).with(expected_payload, :issue_hooks) - expect(project).to receive(:execute_integrations).with(expected_payload, :issue_hooks) + expect(project.project_namespace).to receive(:execute_hooks).with(expected_payload, :issue_hooks) + expect(project.project_namespace).to receive(:execute_integrations).with(expected_payload, :issue_hooks) execute end @@ -121,8 +120,9 @@ RSpec.describe Issues::ReopenService do let(:issue) { create(:issue, :confidential, :closed, project: project) } it 'executes confidential issue hooks' do - expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks) - expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :confidential_issue_hooks) + issue_hooks = :confidential_issue_hooks + expect(project.project_namespace).to receive(:execute_hooks).with(an_instance_of(Hash), issue_hooks) + expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), issue_hooks) execute end diff --git a/spec/services/issues/reorder_service_spec.rb b/spec/services/issues/reorder_service_spec.rb index 430a9e9f526..b98d23e0f7f 100644 --- a/spec/services/issues/reorder_service_spec.rb +++ b/spec/services/issues/reorder_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::ReorderService do +RSpec.describe Issues::ReorderService, feature_category: :team_planning do let_it_be(:user) { create_default(:user) } let_it_be(:group) { create(:group) } let_it_be(:project, reload: true) { create(:project, namespace: group) } diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb index 1ac71b966bc..c2111bffdda 100644 --- a/spec/services/issues/resolve_discussions_spec.rb +++ b/spec/services/issues/resolve_discussions_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::ResolveDiscussions do +RSpec.describe Issues::ResolveDiscussions, feature_category: :team_planning do let(:project) { create(:project, :repository) } let(:user) { create(:user) } @@ -11,7 +11,7 @@ RSpec.describe Issues::ResolveDiscussions do DummyService.class_eval do include ::Issues::ResolveDiscussions - def initialize(project:, current_user: nil, params: {}) + def initialize(container:, current_user: nil, params: {}) super filter_resolve_discussion_params end @@ -26,7 +26,7 @@ RSpec.describe Issues::ResolveDiscussions do let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: "fix") } describe "#merge_request_for_resolving_discussion" do - let(:service) { DummyService.new(project: project, current_user: user, params: { merge_request_to_resolve_discussions_of: merge_request.iid }) } + let(:service) { DummyService.new(container: project, current_user: user, params: { merge_request_to_resolve_discussions_of: merge_request.iid }) } it "finds the merge request" do expect(service.merge_request_to_resolve_discussions_of).to eq(merge_request) @@ -45,7 +45,7 @@ RSpec.describe Issues::ResolveDiscussions do describe "#discussions_to_resolve" do it "contains a single discussion when matching merge request and discussion are passed" do service = DummyService.new( - project: project, + container: project, current_user: user, params: { discussion_to_resolve: discussion.id, @@ -65,7 +65,7 @@ RSpec.describe Issues::ResolveDiscussions do project: merge_request.target_project, line_number: 15)]) service = DummyService.new( - project: project, + container: project, current_user: user, params: { merge_request_to_resolve_discussions_of: merge_request.iid } ) @@ -83,7 +83,7 @@ RSpec.describe Issues::ResolveDiscussions do line_number: 15 )]) service = DummyService.new( - project: project, + container: project, current_user: user, params: { merge_request_to_resolve_discussions_of: merge_request.iid } ) @@ -96,7 +96,7 @@ RSpec.describe Issues::ResolveDiscussions do it "is empty when a discussion and another merge request are passed" do service = DummyService.new( - project: project, + container: project, current_user: user, params: { discussion_to_resolve: discussion.id, diff --git a/spec/services/issues/set_crm_contacts_service_spec.rb b/spec/services/issues/set_crm_contacts_service_spec.rb index 5613cc49cc5..aa5dec20a13 100644 --- a/spec/services/issues/set_crm_contacts_service_spec.rb +++ b/spec/services/issues/set_crm_contacts_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::SetCrmContactsService do +RSpec.describe Issues::SetCrmContactsService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :crm_enabled) } let_it_be(:project) { create(:project, group: create(:group, :crm_enabled, parent: group)) } diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 973025bd2e3..f96fbf54f08 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::UpdateService, :mailer do +RSpec.describe Issues::UpdateService, :mailer, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } let_it_be(:user3) { create(:user) } @@ -191,7 +191,6 @@ RSpec.describe Issues::UpdateService, :mailer do it_behaves_like 'an incident management tracked event', :incident_management_incident_change_confidential it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { issue.namespace } let(:category) { described_class.to_s } let(:label) { 'redis_hll_counters.incident_management.incident_management_total_unique_counts_monthly' } @@ -260,7 +259,7 @@ RSpec.describe Issues::UpdateService, :mailer do it 'creates system note about issue type' do update_issue(issue_type: 'incident') - note = find_note('changed issue type to incident') + note = find_note('changed type from issue to incident') expect(note).not_to eq(nil) end @@ -593,8 +592,8 @@ RSpec.describe Issues::UpdateService, :mailer do end it 'executes confidential issue hooks' do - expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks) - expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :confidential_issue_hooks) + expect(project.project_namespace).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks) + expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :confidential_issue_hooks) update_issue(confidential: true) end @@ -696,7 +695,6 @@ RSpec.describe Issues::UpdateService, :mailer do it_behaves_like 'an incident management tracked event', :incident_management_incident_assigned it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { issue.namespace } let(:category) { described_class.to_s } let(:label) { 'redis_hll_counters.incident_management.incident_management_total_unique_counts_monthly' } @@ -1109,19 +1107,37 @@ RSpec.describe Issues::UpdateService, :mailer do end context 'updating asssignee_id' do + it 'changes assignee' do + expect_next_instance_of(NotificationService::Async) do |service| + expect(service).to receive(:reassigned_issue).with(issue, user, [user3]) + end + + update_issue(assignee_ids: [user2.id]) + + expect(issue.reload.assignees).to eq([user2]) + end + it 'does not update assignee when assignee_id is invalid' do + expect(NotificationService).not_to receive(:new) + update_issue(assignee_ids: [-1]) expect(issue.reload.assignees).to eq([user3]) end it 'unassigns assignee when user id is 0' do + expect_next_instance_of(NotificationService::Async) do |service| + expect(service).to receive(:reassigned_issue).with(issue, user, [user3]) + end + update_issue(assignee_ids: [0]) expect(issue.reload.assignees).to be_empty end it 'does not update assignee_id when user cannot read issue' do + expect(NotificationService).not_to receive(:new) + update_issue(assignee_ids: [create(:user).id]) expect(issue.reload.assignees).to eq([user3]) @@ -1132,6 +1148,8 @@ RSpec.describe Issues::UpdateService, :mailer do levels.each do |level| it "does not update with unauthorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do + expect(NotificationService).not_to receive(:new) + assignee = create(:user) project.update!(visibility_level: level) feature_visibility_attr = :"#{issue.model_name.plural}_access_level" @@ -1141,6 +1159,39 @@ RSpec.describe Issues::UpdateService, :mailer do end end end + + it 'tracks the assignment events' do + original_assignee = issue.assignees.first! + + update_issue(assignee_ids: [user2.id]) + update_issue(assignee_ids: []) + update_issue(assignee_ids: [user3.id]) + + expected_events = [ + have_attributes({ + issue_id: issue.id, + user_id: original_assignee.id, + action: 'remove' + }), + have_attributes({ + issue_id: issue.id, + user_id: user2.id, + action: 'add' + }), + have_attributes({ + issue_id: issue.id, + user_id: user2.id, + action: 'remove' + }), + have_attributes({ + issue_id: issue.id, + user_id: user3.id, + action: 'add' + }) + ] + + expect(issue.assignment_events).to match_array(expected_events) + end end context 'updating mentions' do @@ -1166,9 +1217,9 @@ RSpec.describe Issues::UpdateService, :mailer do end it 'triggers webhooks' do - expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks) - expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks) - expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :incident_hooks) + expect(project.project_namespace).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks) + expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks) + expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :incident_hooks) update_issue(opts) end @@ -1280,9 +1331,9 @@ RSpec.describe Issues::UpdateService, :mailer do end it 'triggers webhooks' do - expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks) - expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks) - expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :incident_hooks) + expect(project.project_namespace).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks) + expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks) + expect(project.project_namespace).to receive(:execute_integrations).with(an_instance_of(Hash), :incident_hooks) update_issue(opts) end @@ -1475,31 +1526,5 @@ RSpec.describe Issues::UpdateService, :mailer do let(:existing_issue) { create(:issue, project: project) } let(:issuable) { described_class.new(container: project, current_user: user, params: params).execute(existing_issue) } end - - context 'with quick actions' do - context 'as work item' do - let(:opts) { { description: "/shrug" } } - - context 'when work item type is not the default Issue' do - let(:issue) { create(:work_item, :task, description: "") } - - it 'does not apply the quick action' do - expect do - update_issue(opts) - end.to change(issue, :description).to("/shrug") - end - end - - context 'when work item type is the default Issue' do - let(:issue) { create(:work_item, :issue, description: "") } - - it 'does not apply the quick action' do - expect do - update_issue(opts) - end.to change(issue, :description).to(" ¯\\_(ツ)_/¯") - end - end - end - end end end diff --git a/spec/services/issues/zoom_link_service_spec.rb b/spec/services/issues/zoom_link_service_spec.rb index 230e4c1b5e1..f2a81cbe33f 100644 --- a/spec/services/issues/zoom_link_service_spec.rb +++ b/spec/services/issues/zoom_link_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Issues::ZoomLinkService do +RSpec.describe Issues::ZoomLinkService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:issue) { create(:issue) } @@ -97,7 +97,6 @@ RSpec.describe Issues::ZoomLinkService do it_behaves_like 'an incident management tracked event', :incident_management_incident_zoom_meeting it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { issue.namespace } let(:category) { described_class.to_s } let(:action) { 'incident_management_incident_zoom_meeting' } diff --git a/spec/services/jira/requests/projects/list_service_spec.rb b/spec/services/jira/requests/projects/list_service_spec.rb index 78ee9cb9698..37e9f66d273 100644 --- a/spec/services/jira/requests/projects/list_service_spec.rb +++ b/spec/services/jira/requests/projects/list_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Jira::Requests::Projects::ListService do +RSpec.describe Jira::Requests::Projects::ListService, feature_category: :projects do include AfterNextHelpers let(:jira_integration) { create(:jira_integration) } diff --git a/spec/services/jira_connect/sync_service_spec.rb b/spec/services/jira_connect/sync_service_spec.rb index 32580a7735f..7457cdca13c 100644 --- a/spec/services/jira_connect/sync_service_spec.rb +++ b/spec/services/jira_connect/sync_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraConnect::SyncService do +RSpec.describe JiraConnect::SyncService, feature_category: :integrations do include AfterNextHelpers describe '#execute' do @@ -44,16 +44,18 @@ RSpec.describe JiraConnect::SyncService do subject end - context 'when a request returns an error' do - it 'logs the response as an error' do + context 'when a request returns errors' do + it 'logs each response as an error' do expect_next(client).to store_info( [ { 'errorMessages' => ['some error message'] }, - { 'errorMessages' => ['x'] } + { 'errorMessage' => 'a single error message' }, + { 'errorMessages' => [] }, + { 'errorMessage' => '' } ]) expect_log(:error, { 'errorMessages' => ['some error message'] }) - expect_log(:error, { 'errorMessages' => ['x'] }) + expect_log(:error, { 'errorMessage' => 'a single error message' }) subject end diff --git a/spec/services/jira_connect_installations/destroy_service_spec.rb b/spec/services/jira_connect_installations/destroy_service_spec.rb index bb5bab53ccb..b8b59d6cc57 100644 --- a/spec/services/jira_connect_installations/destroy_service_spec.rb +++ b/spec/services/jira_connect_installations/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraConnectInstallations::DestroyService do +RSpec.describe JiraConnectInstallations::DestroyService, feature_category: :integrations do describe '.execute' do it 'creates an instance and calls execute' do expect_next_instance_of(described_class, 'param1', 'param2', 'param3') do |destroy_service| diff --git a/spec/services/jira_connect_installations/proxy_lifecycle_event_service_spec.rb b/spec/services/jira_connect_installations/proxy_lifecycle_event_service_spec.rb index c621388a734..3c144de2208 100644 --- a/spec/services/jira_connect_installations/proxy_lifecycle_event_service_spec.rb +++ b/spec/services/jira_connect_installations/proxy_lifecycle_event_service_spec.rb @@ -94,9 +94,9 @@ RSpec.describe JiraConnectInstallations::ProxyLifecycleEventService, feature_cat expect(Gitlab::IntegrationsLogger).to receive(:info).with( integration: 'JiraConnect', message: 'Proxy lifecycle event received error response', - event_type: evnet_type, - status_code: 422, - body: 'Error message' + jira_event_type: evnet_type, + jira_status_code: 422, + jira_body: 'Error message' ) execute_service diff --git a/spec/services/jira_connect_subscriptions/create_service_spec.rb b/spec/services/jira_connect_subscriptions/create_service_spec.rb index 85208a30c30..f9d3954b84c 100644 --- a/spec/services/jira_connect_subscriptions/create_service_spec.rb +++ b/spec/services/jira_connect_subscriptions/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraConnectSubscriptions::CreateService do +RSpec.describe JiraConnectSubscriptions::CreateService, feature_category: :integrations do let_it_be(:installation) { create(:jira_connect_installation) } let_it_be(:current_user) { create(:user) } let_it_be(:group) { create(:group) } diff --git a/spec/services/jira_import/cloud_users_mapper_service_spec.rb b/spec/services/jira_import/cloud_users_mapper_service_spec.rb index 6b06a982a80..e3f3d550467 100644 --- a/spec/services/jira_import/cloud_users_mapper_service_spec.rb +++ b/spec/services/jira_import/cloud_users_mapper_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraImport::CloudUsersMapperService do +RSpec.describe JiraImport::CloudUsersMapperService, feature_category: :integrations do let(:start_at) { 7 } let(:url) { "/rest/api/2/users?maxResults=50&startAt=#{start_at}" } diff --git a/spec/services/jira_import/server_users_mapper_service_spec.rb b/spec/services/jira_import/server_users_mapper_service_spec.rb index 71cb8aea0be..e2304953dd2 100644 --- a/spec/services/jira_import/server_users_mapper_service_spec.rb +++ b/spec/services/jira_import/server_users_mapper_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraImport::ServerUsersMapperService do +RSpec.describe JiraImport::ServerUsersMapperService, feature_category: :integrations do let(:start_at) { 7 } let(:url) { "/rest/api/2/user/search?username=''&maxResults=50&startAt=#{start_at}" } diff --git a/spec/services/jira_import/start_import_service_spec.rb b/spec/services/jira_import/start_import_service_spec.rb index c0db3012a30..9cb163e3d1a 100644 --- a/spec/services/jira_import/start_import_service_spec.rb +++ b/spec/services/jira_import/start_import_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraImport::StartImportService do +RSpec.describe JiraImport::StartImportService, feature_category: :integrations do include JiraIntegrationHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/jira_import/users_importer_spec.rb b/spec/services/jira_import/users_importer_spec.rb index ace9e0d5779..39f8475754a 100644 --- a/spec/services/jira_import/users_importer_spec.rb +++ b/spec/services/jira_import/users_importer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraImport::UsersImporter do +RSpec.describe JiraImport::UsersImporter, feature_category: :integrations do include JiraIntegrationHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/keys/create_service_spec.rb b/spec/services/keys/create_service_spec.rb index 1dbe383ad8e..0a9fe2f5856 100644 --- a/spec/services/keys/create_service_spec.rb +++ b/spec/services/keys/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Keys::CreateService do +RSpec.describe Keys::CreateService, feature_category: :source_code_management do let(:user) { create(:user) } let(:params) { attributes_for(:key) } diff --git a/spec/services/keys/destroy_service_spec.rb b/spec/services/keys/destroy_service_spec.rb index dd40f9d73fd..9f064cb7236 100644 --- a/spec/services/keys/destroy_service_spec.rb +++ b/spec/services/keys/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Keys::DestroyService do +RSpec.describe Keys::DestroyService, feature_category: :source_code_management do let(:user) { create(:user) } subject { described_class.new(user) } diff --git a/spec/services/keys/expiry_notification_service_spec.rb b/spec/services/keys/expiry_notification_service_spec.rb index 7cb6cbce311..b8db4f28df7 100644 --- a/spec/services/keys/expiry_notification_service_spec.rb +++ b/spec/services/keys/expiry_notification_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Keys::ExpiryNotificationService do +RSpec.describe Keys::ExpiryNotificationService, feature_category: :source_code_management do let_it_be_with_reload(:user) { create(:user) } let(:params) { { keys: user.keys, expiring_soon: expiring_soon } } diff --git a/spec/services/keys/last_used_service_spec.rb b/spec/services/keys/last_used_service_spec.rb index a2cd5ffdd38..32100d793ff 100644 --- a/spec/services/keys/last_used_service_spec.rb +++ b/spec/services/keys/last_used_service_spec.rb @@ -2,33 +2,51 @@ require 'spec_helper' -RSpec.describe Keys::LastUsedService do +RSpec.describe Keys::LastUsedService, feature_category: :source_code_management do describe '#execute', :clean_gitlab_redis_shared_state do - it 'updates the key when it has not been used recently' do - key = create(:key, last_used_at: 1.year.ago) - time = Time.zone.now + context 'when it has not been used recently' do + let(:key) { create(:key, last_used_at: 1.year.ago) } + let(:time) { Time.zone.now } - travel_to(time) { described_class.new(key).execute } + it 'updates the key' do + travel_to(time) { described_class.new(key).execute } - expect(key.reload.last_used_at).to be_like_time(time) + expect(key.reload.last_used_at).to be_like_time(time) + end end - it 'does not update the key when it has been used recently' do - time = 1.minute.ago - key = create(:key, last_used_at: time) + context 'when it has been used recently' do + let(:time) { 1.minute.ago } + let(:key) { create(:key, last_used_at: time) } - described_class.new(key).execute + it 'does not update the key' do + described_class.new(key).execute - expect(key.last_used_at).to be_like_time(time) + expect(key.reload.last_used_at).to be_like_time(time) + end end + end + + describe '#execute_async', :clean_gitlab_redis_shared_state do + context 'when it has not been used recently' do + let(:key) { create(:key, last_used_at: 1.year.ago) } + let(:time) { Time.zone.now } - it 'does not update the updated_at field' do - # Since a lot of these updates could happen in parallel for different keys - # we want these updates to be as lightweight as possible, hence we want to - # make sure we _only_ update last_used_at and not always updated_at. - key = create(:key, last_used_at: 1.year.ago) + it 'schedules a job to update last_used_at' do + expect(::SshKeys::UpdateLastUsedAtWorker).to receive(:perform_async) - expect { described_class.new(key).execute }.not_to change { key.updated_at } + travel_to(time) { described_class.new(key).execute_async } + end + end + + context 'when it has been used recently' do + let(:key) { create(:key, last_used_at: 1.minute.ago) } + + it 'does not schedule a job to update last_used_at' do + expect(::SshKeys::UpdateLastUsedAtWorker).not_to receive(:perform_async) + + described_class.new(key).execute_async + end end end @@ -47,14 +65,6 @@ RSpec.describe Keys::LastUsedService do expect(service.update?).to eq(true) end - it 'returns false when a lease has already been obtained' do - key = build(:key, last_used_at: 1.year.ago) - service = described_class.new(key) - - expect(service.update?).to eq(true) - expect(service.update?).to eq(false) - end - it 'returns false when the key does not yet need to be updated' do key = build(:key, last_used_at: 1.minute.ago) service = described_class.new(key) diff --git a/spec/services/keys/revoke_service_spec.rb b/spec/services/keys/revoke_service_spec.rb index ec07701b4b7..8294ec5bbd1 100644 --- a/spec/services/keys/revoke_service_spec.rb +++ b/spec/services/keys/revoke_service_spec.rb @@ -32,17 +32,4 @@ RSpec.describe Keys::RevokeService, feature_category: :source_code_management do expect { service.execute(key) }.not_to change { signature.reload.verification_status } expect(key).to be_persisted end - - context 'when revoke_ssh_signatures disabled' do - before do - stub_feature_flags(revoke_ssh_signatures: false) - end - - it 'does not unverifies signatures' do - key = create(:key) - signature = create(:ssh_signature, key: key) - - expect { service.execute(key) }.not_to change { signature.reload.verification_status } - end - end end diff --git a/spec/services/labels/available_labels_service_spec.rb b/spec/services/labels/available_labels_service_spec.rb index 355dbd0c712..51314c2c226 100644 --- a/spec/services/labels/available_labels_service_spec.rb +++ b/spec/services/labels/available_labels_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Labels::AvailableLabelsService do +RSpec.describe Labels::AvailableLabelsService, feature_category: :team_planning do let(:user) { create(:user) } let(:project) { create(:project, :public, group: group) } let(:group) { create(:group) } diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb index 02dec8ae690..9be611490cf 100644 --- a/spec/services/labels/create_service_spec.rb +++ b/spec/services/labels/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Labels::CreateService do +RSpec.describe Labels::CreateService, feature_category: :team_planning do describe '#execute' do let(:project) { create(:project) } let(:group) { create(:group) } diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb index 3ea2727dc60..0bc1326942d 100644 --- a/spec/services/labels/find_or_create_service_spec.rb +++ b/spec/services/labels/find_or_create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Labels::FindOrCreateService do +RSpec.describe Labels::FindOrCreateService, feature_category: :team_planning do describe '#execute' do let(:group) { create(:group) } let(:project) { create(:project, namespace: group) } diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb index 3af6cf4c8f4..79cc88c65c8 100644 --- a/spec/services/labels/promote_service_spec.rb +++ b/spec/services/labels/promote_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Labels::PromoteService do +RSpec.describe Labels::PromoteService, feature_category: :team_planning do describe '#execute' do let_it_be(:user) { create(:user) } diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb index e67ab6025a5..bf895692e64 100644 --- a/spec/services/labels/transfer_service_spec.rb +++ b/spec/services/labels/transfer_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Labels::TransferService do +RSpec.describe Labels::TransferService, feature_category: :team_planning do shared_examples 'transfer labels' do describe '#execute' do let_it_be(:user) { create(:user) } diff --git a/spec/services/labels/update_service_spec.rb b/spec/services/labels/update_service_spec.rb index abc456f75f9..b9ac5282d10 100644 --- a/spec/services/labels/update_service_spec.rb +++ b/spec/services/labels/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Labels::UpdateService do +RSpec.describe Labels::UpdateService, feature_category: :team_planning do describe '#execute' do let(:project) { create(:project) } diff --git a/spec/services/lfs/lock_file_service_spec.rb b/spec/services/lfs/lock_file_service_spec.rb index b3a121866c8..47bf0c5f4ce 100644 --- a/spec/services/lfs/lock_file_service_spec.rb +++ b/spec/services/lfs/lock_file_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Lfs::LockFileService do +RSpec.describe Lfs::LockFileService, feature_category: :source_code_management do let(:project) { create(:project) } let(:current_user) { create(:user) } diff --git a/spec/services/lfs/locks_finder_service_spec.rb b/spec/services/lfs/locks_finder_service_spec.rb index 1167212eb69..38f8dadd38d 100644 --- a/spec/services/lfs/locks_finder_service_spec.rb +++ b/spec/services/lfs/locks_finder_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Lfs::LocksFinderService do +RSpec.describe Lfs::LocksFinderService, feature_category: :source_code_management do let(:project) { create(:project) } let(:user) { create(:user) } let(:params) { {} } diff --git a/spec/services/lfs/push_service_spec.rb b/spec/services/lfs/push_service_spec.rb index f52bba94eea..1ec143a7fc9 100644 --- a/spec/services/lfs/push_service_spec.rb +++ b/spec/services/lfs/push_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Lfs::PushService do +RSpec.describe Lfs::PushService, feature_category: :source_code_management do let(:logger) { service.send(:logger) } let(:lfs_client) { service.send(:lfs_client) } diff --git a/spec/services/lfs/unlock_file_service_spec.rb b/spec/services/lfs/unlock_file_service_spec.rb index 7ab269f897a..45fd1adcfb4 100644 --- a/spec/services/lfs/unlock_file_service_spec.rb +++ b/spec/services/lfs/unlock_file_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Lfs::UnlockFileService do +RSpec.describe Lfs::UnlockFileService, feature_category: :source_code_management do let(:project) { create(:project) } let(:current_user) { create(:user) } let(:lock_author) { create(:user) } diff --git a/spec/services/loose_foreign_keys/batch_cleaner_service_spec.rb b/spec/services/loose_foreign_keys/batch_cleaner_service_spec.rb index 735f090d926..6eee83d5ee9 100644 --- a/spec/services/loose_foreign_keys/batch_cleaner_service_spec.rb +++ b/spec/services/loose_foreign_keys/batch_cleaner_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe LooseForeignKeys::BatchCleanerService do +RSpec.describe LooseForeignKeys::BatchCleanerService, feature_category: :database do include MigrationsHelpers def create_table_structure diff --git a/spec/services/loose_foreign_keys/cleaner_service_spec.rb b/spec/services/loose_foreign_keys/cleaner_service_spec.rb index 2cfd8385953..04f6270c5f2 100644 --- a/spec/services/loose_foreign_keys/cleaner_service_spec.rb +++ b/spec/services/loose_foreign_keys/cleaner_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe LooseForeignKeys::CleanerService do +RSpec.describe LooseForeignKeys::CleanerService, feature_category: :database do let(:schema) { ApplicationRecord.connection.current_schema } let(:deleted_records) do [ diff --git a/spec/services/loose_foreign_keys/process_deleted_records_service_spec.rb b/spec/services/loose_foreign_keys/process_deleted_records_service_spec.rb index 1824f822ba8..af010547cc9 100644 --- a/spec/services/loose_foreign_keys/process_deleted_records_service_spec.rb +++ b/spec/services/loose_foreign_keys/process_deleted_records_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe LooseForeignKeys::ProcessDeletedRecordsService do +RSpec.describe LooseForeignKeys::ProcessDeletedRecordsService, feature_category: :database do include MigrationsHelpers def create_table_structure diff --git a/spec/services/markdown_content_rewriter_service_spec.rb b/spec/services/markdown_content_rewriter_service_spec.rb index d94289856cf..bf15ef08647 100644 --- a/spec/services/markdown_content_rewriter_service_spec.rb +++ b/spec/services/markdown_content_rewriter_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MarkdownContentRewriterService do +RSpec.describe MarkdownContentRewriterService, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:source_parent) { create(:project, :public) } let_it_be(:target_parent) { create(:project, :public) } diff --git a/spec/services/markup/rendering_service_spec.rb b/spec/services/markup/rendering_service_spec.rb index 99ab87f2072..952ee33da98 100644 --- a/spec/services/markup/rendering_service_spec.rb +++ b/spec/services/markup/rendering_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Markup::RenderingService do +RSpec.describe Markup::RenderingService, feature_category: :projects do describe '#execute' do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) do @@ -111,5 +111,22 @@ RSpec.describe Markup::RenderingService do is_expected.to eq(expected_html) end end + + context 'with reStructuredText' do + let(:file_name) { 'foo.rst' } + let(:text) { "####\nPART\n####" } + + it 'returns rendered html' do + is_expected.to eq("<h1>PART</h1>\n\n") + end + + context 'when input has an invalid syntax' do + let(:text) { "####\nPART\n##" } + + it 'uses a simple formatter for html' do + is_expected.to eq("<p>####\n<br>PART\n<br>##</p>") + end + end + end end end diff --git a/spec/services/mattermost/create_team_service_spec.rb b/spec/services/mattermost/create_team_service_spec.rb new file mode 100644 index 00000000000..b9e5162aab4 --- /dev/null +++ b/spec/services/mattermost/create_team_service_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mattermost::CreateTeamService, feature_category: :integrations do + let(:user) { create(:user) } + let(:group) { create(:group) } + + subject { described_class.new(group, user) } + + it 'creates a team' do + expect_next_instance_of(::Mattermost::Team) do |instance| + expect(instance).to receive(:create).with(name: anything, display_name: anything, type: anything) + end + + subject.execute + end + + it 'adds an error if a team could not be created' do + expect_next_instance_of(::Mattermost::Team) do |instance| + expect(instance).to receive(:create).and_raise(::Mattermost::ClientError, 'client error') + end + + subject.execute + + expect(group.errors).to be_present + end +end diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb index ca5c052d032..6c0d47e98ba 100644 --- a/spec/services/members/approve_access_request_service_spec.rb +++ b/spec/services/members/approve_access_request_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::ApproveAccessRequestService do +RSpec.describe Members::ApproveAccessRequestService, feature_category: :subgroups do let(:project) { create(:project, :public) } let(:group) { create(:group, :public) } let(:current_user) { create(:user) } @@ -14,13 +14,17 @@ RSpec.describe Members::ApproveAccessRequestService do shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do it 'raises Gitlab::Access::AccessDeniedError' do - expect { described_class.new(current_user, params).execute(access_requester, **opts) }.to raise_error(Gitlab::Access::AccessDeniedError) + expect do + described_class.new(current_user, params).execute(access_requester, **opts) + end.to raise_error(Gitlab::Access::AccessDeniedError) end end shared_examples 'a service approving an access request' do it 'succeeds' do - expect { described_class.new(current_user, params).execute(access_requester, **opts) }.to change { source.requesters.count }.by(-1) + expect do + described_class.new(current_user, params).execute(access_requester, **opts) + end.to change { source.requesters.count }.by(-1) end it 'returns a <Source>Member' do @@ -32,7 +36,15 @@ RSpec.describe Members::ApproveAccessRequestService do it 'calls the method to resolve access request for the approver' do expect_next_instance_of(described_class) do |instance| - expect(instance).to receive(:resolve_access_request_todos).with(current_user, access_requester) + expect(instance).to receive(:resolve_access_request_todos).with(access_requester) + end + + described_class.new(current_user, params).execute(access_requester, **opts) + end + + it 'resolves the todos for the access requests' do + expect_next_instance_of(TodoService) do |instance| + expect(instance).to receive(:resolve_access_request_todos).with(access_requester) end described_class.new(current_user, params).execute(access_requester, **opts) diff --git a/spec/services/members/base_service_spec.rb b/spec/services/members/base_service_spec.rb index b2db599db9c..514c25fbc03 100644 --- a/spec/services/members/base_service_spec.rb +++ b/spec/services/members/base_service_spec.rb @@ -3,17 +3,16 @@ require 'spec_helper' RSpec.describe Members::BaseService, feature_category: :projects do - let_it_be(:current_user) { create(:user) } let_it_be(:access_requester) { create(:group_member) } describe '#resolve_access_request_todos' do it 'calls the resolve_access_request_todos of todo service' do expect_next_instance_of(TodoService) do |todo_service| expect(todo_service) - .to receive(:resolve_access_request_todos).with(current_user, access_requester) + .to receive(:resolve_access_request_todos).with(access_requester) end - described_class.new.send(:resolve_access_request_todos, current_user, access_requester) + described_class.new.send(:resolve_access_request_todos, access_requester) end end end diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb index 756e1cf403c..13f233162cd 100644 --- a/spec/services/members/create_service_spec.rb +++ b/spec/services/members/create_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state, :sidekiq_inline do +RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state, :sidekiq_inline, + feature_category: :subgroups do let_it_be(:source, reload: true) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:member) { create(:user) } diff --git a/spec/services/members/creator_service_spec.rb b/spec/services/members/creator_service_spec.rb index ad4c649086b..8191eefbe95 100644 --- a/spec/services/members/creator_service_spec.rb +++ b/spec/services/members/creator_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::CreatorService do +RSpec.describe Members::CreatorService, feature_category: :subgroups do let_it_be(:source, reload: true) { create(:group, :public) } let_it_be(:member_type) { GroupMember } let_it_be(:user) { create(:user) } diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 2b956bec469..498b9576875 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -44,7 +44,7 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do it 'resolves the access request todos for the owner' do expect_next_instance_of(described_class) do |instance| - expect(instance).to receive(:resolve_access_request_todos).with(current_user, member) + expect(instance).to receive(:resolve_access_request_todos).with(member) end described_class.new(current_user).execute(member, **opts) @@ -463,16 +463,26 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do end context 'subresources' do - let(:user) { create(:user) } - let(:member_user) { create(:user) } + let_it_be_with_reload(:user) { create(:user) } + let_it_be_with_reload(:member_user) { create(:user) } + + let_it_be_with_reload(:group) { create(:group, :public) } + let_it_be_with_reload(:subgroup) { create(:group, parent: group) } + let_it_be(:private_subgroup) { create(:group, :private, parent: group, name: 'private_subgroup') } + let_it_be(:private_subgroup_with_direct_membership) { create(:group, :private, parent: group) } + let_it_be_with_reload(:subsubgroup) { create(:group, parent: subgroup) } + + let_it_be_with_reload(:group_project) { create(:project, :public, group: group) } + let_it_be_with_reload(:control_project) { create(:project, :private, group: subsubgroup) } + let_it_be_with_reload(:subsubproject) { create(:project, :public, group: subsubgroup) } - let(:group) { create(:group, :public) } - let(:subgroup) { create(:group, parent: group) } - let(:subsubgroup) { create(:group, parent: subgroup) } - let(:subsubproject) { create(:project, group: subsubgroup) } + let_it_be(:private_subgroup_project) do + create(:project, :private, group: private_subgroup, name: 'private_subgroup_project') + end - let(:group_project) { create(:project, :public, group: group) } - let(:control_project) { create(:project, group: subsubgroup) } + let_it_be(:private_subgroup_with_direct_membership_project) do + create(:project, :private, group: private_subgroup_with_direct_membership, name: 'private_subgroup_project') + end context 'with memberships' do before do @@ -481,14 +491,68 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do subsubproject.add_developer(member_user) group_project.add_developer(member_user) control_project.add_maintainer(user) + private_subgroup_with_direct_membership.add_developer(member_user) group.add_owner(user) @group_member = create(:group_member, :developer, group: group, user: member_user) end + let_it_be(:todo_in_public_group_project) do + create(:todo, :pending, + project: group_project, + user: member_user, + target: create(:issue, project: group_project) + ) + end + + let_it_be(:mr_in_public_group_project) do + create(:merge_request, source_project: group_project, assignees: [member_user]) + end + + let_it_be(:todo_in_private_subgroup_project) do + create(:todo, :pending, + project: private_subgroup_project, + user: member_user, + target: create(:issue, project: private_subgroup_project) + ) + end + + let_it_be(:mr_in_private_subgroup_project) do + create(:merge_request, source_project: private_subgroup_project, assignees: [member_user]) + end + + let_it_be(:todo_in_public_subsubgroup_project) do + create(:todo, :pending, + project: subsubproject, + user: member_user, + target: create(:issue, project: subsubproject) + ) + end + + let_it_be(:mr_in_public_subsubgroup_project) do + create(:merge_request, source_project: subsubproject, assignees: [member_user]) + end + + let_it_be(:todo_in_private_subgroup_with_direct_membership_project) do + create(:todo, :pending, + project: private_subgroup_with_direct_membership_project, + user: member_user, + target: create(:issue, project: private_subgroup_with_direct_membership_project) + ) + end + + let_it_be(:mr_in_private_subgroup_with_direct_membership_project) do + create(:merge_request, + source_project: private_subgroup_with_direct_membership_project, + assignees: [member_user] + ) + end + context 'with skipping of subresources' do + subject(:execute_service) { described_class.new(user).execute(@group_member, skip_subresources: true) } + before do - described_class.new(user).execute(@group_member, skip_subresources: true) + execute_service end it 'removes the group membership' do @@ -514,11 +578,35 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do it 'does not remove the user from the control project' do expect(control_project.members.map(&:user)).to include(user) end + + context 'todos', :sidekiq_inline do + it 'removes todos for which the user no longer has access' do + expect(member_user.todos).to include( + todo_in_public_group_project, + todo_in_public_subsubgroup_project, + todo_in_private_subgroup_with_direct_membership_project + ) + + expect(member_user.todos).not_to include(todo_in_private_subgroup_project) + end + end + + context 'issuables', :sidekiq_inline do + subject(:execute_service) do + described_class.new(user).execute(@group_member, skip_subresources: true, unassign_issuables: true) + end + + it 'removes assigned issuables, even in subresources' do + expect(member_user.assigned_merge_requests).to be_empty + end + end end context 'without skipping of subresources' do + subject(:execute_service) { described_class.new(user).execute(@group_member, skip_subresources: false) } + before do - described_class.new(user).execute(@group_member, skip_subresources: false) + execute_service end it 'removes the project membership' do @@ -544,6 +632,30 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do it 'does not remove the user from the control project' do expect(control_project.members.map(&:user)).to include(user) end + + context 'todos', :sidekiq_inline do + it 'removes todos for which the user no longer has access' do + expect(member_user.todos).to include( + todo_in_public_group_project, + todo_in_public_subsubgroup_project + ) + + expect(member_user.todos).not_to include( + todo_in_private_subgroup_project, + todo_in_private_subgroup_with_direct_membership_project + ) + end + end + + context 'issuables', :sidekiq_inline do + subject(:execute_service) do + described_class.new(user).execute(@group_member, skip_subresources: false, unassign_issuables: true) + end + + it 'removes assigned issuables' do + expect(member_user.assigned_merge_requests).to be_empty + end + end end end @@ -626,4 +738,13 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do expect(project.members.not_accepted_invitations_by_user(member_user)).to be_empty end end + + describe '#mark_as_recursive_call' do + it 'marks the instance as recursive' do + service = described_class.new(current_user) + service.mark_as_recursive_call + + expect(service.send(:recursive_call?)).to eq(true) + end + end end diff --git a/spec/services/members/groups/creator_service_spec.rb b/spec/services/members/groups/creator_service_spec.rb index fced7195046..4c13106145e 100644 --- a/spec/services/members/groups/creator_service_spec.rb +++ b/spec/services/members/groups/creator_service_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe Members::Groups::CreatorService do +RSpec.describe Members::Groups::CreatorService, feature_category: :subgroups do let_it_be(:source, reload: true) { create(:group, :public) } + let_it_be(:source2, reload: true) { create(:group, :public) } let_it_be(:user) { create(:user) } describe '.access_levels' do @@ -16,6 +17,7 @@ RSpec.describe Members::Groups::CreatorService do describe '.add_members' do it_behaves_like 'bulk member creation' do + let_it_be(:source_type) { Group } let_it_be(:member_type) { GroupMember } end end diff --git a/spec/services/members/import_project_team_service_spec.rb b/spec/services/members/import_project_team_service_spec.rb index 96e8db1ba73..af9b30aa0b3 100644 --- a/spec/services/members/import_project_team_service_spec.rb +++ b/spec/services/members/import_project_team_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::ImportProjectTeamService do +RSpec.describe Members::ImportProjectTeamService, feature_category: :subgroups do describe '#execute' do let_it_be(:source_project) { create(:project) } let_it_be(:target_project) { create(:project) } diff --git a/spec/services/members/invitation_reminder_email_service_spec.rb b/spec/services/members/invitation_reminder_email_service_spec.rb index 768a8719d54..da23965eabe 100644 --- a/spec/services/members/invitation_reminder_email_service_spec.rb +++ b/spec/services/members/invitation_reminder_email_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::InvitationReminderEmailService do +RSpec.describe Members::InvitationReminderEmailService, feature_category: :subgroups do describe 'sending invitation reminders' do subject { described_class.new(invitation).execute } diff --git a/spec/services/members/invite_member_builder_spec.rb b/spec/services/members/invite_member_builder_spec.rb index 52de65364c4..e7bbec4e0ef 100644 --- a/spec/services/members/invite_member_builder_spec.rb +++ b/spec/services/members/invite_member_builder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::InviteMemberBuilder do +RSpec.describe Members::InviteMemberBuilder, feature_category: :subgroups do let_it_be(:source) { create(:group) } let_it_be(:existing_member) { create(:group_member) } diff --git a/spec/services/members/invite_service_spec.rb b/spec/services/members/invite_service_spec.rb index 23d4d671afc..22294b3fda5 100644 --- a/spec/services/members/invite_service_spec.rb +++ b/spec/services/members/invite_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_shared_state, :sidekiq_inline do +RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_shared_state, :sidekiq_inline, + feature_category: :subgroups do let_it_be(:project, reload: true) { create(:project) } let_it_be(:user) { project.first_owner } let_it_be(:project_user) { create(:user) } diff --git a/spec/services/members/projects/creator_service_spec.rb b/spec/services/members/projects/creator_service_spec.rb index 5dfba7adf0f..7ec7361a285 100644 --- a/spec/services/members/projects/creator_service_spec.rb +++ b/spec/services/members/projects/creator_service_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe Members::Projects::CreatorService do +RSpec.describe Members::Projects::CreatorService, feature_category: :projects do let_it_be(:source, reload: true) { create(:project, :public) } + let_it_be(:source2, reload: true) { create(:project, :public) } let_it_be(:user) { create(:user) } describe '.access_levels' do @@ -16,6 +17,7 @@ RSpec.describe Members::Projects::CreatorService do describe '.add_members' do it_behaves_like 'bulk member creation' do + let_it_be(:source_type) { Project } let_it_be(:member_type) { ProjectMember } end end diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb index 69eea2aea4b..ef8ee6492ab 100644 --- a/spec/services/members/request_access_service_spec.rb +++ b/spec/services/members/request_access_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::RequestAccessService do +RSpec.describe Members::RequestAccessService, feature_category: :subgroups do let(:user) { create(:user) } shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do diff --git a/spec/services/members/standard_member_builder_spec.rb b/spec/services/members/standard_member_builder_spec.rb index 16daff53d31..69b764f3f16 100644 --- a/spec/services/members/standard_member_builder_spec.rb +++ b/spec/services/members/standard_member_builder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::StandardMemberBuilder do +RSpec.describe Members::StandardMemberBuilder, feature_category: :subgroups do let_it_be(:source) { create(:group) } let_it_be(:existing_member) { create(:group_member) } diff --git a/spec/services/members/unassign_issuables_service_spec.rb b/spec/services/members/unassign_issuables_service_spec.rb index 3f7ccb7bab3..37dfbd16c56 100644 --- a/spec/services/members/unassign_issuables_service_spec.rb +++ b/spec/services/members/unassign_issuables_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::UnassignIssuablesService do +RSpec.describe Members::UnassignIssuablesService, feature_category: :subgroups do let_it_be(:group) { create(:group, :private) } let_it_be(:project) { create(:project, group: group) } let_it_be(:user, reload: true) { create(:user) } diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb index 8a7f9a84c77..b94b44c8485 100644 --- a/spec/services/members/update_service_spec.rb +++ b/spec/services/members/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Members::UpdateService do +RSpec.describe Members::UpdateService, feature_category: :subgroups do let_it_be(:project) { create(:project, :public) } let_it_be(:group) { create(:group, :public) } let_it_be(:current_user) { create(:user) } diff --git a/spec/services/merge_requests/add_context_service_spec.rb b/spec/services/merge_requests/add_context_service_spec.rb index 448be27efe8..5fca2c17a3c 100644 --- a/spec/services/merge_requests/add_context_service_spec.rb +++ b/spec/services/merge_requests/add_context_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::AddContextService do +RSpec.describe MergeRequests::AddContextService, feature_category: :code_review_workflow do let(:project) { create(:project, :repository) } let(:admin) { create(:admin) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: admin) } diff --git a/spec/services/merge_requests/add_spent_time_service_spec.rb b/spec/services/merge_requests/add_spent_time_service_spec.rb index 1e0b3e07f26..5d6d33c14d7 100644 --- a/spec/services/merge_requests/add_spent_time_service_spec.rb +++ b/spec/services/merge_requests/add_spent_time_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::AddSpentTimeService do +RSpec.describe MergeRequests::AddSpentTimeService, feature_category: :code_review_workflow do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, :repository) } let_it_be_with_reload(:merge_request) { create(:merge_request, :simple, :unique_branches, source_project: project) } diff --git a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb index 8d1abe5ea89..1307e2be3be 100644 --- a/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb +++ b/spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::MergeRequests::AddTodoWhenBuildFailsService do +RSpec.describe ::MergeRequests::AddTodoWhenBuildFailsService, feature_category: :code_review_workflow do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:sha) { '1234567890abcdef1234567890abcdef12345678' } @@ -74,10 +74,13 @@ RSpec.describe ::MergeRequests::AddTodoWhenBuildFailsService do context 'when build belongs to a merge request pipeline' do let(:pipeline) do - create(:ci_pipeline, source: :merge_request_event, - ref: merge_request.merge_ref_path, - merge_request: merge_request, - merge_requests_as_head_pipeline: [merge_request]) + create( + :ci_pipeline, + source: :merge_request_event, + ref: merge_request.merge_ref_path, + merge_request: merge_request, + merge_requests_as_head_pipeline: [merge_request] + ) end let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) } @@ -119,10 +122,13 @@ RSpec.describe ::MergeRequests::AddTodoWhenBuildFailsService do context 'when build belongs to a merge request pipeline' do let(:pipeline) do - create(:ci_pipeline, source: :merge_request_event, - ref: merge_request.merge_ref_path, - merge_request: merge_request, - merge_requests_as_head_pipeline: [merge_request]) + create( + :ci_pipeline, + source: :merge_request_event, + ref: merge_request.merge_ref_path, + merge_request: merge_request, + merge_requests_as_head_pipeline: [merge_request] + ) end let(:commit_status) { create(:ci_build, ref: merge_request.merge_ref_path, pipeline: pipeline) } diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb index f2823b1f0c7..50a3d49d4a3 100644 --- a/spec/services/merge_requests/after_create_service_spec.rb +++ b/spec/services/merge_requests/after_create_service_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe MergeRequests::AfterCreateService, feature_category: :code_review_workflow do let_it_be(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } subject(:after_create_service) do described_class.new(project: merge_request.target_project, current_user: merge_request.author) @@ -68,6 +69,12 @@ RSpec.describe MergeRequests::AfterCreateService, feature_category: :code_review execute_service end + it 'executes hooks with default action' do + expect(project).to receive(:execute_hooks) + + execute_service + end + it_behaves_like 'records an onboarding progress action', :merge_request_created do let(:namespace) { merge_request.target_project.namespace } end @@ -143,22 +150,6 @@ RSpec.describe MergeRequests::AfterCreateService, feature_category: :code_review expect { execute_service }.to change { counter.read(:create) }.by(1) end - context 'with a milestone' do - let(:milestone) { create(:milestone, project: merge_request.target_project) } - - before do - merge_request.update!(milestone_id: milestone.id) - end - - it 'deletes the cache key for milestone merge request counter', :use_clean_rails_memory_store_caching do - expect_next_instance_of(Milestones::MergeRequestsCountService, milestone) do |service| - expect(service).to receive(:delete_cache).and_call_original - end - - execute_service - end - end - context 'todos' do it 'does not creates todos' do attributes = { diff --git a/spec/services/merge_requests/approval_service_spec.rb b/spec/services/merge_requests/approval_service_spec.rb index 1d6427900b9..6140021c8d2 100644 --- a/spec/services/merge_requests/approval_service_spec.rb +++ b/spec/services/merge_requests/approval_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::ApprovalService do +RSpec.describe MergeRequests::ApprovalService, feature_category: :code_review_workflow do describe '#execute' do let(:user) { create(:user) } let(:merge_request) { create(:merge_request, reviewers: [user]) } diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb index cf405c0102e..9f82207086b 100644 --- a/spec/services/merge_requests/assign_issues_service_spec.rb +++ b/spec/services/merge_requests/assign_issues_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::AssignIssuesService do +RSpec.describe MergeRequests::AssignIssuesService, feature_category: :code_review_workflow do let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } let(:issue) { create(:issue, project: project) } @@ -37,12 +37,14 @@ RSpec.describe MergeRequests::AssignIssuesService do it 'accepts precomputed data for closes_issues' do issue2 = create(:issue, project: project) - service2 = described_class.new(project: project, - current_user: user, - params: { - merge_request: merge_request, - closes_issues: [issue, issue2] - }) + service2 = described_class.new( + project: project, + current_user: user, + params: { + merge_request: merge_request, + closes_issues: [issue, issue2] + } + ) expect(service2.assignable_issues.count).to eq 2 end diff --git a/spec/services/merge_requests/base_service_spec.rb b/spec/services/merge_requests/base_service_spec.rb index bd907ba6015..1ca4bfe622c 100644 --- a/spec/services/merge_requests/base_service_spec.rb +++ b/spec/services/merge_requests/base_service_spec.rb @@ -15,6 +15,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl let_it_be(:project) { create(:project, :repository) } + let(:user) { project.first_owner } let(:title) { 'Awesome merge_request' } let(:params) do { @@ -25,14 +26,14 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl } end - subject { MergeRequests::CreateService.new(project: project, current_user: project.first_owner, params: params) } - describe '#execute_hooks' do + subject { MergeRequests::CreateService.new(project: project, current_user: user, params: params).execute } + shared_examples 'enqueues Jira sync worker' do specify :aggregate_failures do expect(JiraConnect::SyncMergeRequestWorker).to receive(:perform_async).with(kind_of(Numeric), kind_of(Numeric)).and_call_original Sidekiq::Testing.fake! do - expect { subject.execute }.to change(JiraConnect::SyncMergeRequestWorker.jobs, :size).by(1) + expect { subject }.to change(JiraConnect::SyncMergeRequestWorker.jobs, :size).by(1) end end end @@ -40,7 +41,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl shared_examples 'does not enqueue Jira sync worker' do it do Sidekiq::Testing.fake! do - expect { subject.execute }.not_to change(JiraConnect::SyncMergeRequestWorker.jobs, :size) + expect { subject }.not_to change(JiraConnect::SyncMergeRequestWorker.jobs, :size) end end end @@ -53,7 +54,20 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl context 'MR contains Jira issue key' do let(:title) { 'Awesome merge_request with issue JIRA-123' } - it_behaves_like 'enqueues Jira sync worker' + it_behaves_like 'does not enqueue Jira sync worker' + + context 'for UpdateService' do + subject { MergeRequests::UpdateService.new(project: project, current_user: user, params: params).execute(merge_request) } + + let(:merge_request) do + create(:merge_request, :simple, title: 'Old title', + assignee_ids: [user.id], + source_project: project, + author: user) + end + + it_behaves_like 'enqueues Jira sync worker' + end end context 'MR does not contain Jira issue key' do @@ -69,13 +83,13 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl describe `#create_pipeline_for` do let_it_be(:merge_request) { create(:merge_request) } - subject { MergeRequests::ExampleService.new(project: project, current_user: project.first_owner, params: params) } + subject { MergeRequests::ExampleService.new(project: project, current_user: user, params: params) } context 'async: false' do it 'creates a pipeline directly' do expect(MergeRequests::CreatePipelineService) .to receive(:new) - .with(hash_including(project: project, current_user: project.first_owner, params: { allow_duplicate: false })) + .with(hash_including(project: project, current_user: user, params: { allow_duplicate: false })) .and_call_original expect(MergeRequests::CreatePipelineWorker).not_to receive(:perform_async) @@ -86,7 +100,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl it 'passes :allow_duplicate as true' do expect(MergeRequests::CreatePipelineService) .to receive(:new) - .with(hash_including(project: project, current_user: project.first_owner, params: { allow_duplicate: true })) + .with(hash_including(project: project, current_user: user, params: { allow_duplicate: true })) .and_call_original expect(MergeRequests::CreatePipelineWorker).not_to receive(:perform_async) @@ -100,7 +114,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl expect(MergeRequests::CreatePipelineService).not_to receive(:new) expect(MergeRequests::CreatePipelineWorker) .to receive(:perform_async) - .with(project.id, project.first_owner.id, merge_request.id, { "allow_duplicate" => false }) + .with(project.id, user.id, merge_request.id, { "allow_duplicate" => false }) .and_call_original Sidekiq::Testing.fake! do @@ -113,7 +127,7 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl expect(MergeRequests::CreatePipelineService).not_to receive(:new) expect(MergeRequests::CreatePipelineWorker) .to receive(:perform_async) - .with(project.id, project.first_owner.id, merge_request.id, { "allow_duplicate" => true }) + .with(project.id, user.id, merge_request.id, { "allow_duplicate" => true }) .and_call_original Sidekiq::Testing.fake! do @@ -123,4 +137,8 @@ RSpec.describe MergeRequests::BaseService, feature_category: :code_review_workfl end end end + + describe '#constructor_container_arg' do + it { expect(described_class.constructor_container_arg("some-value")).to eq({ project: "some-value" }) } + end end diff --git a/spec/services/merge_requests/cleanup_refs_service_spec.rb b/spec/services/merge_requests/cleanup_refs_service_spec.rb index e8690ae5bf2..960b8101c36 100644 --- a/spec/services/merge_requests/cleanup_refs_service_spec.rb +++ b/spec/services/merge_requests/cleanup_refs_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::CleanupRefsService do +RSpec.describe MergeRequests::CleanupRefsService, feature_category: :code_review_workflow do describe '.schedule' do let(:merge_request) { create(:merge_request) } diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 2c0817550c6..25c75ae7244 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -88,7 +88,11 @@ RSpec.describe MergeRequests::CloseService, feature_category: :code_review_workf end it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do - expect { execute } + expect do + execute + + BatchLoader::Executor.clear_current + end .to change { project.open_merge_requests_count }.from(1).to(0) end diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb index 5132eac0158..5eb53b1bcba 100644 --- a/spec/services/merge_requests/conflicts/list_service_spec.rb +++ b/spec/services/merge_requests/conflicts/list_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Conflicts::ListService do +RSpec.describe MergeRequests::Conflicts::ListService, feature_category: :code_review_workflow do describe '#can_be_resolved_in_ui?' do def create_merge_request(source_branch, target_branch = 'conflict-start') create(:merge_request, source_branch: source_branch, target_branch: target_branch, merge_status: :unchecked) do |mr| diff --git a/spec/services/merge_requests/conflicts/resolve_service_spec.rb b/spec/services/merge_requests/conflicts/resolve_service_spec.rb index 0abc70f71b0..002a07ff14e 100644 --- a/spec/services/merge_requests/conflicts/resolve_service_spec.rb +++ b/spec/services/merge_requests/conflicts/resolve_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Conflicts::ResolveService do +RSpec.describe MergeRequests::Conflicts::ResolveService, feature_category: :code_review_workflow do include ProjectForksHelper let(:user) { create(:user) } let(:project) { create(:project, :public, :repository) } @@ -12,15 +12,22 @@ RSpec.describe MergeRequests::Conflicts::ResolveService do end let(:merge_request) do - create(:merge_request, - source_branch: 'conflict-resolvable', source_project: project, - target_branch: 'conflict-start') + create( + :merge_request, + source_branch: 'conflict-resolvable', + source_project: project, + target_branch: 'conflict-start' + ) end let(:merge_request_from_fork) do - create(:merge_request, - source_branch: 'conflict-resolvable-fork', source_project: forked_project, - target_branch: 'conflict-start', target_project: project) + create( + :merge_request, + source_branch: 'conflict-resolvable-fork', + source_project: forked_project, + target_branch: 'conflict-start', + target_project: project + ) end describe '#execute' do diff --git a/spec/services/merge_requests/create_approval_event_service_spec.rb b/spec/services/merge_requests/create_approval_event_service_spec.rb index 3d41ace11a7..4876c992337 100644 --- a/spec/services/merge_requests/create_approval_event_service_spec.rb +++ b/spec/services/merge_requests/create_approval_event_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::CreateApprovalEventService do +RSpec.describe MergeRequests::CreateApprovalEventService, feature_category: :code_review_workflow do let(:user) { create(:user) } let(:merge_request) { create(:merge_request) } let(:project) { merge_request.project } diff --git a/spec/services/merge_requests/create_pipeline_service_spec.rb b/spec/services/merge_requests/create_pipeline_service_spec.rb index f11e3d0d1df..9c2321a2f16 100644 --- a/spec/services/merge_requests/create_pipeline_service_spec.rb +++ b/spec/services/merge_requests/create_pipeline_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::CreatePipelineService, :clean_gitlab_redis_cache do +RSpec.describe MergeRequests::CreatePipelineService, :clean_gitlab_redis_cache, feature_category: :code_review_workflow do include ProjectForksHelper let_it_be(:project, refind: true) { create(:project, :repository) } diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index 394fc269ac3..7705278f30d 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state, feature_category: :code_review_workflow do include ProjectForksHelper + include AfterNextHelpers let(:project) { create(:project, :repository) } let(:user) { create(:user) } @@ -27,7 +28,6 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state, f before do project.add_maintainer(user) project.add_developer(user2) - allow(service).to receive(:execute_hooks) end it 'creates an MR' do @@ -38,13 +38,18 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state, f expect(merge_request.merge_params['force_remove_source_branch']).to eq('1') end - it 'executes hooks with default action' do - expect(service).to have_received(:execute_hooks).with(merge_request) + it 'does not execute hooks' do + expect(project).not_to receive(:execute_hooks) + + service.execute end it 'refreshes the number of open merge requests', :use_clean_rails_memory_store_caching do - expect { service.execute } - .to change { project.open_merge_requests_count }.from(0).to(1) + expect do + service.execute + + BatchLoader::Executor.clear_current + end.to change { project.open_merge_requests_count }.from(0).to(1) end it 'creates exactly 1 create MR event', :sidekiq_might_not_need_inline do @@ -245,10 +250,13 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state, f context "when branch pipeline was created before a merge request pipline has been created" do before do - create(:ci_pipeline, project: merge_request.source_project, - sha: merge_request.diff_head_sha, - ref: merge_request.source_branch, - tag: false) + create( + :ci_pipeline, + project: merge_request.source_project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch, + tag: false + ) merge_request end @@ -333,6 +341,19 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state, f end end + context 'with a milestone' do + let(:milestone) { create(:milestone, project: project) } + + let(:opts) { { title: 'Awesome merge_request', source_branch: 'feature', target_branch: 'master', milestone_id: milestone.id } } + + it 'deletes the cache key for milestone merge request counter' do + expect_next(Milestones::MergeRequestsCountService, milestone) + .to receive(:delete_cache).and_call_original + + expect(merge_request).to be_persisted + end + end + it_behaves_like 'reviewer_ids filter' do let(:execute) { service.execute } end @@ -428,19 +449,29 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state, f } end - it 'invalidates open merge request counter for assignees when merge request is assigned' do + before do project.add_maintainer(user2) + end + it 'invalidates open merge request counter for assignees when merge request is assigned' do described_class.new(project: project, current_user: user, params: opts).execute expect(user2.assigned_open_merge_requests_count).to eq 1 end + + it 'records the assignee assignment event', :sidekiq_inline do + mr = described_class.new(project: project, current_user: user, params: opts).execute.reload + + expect(mr.assignment_events).to match([have_attributes(user_id: user2.id, action: 'add')]) + end end context "when issuable feature is private" do before do - project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE, - merge_requests_access_level: ProjectFeature::PRIVATE) + project.project_feature.update!( + issues_access_level: ProjectFeature::PRIVATE, + merge_requests_access_level: ProjectFeature::PRIVATE + ) end levels = [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC] diff --git a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb index d2070a466b1..d9e60911ada 100644 --- a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb +++ b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state do +RSpec.describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state, + feature_category: :code_review_workflow do let(:merge_request) { create(:merge_request) } let!(:subject) { described_class.new(merge_request) } diff --git a/spec/services/merge_requests/execute_approval_hooks_service_spec.rb b/spec/services/merge_requests/execute_approval_hooks_service_spec.rb index 863c47e8191..9f460648b05 100644 --- a/spec/services/merge_requests/execute_approval_hooks_service_spec.rb +++ b/spec/services/merge_requests/execute_approval_hooks_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::ExecuteApprovalHooksService do +RSpec.describe MergeRequests::ExecuteApprovalHooksService, feature_category: :code_review_workflow do let(:user) { create(:user) } let(:merge_request) { create(:merge_request) } let(:project) { merge_request.project } diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb index 5027acbba0a..f2dbc02f12c 100644 --- a/spec/services/merge_requests/ff_merge_service_spec.rb +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -2,15 +2,17 @@ require 'spec_helper' -RSpec.describe MergeRequests::FfMergeService do +RSpec.describe MergeRequests::FfMergeService, feature_category: :code_review_workflow do let(:user) { create(:user) } let(:user2) { create(:user) } let(:merge_request) do - create(:merge_request, - source_branch: 'flatten-dir', - target_branch: 'improve/awesome', - assignees: [user2], - author: create(:user)) + create( + :merge_request, + source_branch: 'flatten-dir', + target_branch: 'improve/awesome', + assignees: [user2], + author: create(:user) + ) end let(:project) { merge_request.project } diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb index 5f81e1728fa..31b3e513a51 100644 --- a/spec/services/merge_requests/get_urls_service_spec.rb +++ b/spec/services/merge_requests/get_urls_service_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe MergeRequests::GetUrlsService do +RSpec.describe MergeRequests::GetUrlsService, feature_category: :code_review_workflow do include ProjectForksHelper let(:project) { create(:project, :public, :repository) } diff --git a/spec/services/merge_requests/handle_assignees_change_service_spec.rb b/spec/services/merge_requests/handle_assignees_change_service_spec.rb index 3db3efedb84..951e59afe7f 100644 --- a/spec/services/merge_requests/handle_assignees_change_service_spec.rb +++ b/spec/services/merge_requests/handle_assignees_change_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::HandleAssigneesChangeService do +RSpec.describe MergeRequests::HandleAssigneesChangeService, feature_category: :code_review_workflow do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } let_it_be(:assignee) { create(:user) } diff --git a/spec/services/merge_requests/mark_reviewer_reviewed_service_spec.rb b/spec/services/merge_requests/mark_reviewer_reviewed_service_spec.rb index 8437876c3cf..172c2133168 100644 --- a/spec/services/merge_requests/mark_reviewer_reviewed_service_spec.rb +++ b/spec/services/merge_requests/mark_reviewer_reviewed_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::MarkReviewerReviewedService do +RSpec.describe MergeRequests::MarkReviewerReviewedService, feature_category: :code_review_workflow do let(:current_user) { create(:user) } let(:merge_request) { create(:merge_request, reviewers: [current_user]) } let(:reviewer) { merge_request.merge_request_reviewers.find_by(user_id: current_user.id) } diff --git a/spec/services/merge_requests/merge_orchestration_service_spec.rb b/spec/services/merge_requests/merge_orchestration_service_spec.rb index ebcd2f0e277..389956bf258 100644 --- a/spec/services/merge_requests/merge_orchestration_service_spec.rb +++ b/spec/services/merge_requests/merge_orchestration_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::MergeOrchestrationService do +RSpec.describe MergeRequests::MergeOrchestrationService, feature_category: :code_review_workflow do let_it_be(:maintainer) { create(:user) } let(:merge_params) { { sha: merge_request.diff_head_sha } } @@ -10,8 +10,11 @@ RSpec.describe MergeRequests::MergeOrchestrationService do let(:service) { described_class.new(project, user, merge_params) } let!(:merge_request) do - create(:merge_request, source_project: project, source_branch: 'feature', - target_project: project, target_branch: 'master') + create( + :merge_request, + source_project: project, source_branch: 'feature', + target_project: project, target_branch: 'master' + ) end shared_context 'fresh repository' do diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index d3bf203d6bb..c77cf288f56 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::MergeService do +RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workflow do include ExclusiveLeaseHelpers let_it_be(:user) { create(:user) } @@ -69,12 +69,15 @@ RSpec.describe MergeRequests::MergeService do let(:merge_request) do # A merge request with 5 commits - create(:merge_request, :simple, - author: user2, - assignees: [user2], - squash: true, - source_branch: 'improve/awesome', - target_branch: 'fix') + create( + :merge_request, + :simple, + author: user2, + assignees: [user2], + squash: true, + source_branch: 'improve/awesome', + target_branch: 'fix' + ) end it 'merges the merge request with squashed commits' do @@ -147,12 +150,15 @@ RSpec.describe MergeRequests::MergeService do context 'when an invalid sha is passed' do let(:merge_request) do - create(:merge_request, :simple, - author: user2, - assignees: [user2], - squash: true, - source_branch: 'improve/awesome', - target_branch: 'fix') + create( + :merge_request, + :simple, + author: user2, + assignees: [user2], + squash: true, + source_branch: 'improve/awesome', + target_branch: 'fix' + ) end let(:merge_params) do @@ -351,9 +357,12 @@ RSpec.describe MergeRequests::MergeService do service.execute(merge_request) expect(merge_request.merge_error).to eq(error_message) - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(error_message))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message) + ) + ) end end @@ -366,9 +375,12 @@ RSpec.describe MergeRequests::MergeService do service.execute(merge_request) expect(merge_request.merge_error).to eq(described_class::GENERIC_ERROR_MESSAGE) - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(error_message))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message) + ) + ) end it 'logs and saves error if user is not authorized' do @@ -394,9 +406,12 @@ RSpec.describe MergeRequests::MergeService do service.execute(merge_request) expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook') - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(error_message))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message) + ) + ) end it 'logs and saves error if commit is not created' do @@ -408,9 +423,12 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request).to be_open expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.merge_error).to include(described_class::GENERIC_ERROR_MESSAGE) - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(described_class::GENERIC_ERROR_MESSAGE))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(described_class::GENERIC_ERROR_MESSAGE) + ) + ) end context 'when squashing is required' do @@ -429,9 +447,12 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(error_message))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message) + ) + ) end end @@ -452,9 +473,12 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(error_message))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message) + ) + ) end it 'logs and saves error if there is an PreReceiveError exception' do @@ -470,9 +494,12 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook') - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(error_message))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message) + ) + ) end context 'when fast-forward merge is not allowed' do @@ -494,9 +521,12 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request.merge_commit_sha).to be_nil expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(error_message))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message) + ) + ) end end end @@ -513,9 +543,12 @@ RSpec.describe MergeRequests::MergeService do it 'logs and saves error' do service.execute(merge_request) - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(error_message))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message) + ) + ) end end @@ -527,9 +560,12 @@ RSpec.describe MergeRequests::MergeService do it 'logs and saves error' do service.execute(merge_request) - expect(Gitlab::AppLogger).to have_received(:error) - .with(hash_including(merge_request_info: merge_request.to_reference(full: true), - message: a_string_matching(error_message))) + expect(Gitlab::AppLogger).to have_received(:error).with( + hash_including( + merge_request_info: merge_request.to_reference(full: true), + message: a_string_matching(error_message) + ) + ) end context 'when passing `skip_discussions_check: true` as `options` parameter' do 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 19fac3b5095..8200f60b072 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::MergeToRefService do +RSpec.describe MergeRequests::MergeToRefService, feature_category: :code_review_workflow do shared_examples_for 'MergeService for target ref' do it 'target_ref has the same state of target branch' do repo = merge_request.target_project.repository @@ -210,11 +210,14 @@ RSpec.describe MergeRequests::MergeToRefService do let(:merge_request) { create(:merge_request, assignees: [user], author: user) } let(:project) { merge_request.project } let!(:todo) do - create(:todo, :assigned, - project: project, - author: user, - user: user, - target: merge_request) + create( + :todo, + :assigned, + project: project, + author: user, + user: user, + target: merge_request + ) end before do @@ -258,8 +261,10 @@ RSpec.describe MergeRequests::MergeToRefService do context 'when first merge happens' do let(:merge_request) do - create(:merge_request, source_project: project, source_branch: 'feature', - target_project: project, target_branch: 'master') + create( + :merge_request, source_project: project, source_branch: 'feature', + target_project: project, target_branch: 'master' + ) end it_behaves_like 'successfully merges to ref with merge method' do @@ -269,8 +274,11 @@ RSpec.describe MergeRequests::MergeToRefService do context 'when second merge happens' do let(:merge_request) do - create(:merge_request, source_project: project, source_branch: 'improve/awesome', - target_project: project, target_branch: 'master') + create( + :merge_request, + source_project: project, source_branch: 'improve/awesome', + target_project: project, target_branch: 'master' + ) end it_behaves_like 'successfully merges to ref with merge method' do diff --git a/spec/services/merge_requests/mergeability/check_base_service_spec.rb b/spec/services/merge_requests/mergeability/check_base_service_spec.rb index f07522b43cb..806bde61c23 100644 --- a/spec/services/merge_requests/mergeability/check_base_service_spec.rb +++ b/spec/services/merge_requests/mergeability/check_base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Mergeability::CheckBaseService do +RSpec.describe MergeRequests::Mergeability::CheckBaseService, feature_category: :code_review_workflow do subject(:check_base_service) { described_class.new(merge_request: merge_request, params: params) } let(:merge_request) { double } diff --git a/spec/services/merge_requests/mergeability/check_broken_status_service_spec.rb b/spec/services/merge_requests/mergeability/check_broken_status_service_spec.rb index 6cc1079c94a..b6ee1049bb9 100644 --- a/spec/services/merge_requests/mergeability/check_broken_status_service_spec.rb +++ b/spec/services/merge_requests/mergeability/check_broken_status_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Mergeability::CheckBrokenStatusService do +RSpec.describe MergeRequests::Mergeability::CheckBrokenStatusService, feature_category: :code_review_workflow do subject(:check_broken_status) { described_class.new(merge_request: merge_request, params: {}) } let(:merge_request) { build(:merge_request) } diff --git a/spec/services/merge_requests/mergeability/check_ci_status_service_spec.rb b/spec/services/merge_requests/mergeability/check_ci_status_service_spec.rb index def3cb0ca28..cf835cf70a3 100644 --- a/spec/services/merge_requests/mergeability/check_ci_status_service_spec.rb +++ b/spec/services/merge_requests/mergeability/check_ci_status_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Mergeability::CheckCiStatusService do +RSpec.describe MergeRequests::Mergeability::CheckCiStatusService, feature_category: :code_review_workflow do subject(:check_ci_status) { described_class.new(merge_request: merge_request, params: params) } let(:merge_request) { build(:merge_request) } diff --git a/spec/services/merge_requests/mergeability/check_discussions_status_service_spec.rb b/spec/services/merge_requests/mergeability/check_discussions_status_service_spec.rb index 9f107ce046a..a3b77558ec3 100644 --- a/spec/services/merge_requests/mergeability/check_discussions_status_service_spec.rb +++ b/spec/services/merge_requests/mergeability/check_discussions_status_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Mergeability::CheckDiscussionsStatusService do +RSpec.describe MergeRequests::Mergeability::CheckDiscussionsStatusService, feature_category: :code_review_workflow do subject(:check_discussions_status) { described_class.new(merge_request: merge_request, params: params) } let(:merge_request) { build(:merge_request) } diff --git a/spec/services/merge_requests/mergeability/check_draft_status_service_spec.rb b/spec/services/merge_requests/mergeability/check_draft_status_service_spec.rb index e9363e5d676..cb624705a02 100644 --- a/spec/services/merge_requests/mergeability/check_draft_status_service_spec.rb +++ b/spec/services/merge_requests/mergeability/check_draft_status_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Mergeability::CheckDraftStatusService do +RSpec.describe MergeRequests::Mergeability::CheckDraftStatusService, feature_category: :code_review_workflow do subject(:check_draft_status) { described_class.new(merge_request: merge_request, params: {}) } let(:merge_request) { build(:merge_request) } diff --git a/spec/services/merge_requests/mergeability/check_open_status_service_spec.rb b/spec/services/merge_requests/mergeability/check_open_status_service_spec.rb index 936524b020a..53ad77ea4df 100644 --- a/spec/services/merge_requests/mergeability/check_open_status_service_spec.rb +++ b/spec/services/merge_requests/mergeability/check_open_status_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Mergeability::CheckOpenStatusService do +RSpec.describe MergeRequests::Mergeability::CheckOpenStatusService, feature_category: :code_review_workflow do subject(:check_open_status) { described_class.new(merge_request: merge_request, params: {}) } let(:merge_request) { build(:merge_request) } diff --git a/spec/services/merge_requests/mergeability/detailed_merge_status_service_spec.rb b/spec/services/merge_requests/mergeability/detailed_merge_status_service_spec.rb index 5722bb79cc5..66bcb948cb6 100644 --- a/spec/services/merge_requests/mergeability/detailed_merge_status_service_spec.rb +++ b/spec/services/merge_requests/mergeability/detailed_merge_status_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::MergeRequests::Mergeability::DetailedMergeStatusService do +RSpec.describe ::MergeRequests::Mergeability::DetailedMergeStatusService, feature_category: :code_review_workflow do subject(:detailed_merge_status) { described_class.new(merge_request: merge_request).execute } context 'when merge status is cannot_be_merged_rechecking' do @@ -17,7 +17,19 @@ RSpec.describe ::MergeRequests::Mergeability::DetailedMergeStatusService do let(:merge_request) { create(:merge_request, merge_status: :preparing) } it 'returns :checking' do - expect(detailed_merge_status).to eq(:checking) + allow(merge_request.merge_request_diff).to receive(:persisted?).and_return(false) + + expect(detailed_merge_status).to eq(:preparing) + end + end + + context 'when merge status is preparing and merge request diff is persisted' do + let(:merge_request) { create(:merge_request, merge_status: :preparing) } + + it 'returns :checking' do + allow(merge_request.merge_request_diff).to receive(:persisted?).and_return(true) + + expect(detailed_merge_status).to eq(:mergeable) end end @@ -72,9 +84,14 @@ RSpec.describe ::MergeRequests::Mergeability::DetailedMergeStatusService do context 'when pipeline exists' do before do - create(:ci_pipeline, ci_status, merge_request: merge_request, - project: merge_request.project, sha: merge_request.source_branch_sha, - head_pipeline_of: merge_request) + create( + :ci_pipeline, + ci_status, + merge_request: merge_request, + project: merge_request.project, + sha: merge_request.source_branch_sha, + head_pipeline_of: merge_request + ) end context 'when the pipeline is running' do diff --git a/spec/services/merge_requests/mergeability/logger_spec.rb b/spec/services/merge_requests/mergeability/logger_spec.rb index 3e2a1e9f9fd..1f56b6bebdb 100644 --- a/spec/services/merge_requests/mergeability/logger_spec.rb +++ b/spec/services/merge_requests/mergeability/logger_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Mergeability::Logger, :request_store do +RSpec.describe MergeRequests::Mergeability::Logger, :request_store, feature_category: :code_review_workflow do let_it_be(:merge_request) { create(:merge_request) } subject(:logger) { described_class.new(merge_request: merge_request) } diff --git a/spec/services/merge_requests/mergeability/run_checks_service_spec.rb b/spec/services/merge_requests/mergeability/run_checks_service_spec.rb index c56b38bccc1..bfff582994b 100644 --- a/spec/services/merge_requests/mergeability/run_checks_service_spec.rb +++ b/spec/services/merge_requests/mergeability/run_checks_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::Mergeability::RunChecksService, :clean_gitlab_redis_cache do +RSpec.describe MergeRequests::Mergeability::RunChecksService, :clean_gitlab_redis_cache, feature_category: :code_review_workflow do subject(:run_checks) { described_class.new(merge_request: merge_request, params: {}) } describe '#execute' do diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb index ee23238314e..82ef8440380 100644 --- a/spec/services/merge_requests/mergeability_check_service_spec.rb +++ b/spec/services/merge_requests/mergeability_check_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_state do +RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_state, feature_category: :code_review_workflow do shared_examples_for 'unmergeable merge request' do it 'updates or keeps merge status as cannot_be_merged' do subject @@ -158,9 +158,11 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar threads = execute_within_threads(amount: 3, retry_lease: false) results = threads.map { |t| [t.value.status, t.value.message] } - expect(results).to contain_exactly([:error, 'Failed to obtain a lock'], - [:error, 'Failed to obtain a lock'], - [:success, nil]) + expect(results).to contain_exactly( + [:error, 'Failed to obtain a lock'], + [:error, 'Failed to obtain a lock'], + [:success, nil] + ) end end end @@ -183,11 +185,13 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar context 'when it cannot be merged on git' do let(:merge_request) do - create(:merge_request, - merge_status: :unchecked, - source_branch: 'conflict-resolvable', - source_project: project, - target_branch: 'conflict-start') + create( + :merge_request, + merge_status: :unchecked, + source_branch: 'conflict-resolvable', + source_project: project, + target_branch: 'conflict-start' + ) end it 'returns ServiceResponse.error and keeps merge status as cannot_be_merged' do diff --git a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb index 6ea8626ba73..c7f78dfa992 100644 --- a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb +++ b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::MigrateExternalDiffsService do +RSpec.describe MergeRequests::MigrateExternalDiffsService, feature_category: :code_review_workflow do let(:merge_request) { create(:merge_request) } let(:diff) { merge_request.merge_request_diff } diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb index e486daae15e..f7526c169bd 100644 --- a/spec/services/merge_requests/post_merge_service_spec.rb +++ b/spec/services/merge_requests/post_merge_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::PostMergeService do +RSpec.describe MergeRequests::PostMergeService, feature_category: :code_review_workflow do include ProjectForksHelper let_it_be(:user) { create(:user) } @@ -23,7 +23,11 @@ RSpec.describe MergeRequests::PostMergeService do # Cache the counter before the MR changed state. project.open_merge_requests_count - expect { subject }.to change { project.open_merge_requests_count }.from(1).to(0) + expect do + subject + + BatchLoader::Executor.clear_current + end.to change { project.open_merge_requests_count }.from(1).to(0) end it 'updates metrics' do diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb index 03f3d56cdd2..49ec8b09939 100644 --- a/spec/services/merge_requests/push_options_handler_service_spec.rb +++ b/spec/services/merge_requests/push_options_handler_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::PushOptionsHandlerService do +RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :source_code_management do include ProjectForksHelper let_it_be(:parent_group) { create(:group, :public) } diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb index 704dc1f9000..c8b9ab5a34e 100644 --- a/spec/services/merge_requests/rebase_service_spec.rb +++ b/spec/services/merge_requests/rebase_service_spec.rb @@ -8,10 +8,12 @@ RSpec.describe MergeRequests::RebaseService, feature_category: :source_code_mana let(:user) { create(:user) } let(:rebase_jid) { 'fake-rebase-jid' } let(:merge_request) do - create :merge_request, - source_branch: 'feature_conflict', - target_branch: 'master', - rebase_jid: rebase_jid + create( + :merge_request, + source_branch: 'feature_conflict', + target_branch: 'master', + rebase_jid: rebase_jid + ) end let(:project) { merge_request.project } @@ -102,8 +104,9 @@ RSpec.describe MergeRequests::RebaseService, feature_category: :source_code_mana end it 'returns an error' do - expect(service.execute(merge_request)).to match(status: :error, - message: described_class::REBASE_ERROR) + expect(service.execute(merge_request)).to match( + status: :error, message: described_class::REBASE_ERROR + ) end it 'logs the error' do @@ -154,8 +157,9 @@ RSpec.describe MergeRequests::RebaseService, feature_category: :source_code_mana end it 'returns an error' do - expect(service.execute(merge_request)).to match(status: :error, - message: described_class::REBASE_ERROR) + expect(service.execute(merge_request)).to match( + status: :error, message: described_class::REBASE_ERROR + ) end end @@ -215,9 +219,11 @@ RSpec.describe MergeRequests::RebaseService, feature_category: :source_code_mana message: 'Add new file to target', branch_name: 'master') - create(:merge_request, - source_branch: 'master', source_project: forked_project, - target_branch: 'master', target_project: project) + create( + :merge_request, + source_branch: 'master', source_project: forked_project, + target_branch: 'master', target_project: project + ) end it 'rebases source branch', :sidekiq_might_not_need_inline do diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 0814942b6b7..4d533b67690 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -19,43 +19,53 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor @project = create(:project, :repository, namespace: group) @fork_project = fork_project(@project, @user, repository: true) - @merge_request = create(:merge_request, - source_project: @project, - source_branch: 'master', - target_branch: 'feature', - target_project: @project, - auto_merge_enabled: true, - auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, - merge_user: @user) - - @another_merge_request = create(:merge_request, - source_project: @project, - source_branch: 'master', - target_branch: 'test', - target_project: @project, - auto_merge_enabled: true, - auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, - merge_user: @user) - - @fork_merge_request = create(:merge_request, - source_project: @fork_project, - source_branch: 'master', - target_branch: 'feature', - target_project: @project) - - @build_failed_todo = create(:todo, - :build_failed, - user: @user, - project: @project, - target: @merge_request, - author: @user) - - @fork_build_failed_todo = create(:todo, - :build_failed, - user: @user, - project: @project, - target: @merge_request, - author: @user) + @merge_request = create( + :merge_request, + source_project: @project, + source_branch: 'master', + target_branch: 'feature', + target_project: @project, + auto_merge_enabled: true, + auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, + merge_user: @user + ) + + @another_merge_request = create( + :merge_request, + source_project: @project, + source_branch: 'master', + target_branch: 'test', + target_project: @project, + auto_merge_enabled: true, + auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, + merge_user: @user + ) + + @fork_merge_request = create( + :merge_request, + source_project: @fork_project, + source_branch: 'master', + target_branch: 'feature', + target_project: @project + ) + + @build_failed_todo = create( + :todo, + :build_failed, + user: @user, + project: @project, + target: @merge_request, + author: @user + ) + + @fork_build_failed_todo = create( + :todo, + :build_failed, + user: @user, + project: @project, + target: @merge_request, + author: @user + ) @commits = @merge_request.commits @@ -109,6 +119,14 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor expect(@fork_build_failed_todo).to be_done end + it 'triggers mergeRequestMergeStatusUpdated GraphQL subscription conditionally' do + expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@merge_request) + expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@another_merge_request) + expect(GraphqlTriggers).not_to receive(:merge_request_merge_status_updated).with(@fork_merge_request) + + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + end + context 'when a merge error exists' do let(:error_message) { 'This is a merge error' } @@ -298,10 +316,13 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor context "when branch pipeline was created before a detaced merge request pipeline has been created" do before do - create(:ci_pipeline, project: @merge_request.source_project, - sha: @merge_request.diff_head_sha, - ref: @merge_request.source_branch, - tag: false) + create( + :ci_pipeline, + project: @merge_request.source_project, + sha: @merge_request.diff_head_sha, + ref: @merge_request.source_branch, + tag: false + ) subject end @@ -332,9 +353,11 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor context 'when the pipeline should be skipped' do it 'saves a skipped detached merge request pipeline' do - project.repository.create_file(@user, 'new-file.txt', 'A new file', - message: '[skip ci] This is a test', - branch_name: 'master') + project.repository.create_file( + @user, 'new-file.txt', 'A new file', + message: '[skip ci] This is a test', + branch_name: 'master' + ) expect { subject } .to change { @merge_request.pipelines_for_merge_request.count }.by(1) @@ -444,11 +467,13 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor context 'when an MR to be closed was empty already' do let!(:empty_fork_merge_request) do - create(:merge_request, - source_project: @fork_project, - source_branch: 'master', - target_branch: 'master', - target_project: @project) + create( + :merge_request, + source_project: @fork_project, + source_branch: 'master', + target_branch: 'master', + target_project: @project + ) end before do @@ -589,23 +614,33 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor context 'forked projects with the same source branch name as target branch' do let!(:first_commit) do - @fork_project.repository.create_file(@user, 'test1.txt', 'Test data', - message: 'Test commit', - branch_name: 'master') + @fork_project.repository.create_file( + @user, + 'test1.txt', + 'Test data', + message: 'Test commit', + branch_name: 'master' + ) end let!(:second_commit) do - @fork_project.repository.create_file(@user, 'test2.txt', 'More test data', - message: 'Second test commit', - branch_name: 'master') + @fork_project.repository.create_file( + @user, + 'test2.txt', + 'More test data', + message: 'Second test commit', + branch_name: 'master' + ) end let!(:forked_master_mr) do - create(:merge_request, - source_project: @fork_project, - source_branch: 'master', - target_branch: 'master', - target_project: @project) + create( + :merge_request, + source_project: @fork_project, + source_branch: 'master', + target_branch: 'master', + target_project: @project + ) end let(:force_push_commit) { @project.commit('feature').id } @@ -639,9 +674,13 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor end it 'does not increase the diff count for a new push to target branch' do - new_commit = @project.repository.create_file(@user, 'new-file.txt', 'A new file', - message: 'This is a test', - branch_name: 'master') + new_commit = @project.repository.create_file( + @user, + 'new-file.txt', + 'A new file', + message: 'This is a test', + branch_name: 'master' + ) expect do service.new(project: @project, current_user: @user).execute(@newrev, new_commit, 'refs/heads/master') @@ -713,10 +752,12 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor CommitCollection.new(project, [commit], 'close-by-commit') ) - merge_request = create(:merge_request, - target_branch: 'master', - source_branch: 'close-by-commit', - source_project: project) + merge_request = create( + :merge_request, + target_branch: 'master', + source_branch: 'close-by-commit', + source_project: project + ) refresh_service = service.new(project: project, current_user: user) allow(refresh_service).to receive(:execute_hooks) @@ -735,11 +776,13 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor CommitCollection.new(forked_project, [commit], 'close-by-commit') ) - merge_request = create(:merge_request, - target_branch: 'master', - target_project: project, - source_branch: 'close-by-commit', - source_project: forked_project) + merge_request = create( + :merge_request, + target_branch: 'master', + target_project: project, + source_branch: 'close-by-commit', + source_project: forked_project + ) refresh_service = service.new(project: forked_project, current_user: user) allow(refresh_service).to receive(:execute_hooks) @@ -759,11 +802,13 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor end it 'marks the merge request as draft from fixup commits' do - fixup_merge_request = create(:merge_request, - source_project: @project, - source_branch: 'wip', - target_branch: 'master', - target_project: @project) + fixup_merge_request = create( + :merge_request, + source_project: @project, + source_branch: 'wip', + target_branch: 'master', + target_project: @project + ) commits = fixup_merge_request.commits oldrev = commits.last.id newrev = commits.first.id @@ -778,11 +823,13 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor end it 'references the commit that caused the draft status' do - draft_merge_request = create(:merge_request, - source_project: @project, - source_branch: 'wip', - target_branch: 'master', - target_project: @project) + draft_merge_request = create( + :merge_request, + source_project: @project, + source_branch: 'wip', + target_branch: 'master', + target_project: @project + ) commits = draft_merge_request.commits oldrev = commits.last.id @@ -896,22 +943,23 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor end let_it_be(:merge_request, refind: true) do - create(:merge_request, - author: author, - source_project: source_project, - source_branch: 'feature', - target_branch: 'master', - target_project: target_project, - auto_merge_enabled: true, - auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, - merge_user: user) + create( + :merge_request, + author: author, + source_project: source_project, + source_branch: 'feature', + target_branch: 'master', + target_project: target_project, + auto_merge_enabled: true, + auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS, + merge_user: user + ) end let_it_be(:newrev) do - target_project - .repository - .create_file(user, 'test1.txt', 'Test data', - message: 'Test commit', branch_name: 'master') + target_project.repository.create_file( + user, 'test1.txt', 'Test data', message: 'Test commit', branch_name: 'master' + ) end let_it_be(:oldrev) do diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb index 6cf8af1fcf6..77056cbe541 100644 --- a/spec/services/merge_requests/reload_diffs_service_spec.rb +++ b/spec/services/merge_requests/reload_diffs_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_caching do +RSpec.describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_caching, + feature_category: :code_review_workflow do let(:current_user) { create(:user) } let(:merge_request) { create(:merge_request) } let(:subject) { described_class.new(merge_request, current_user) } @@ -18,10 +19,9 @@ RSpec.describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_ new_diff_refs = merge_request.diff_refs expect(merge_request).to receive(:create_merge_request_diff).and_return(new_diff) - expect(merge_request).to receive(:update_diff_discussion_positions) - .with(old_diff_refs: old_diff_refs, - new_diff_refs: new_diff_refs, - current_user: current_user) + expect(merge_request).to receive(:update_diff_discussion_positions).with( + old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs, current_user: current_user + ) subject.execute end diff --git a/spec/services/merge_requests/reload_merge_head_diff_service_spec.rb b/spec/services/merge_requests/reload_merge_head_diff_service_spec.rb index 20b5cf5e3a1..1c315f12221 100644 --- a/spec/services/merge_requests/reload_merge_head_diff_service_spec.rb +++ b/spec/services/merge_requests/reload_merge_head_diff_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::ReloadMergeHeadDiffService do +RSpec.describe MergeRequests::ReloadMergeHeadDiffService, feature_category: :code_review_workflow do let(:merge_request) { create(:merge_request) } subject { described_class.new(merge_request).execute } diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index b9df31b6727..7399b29d06e 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::ReopenService do +RSpec.describe MergeRequests::ReopenService, feature_category: :code_review_workflow do let(:user) { create(:user) } let(:user2) { create(:user) } let(:guest) { create(:user) } @@ -92,7 +92,11 @@ RSpec.describe MergeRequests::ReopenService do it 'refreshes the number of open merge requests for a valid MR' do service = described_class.new(project: project, current_user: user) - expect { service.execute(merge_request) } + expect do + service.execute(merge_request) + + BatchLoader::Executor.clear_current + end .to change { project.open_merge_requests_count }.from(0).to(1) end diff --git a/spec/services/merge_requests/request_review_service_spec.rb b/spec/services/merge_requests/request_review_service_spec.rb index 1d3f92b083f..ef96bf11e0b 100644 --- a/spec/services/merge_requests/request_review_service_spec.rb +++ b/spec/services/merge_requests/request_review_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::RequestReviewService do +RSpec.describe MergeRequests::RequestReviewService, feature_category: :code_review_workflow do let(:current_user) { create(:user) } let(:user) { create(:user) } let(:merge_request) { create(:merge_request, reviewers: [user]) } diff --git a/spec/services/merge_requests/resolve_todos_service_spec.rb b/spec/services/merge_requests/resolve_todos_service_spec.rb index 53bd259f0f4..de7ddbea8bb 100644 --- a/spec/services/merge_requests/resolve_todos_service_spec.rb +++ b/spec/services/merge_requests/resolve_todos_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::ResolveTodosService do +RSpec.describe MergeRequests::ResolveTodosService, feature_category: :code_review_workflow do let_it_be(:merge_request) { create(:merge_request) } let_it_be(:user) { create(:user) } diff --git a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb index 2f191f2ee44..c3a99431dcc 100644 --- a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb +++ b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::ResolvedDiscussionNotificationService do +RSpec.describe MergeRequests::ResolvedDiscussionNotificationService, feature_category: :code_review_workflow do let(:merge_request) { create(:merge_request) } let(:user) { create(:user) } let(:project) { merge_request.project } diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb index 471bb03f18c..1afca466fb5 100644 --- a/spec/services/merge_requests/squash_service_spec.rb +++ b/spec/services/merge_requests/squash_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::SquashService do +RSpec.describe MergeRequests::SquashService, feature_category: :source_code_management do let(:service) { described_class.new(project: project, current_user: user, params: { merge_request: merge_request }) } let(:user) { project.first_owner } let(:project) { create(:project, :repository) } @@ -13,21 +13,27 @@ RSpec.describe MergeRequests::SquashService do end let(:merge_request_with_one_commit) do - create(:merge_request, - source_branch: 'feature', source_project: project, - target_branch: 'master', target_project: project) + create( + :merge_request, + source_branch: 'feature', source_project: project, + target_branch: 'master', target_project: project + ) end let(:merge_request_with_only_new_files) do - create(:merge_request, - source_branch: 'video', source_project: project, - target_branch: 'master', target_project: project) + create( + :merge_request, + source_branch: 'video', source_project: project, + target_branch: 'master', target_project: project + ) end let(:merge_request_with_large_files) do - create(:merge_request, - source_branch: 'squash-large-files', source_project: project, - target_branch: 'master', target_project: project) + create( + :merge_request, + source_branch: 'squash-large-files', source_project: project, + target_branch: 'master', target_project: project + ) end shared_examples 'the squash succeeds' do diff --git a/spec/services/merge_requests/update_assignees_service_spec.rb b/spec/services/merge_requests/update_assignees_service_spec.rb index 2d80d75a262..85d749de83c 100644 --- a/spec/services/merge_requests/update_assignees_service_spec.rb +++ b/spec/services/merge_requests/update_assignees_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::UpdateAssigneesService do +RSpec.describe MergeRequests::UpdateAssigneesService, feature_category: :code_review_workflow do include AfterNextHelpers let_it_be(:group) { create(:group, :public) } @@ -12,13 +12,17 @@ RSpec.describe MergeRequests::UpdateAssigneesService do let_it_be(:user3) { create(:user) } let_it_be_with_reload(:merge_request) do - create(:merge_request, :simple, :unique_branches, - title: 'Old title', - description: "FYI #{user2.to_reference}", - assignee_ids: [user3.id], - source_project: project, - target_project: project, - author: create(:user)) + create( + :merge_request, + :simple, + :unique_branches, + title: 'Old title', + description: "FYI #{user2.to_reference}", + assignee_ids: [user3.id], + source_project: project, + target_project: project, + author: create(:user) + ) end before do @@ -107,12 +111,16 @@ RSpec.describe MergeRequests::UpdateAssigneesService do .with(merge_request, [user3], execute_hooks: true) end - other_mr = create(:merge_request, :simple, :unique_branches, - title: merge_request.title, - description: merge_request.description, - assignee_ids: merge_request.assignee_ids, - source_project: merge_request.project, - author: merge_request.author) + other_mr = create( + :merge_request, + :simple, + :unique_branches, + title: merge_request.title, + description: merge_request.description, + assignee_ids: merge_request.assignee_ids, + source_project: merge_request.project, + author: merge_request.author + ) update_service = ::MergeRequests::UpdateService.new(project: project, current_user: user, params: opts) diff --git a/spec/services/merge_requests/update_reviewers_service_spec.rb b/spec/services/merge_requests/update_reviewers_service_spec.rb index 9f935e1cecf..1ae20e8c29c 100644 --- a/spec/services/merge_requests/update_reviewers_service_spec.rb +++ b/spec/services/merge_requests/update_reviewers_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequests::UpdateReviewersService do +RSpec.describe MergeRequests::UpdateReviewersService, feature_category: :code_review_workflow do include AfterNextHelpers let_it_be(:group) { create(:group, :public) } @@ -12,13 +12,17 @@ RSpec.describe MergeRequests::UpdateReviewersService do let_it_be(:user3) { create(:user) } let_it_be_with_reload(:merge_request) do - create(:merge_request, :simple, :unique_branches, - title: 'Old title', - description: "FYI #{user2.to_reference}", - reviewer_ids: [user3.id], - source_project: project, - target_project: project, - author: create(:user)) + create( + :merge_request, + :simple, + :unique_branches, + title: 'Old title', + description: "FYI #{user2.to_reference}", + reviewer_ids: [user3.id], + source_project: project, + target_project: project, + author: create(:user) + ) end before do diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index e20ebf18e7c..012eb5f6fca 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -15,11 +15,15 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re let(:milestone) { create(:milestone, project: project) } let(:merge_request) do - create(:merge_request, :simple, title: 'Old title', - description: "FYI #{user2.to_reference}", - assignee_ids: [user3.id], - source_project: project, - author: create(:user)) + create( + :merge_request, + :simple, + title: 'Old title', + description: "FYI #{user2.to_reference}", + assignee_ids: [user3.id], + source_project: project, + author: create(:user) + ) end before do @@ -782,6 +786,27 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re expect(user3.assigned_open_merge_requests_count).to eq 0 expect(user2.assigned_open_merge_requests_count).to eq 1 end + + it 'records the assignment history', :sidekiq_inline do + original_assignee = merge_request.assignees.first! + + update_merge_request(assignee_ids: [user2.id]) + + expected_events = [ + have_attributes({ + merge_request_id: merge_request.id, + user_id: original_assignee.id, + action: 'remove' + }), + have_attributes({ + merge_request_id: merge_request.id, + user_id: user2.id, + action: 'add' + }) + ] + + expect(merge_request.assignment_events).to match_array(expected_events) + end end context 'when the target branch changes' do @@ -1166,10 +1191,12 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re let(:source_project) { fork_project(target_project, nil, repository: true) } let(:user) { create(:user) } let(:merge_request) do - create(:merge_request, - source_project: source_project, - source_branch: 'fixes', - target_project: target_project) + create( + :merge_request, + source_project: source_project, + source_branch: 'fixes', + target_project: target_project + ) end before do @@ -1201,10 +1228,12 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re let(:source_project) { fork_project(target_project, nil, repository: true) } let(:user) { target_project.first_owner } let(:merge_request) do - create(:merge_request, - source_project: source_project, - source_branch: 'fixes', - target_project: target_project) + create( + :merge_request, + source_project: source_project, + source_branch: 'fixes', + target_project: target_project + ) end it "cannot be done by members of the target project when they don't have access" do @@ -1222,10 +1251,12 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re context 'updating `target_branch`' do let(:merge_request) do - create(:merge_request, - source_project: project, - source_branch: 'mr-b', - target_branch: 'mr-a') + create( + :merge_request, + source_project: project, + source_branch: 'mr-b', + target_branch: 'mr-a' + ) end it 'updates to master' do diff --git a/spec/services/metrics/dashboard/annotations/create_service_spec.rb b/spec/services/metrics/dashboard/annotations/create_service_spec.rb index 8f5484fcabe..2bcfa54ead7 100644 --- a/spec/services/metrics/dashboard/annotations/create_service_spec.rb +++ b/spec/services/metrics/dashboard/annotations/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::Annotations::CreateService do +RSpec.describe Metrics::Dashboard::Annotations::CreateService, feature_category: :metrics do let_it_be(:user) { create(:user) } let(:description) { 'test annotation' } diff --git a/spec/services/metrics/dashboard/annotations/delete_service_spec.rb b/spec/services/metrics/dashboard/annotations/delete_service_spec.rb index ec2bd3772bf..557d6d95767 100644 --- a/spec/services/metrics/dashboard/annotations/delete_service_spec.rb +++ b/spec/services/metrics/dashboard/annotations/delete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::Annotations::DeleteService do +RSpec.describe Metrics::Dashboard::Annotations::DeleteService, feature_category: :metrics do let(:user) { create(:user) } let(:service_instance) { described_class.new(user, annotation) } diff --git a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb index 47e5557105b..bb11b905a7c 100644 --- a/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb +++ b/spec/services/metrics/dashboard/clone_dashboard_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_store_caching, feature_category: :metrics do include MetricsDashboardHelpers let_it_be(:user) { create(:user) } @@ -92,10 +92,6 @@ RSpec.describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memor ::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter ] - it_behaves_like 'valid dashboard cloning process', - ::Metrics::Dashboard::SelfMonitoringDashboardService::DASHBOARD_PATH, - [::Gitlab::Metrics::Dashboard::Stages::CustomMetricsInserter] - context 'selected branch already exists' do let(:branch) { 'existing_branch' } diff --git a/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb b/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb index f2e32d5eb35..beed23a366f 100644 --- a/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb +++ b/spec/services/metrics/dashboard/cluster_dashboard_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::ClusterDashboardService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::ClusterDashboardService, :use_clean_rails_memory_store_caching, + feature_category: :metrics do include MetricsDashboardHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb b/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb index dbb89af45d0..5d63505e5cc 100644 --- a/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/cluster_metrics_embed_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::ClusterMetricsEmbedService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::ClusterMetricsEmbedService, :use_clean_rails_memory_store_caching, + feature_category: :metrics do include MetricsDashboardHelpers using RSpec::Parameterized::TableSyntax diff --git a/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb b/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb index afeb1646005..940daa38ae7 100644 --- a/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb +++ b/spec/services/metrics/dashboard/custom_dashboard_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::CustomDashboardService, :use_clean_rails_memory_store_caching, + feature_category: :metrics do include MetricsDashboardHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb index 127cec6275c..8117296b048 100644 --- a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::CustomMetricEmbedService do +RSpec.describe Metrics::Dashboard::CustomMetricEmbedService, feature_category: :metrics do include MetricsDashboardHelpers let_it_be(:project, reload: true) { build(:project) } diff --git a/spec/services/metrics/dashboard/default_embed_service_spec.rb b/spec/services/metrics/dashboard/default_embed_service_spec.rb index 647778eadc1..6ef248f6b09 100644 --- a/spec/services/metrics/dashboard/default_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/default_embed_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching, + feature_category: :metrics do include MetricsDashboardHelpers let_it_be(:project) { build(:project) } diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb index 5eb8f24266c..1643f552a70 100644 --- a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching, + feature_category: :metrics do include MetricsDashboardHelpers let_it_be(:project) { build(:project) } diff --git a/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb b/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb index 2905e4599f3..25812a492b2 100644 --- a/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::GitlabAlertEmbedService do +RSpec.describe Metrics::Dashboard::GitlabAlertEmbedService, feature_category: :metrics do include MetricsDashboardHelpers let_it_be(:alert) { create(:prometheus_alert) } diff --git a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb index 5263fd40a40..877a455ea44 100644 --- a/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/grafana_metric_embed_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::GrafanaMetricEmbedService do +RSpec.describe Metrics::Dashboard::GrafanaMetricEmbedService, feature_category: :metrics do include MetricsDashboardHelpers include ReactiveCachingHelpers include GrafanaApiHelpers diff --git a/spec/services/metrics/dashboard/panel_preview_service_spec.rb b/spec/services/metrics/dashboard/panel_preview_service_spec.rb index 787c61cc918..584be717d7c 100644 --- a/spec/services/metrics/dashboard/panel_preview_service_spec.rb +++ b/spec/services/metrics/dashboard/panel_preview_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::PanelPreviewService do +RSpec.describe Metrics::Dashboard::PanelPreviewService, feature_category: :metrics do let_it_be(:project) { create(:project) } let_it_be(:environment) { create(:environment, project: project) } let_it_be(:panel_yml) do @@ -64,18 +64,20 @@ RSpec.describe Metrics::Dashboard::PanelPreviewService do Gitlab::Config::Loader::Yaml::DataTooLargeError, Gitlab::Config::Loader::FormatError ].each do |error_class| - before do - allow_next_instance_of(::Gitlab::Metrics::Dashboard::Processor) do |processor| - allow(processor).to receive(:process).and_raise(error_class.new('error')) + context "with #{error_class}" do + before do + allow_next_instance_of(::Gitlab::Metrics::Dashboard::Processor) do |processor| + allow(processor).to receive(:process).and_raise(error_class.new('error')) + end end - end - it 'returns error service response' do - expect(service_response.error?).to be_truthy - end + it 'returns error service response' do + expect(service_response.error?).to be_truthy + end - it 'returns error message' do - expect(service_response.message).to eq('error') + it 'returns error message' do + expect(service_response.message).to eq('error') + end end end end diff --git a/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb b/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb index 0ea812e93ee..a6fcb6b4842 100644 --- a/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb +++ b/spec/services/metrics/dashboard/pod_dashboard_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::PodDashboardService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::PodDashboardService, :use_clean_rails_memory_store_caching, + feature_category: :cell do include MetricsDashboardHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb b/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb deleted file mode 100644 index d0cefdbeb30..00000000000 --- a/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_memory_store_caching do - include MetricsDashboardHelpers - - let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } - let_it_be(:environment) { create(:environment, project: project) } - - let(:service_params) { [project, user, { environment: environment }] } - - before do - project.add_maintainer(user) if user - stub_application_setting(self_monitoring_project_id: project.id) - end - - subject do - described_class.new(service_params) - end - - describe '#raw_dashboard' do - it_behaves_like '#raw_dashboard raises error if dashboard loading fails' - end - - describe '#get_dashboard' do - let(:service_call) { subject.get_dashboard } - - subject { described_class.new(*service_params) } - - it_behaves_like 'valid dashboard service response' - it_behaves_like 'raises error for users with insufficient permissions' - it_behaves_like 'caches the unprocessed dashboard for subsequent calls' - it_behaves_like 'refreshes cache when dashboard_version is changed' - it_behaves_like 'updates gitlab_metrics_dashboard_processing_time_ms metric' - - it_behaves_like 'dashboard_version contains SHA256 hash of dashboard file content' do - let(:dashboard_path) { described_class::DASHBOARD_PATH } - let(:dashboard_version) { subject.send(:dashboard_version) } - end - end - - describe '.all_dashboard_paths' do - it 'returns the dashboard attributes' do - all_dashboards = described_class.all_dashboard_paths(project) - - expect(all_dashboards).to eq( - [{ - path: described_class::DASHBOARD_PATH, - display_name: described_class::DASHBOARD_NAME, - default: true, - system_dashboard: true, - out_of_the_box_dashboard: true - }] - ) - end - end - - describe '.valid_params?' do - subject { described_class.valid_params?(params) } - - context 'with environment' do - let(:params) { { environment: environment } } - - it { is_expected.to be_truthy } - end - - context 'with dashboard_path' do - let(:params) { { dashboard_path: self_monitoring_dashboard_path } } - - it { is_expected.to be_truthy } - end - - context 'with a different dashboard selected' do - let(:dashboard_path) { '.gitlab/dashboards/test.yml' } - let(:params) { { dashboard_path: dashboard_path, environment: environment } } - - it { is_expected.to be_falsey } - end - - context 'missing environment and dashboard_path' do - let(:params) { {} } - - it { is_expected.to be_falsey } - end - end -end diff --git a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb index e1c6aaeec66..b08b980e50e 100644 --- a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb +++ b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching, + feature_category: :metrics do include MetricsDashboardHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/metrics/dashboard/transient_embed_service_spec.rb b/spec/services/metrics/dashboard/transient_embed_service_spec.rb index 53ea83c33d6..1e3ccde6ae3 100644 --- a/spec/services/metrics/dashboard/transient_embed_service_spec.rb +++ b/spec/services/metrics/dashboard/transient_embed_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::TransientEmbedService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::TransientEmbedService, :use_clean_rails_memory_store_caching, + feature_category: :metrics do let_it_be(:project) { build(:project) } let_it_be(:user) { create(:user) } let_it_be(:environment) { create(:environment, project: project) } diff --git a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb index 148005480ea..15bbe9f9364 100644 --- a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb +++ b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_store_caching do +RSpec.describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_store_caching, feature_category: :metrics do include MetricsDashboardHelpers let_it_be(:user) { create(:user) } diff --git a/spec/services/metrics/global_metrics_update_service_spec.rb b/spec/services/metrics/global_metrics_update_service_spec.rb new file mode 100644 index 00000000000..38c7f9282d9 --- /dev/null +++ b/spec/services/metrics/global_metrics_update_service_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Metrics::GlobalMetricsUpdateService, :prometheus, feature_category: :metrics do + describe '#execute' do + it 'sets gitlab_maintenance_mode gauge metric' do + metric = subject.maintenance_mode_metric + expect(Gitlab).to receive(:maintenance_mode?).and_return(true) + + expect { subject.execute }.to change { metric.get }.from(0).to(1) + end + end +end diff --git a/spec/services/metrics/sample_metrics_service_spec.rb b/spec/services/metrics/sample_metrics_service_spec.rb index b94345500f0..3442b4303db 100644 --- a/spec/services/metrics/sample_metrics_service_spec.rb +++ b/spec/services/metrics/sample_metrics_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::SampleMetricsService do +RSpec.describe Metrics::SampleMetricsService, feature_category: :metrics do describe 'query' do let(:range_start) { '2019-12-02T23:31:45.000Z' } let(:range_end) { '2019-12-03T00:01:45.000Z' } diff --git a/spec/services/metrics/users_starred_dashboards/create_service_spec.rb b/spec/services/metrics/users_starred_dashboards/create_service_spec.rb index 1435e39e458..e08bdca8410 100644 --- a/spec/services/metrics/users_starred_dashboards/create_service_spec.rb +++ b/spec/services/metrics/users_starred_dashboards/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::UsersStarredDashboards::CreateService do +RSpec.describe Metrics::UsersStarredDashboards::CreateService, feature_category: :metrics do let_it_be(:user) { create(:user) } let(:dashboard_path) { 'config/prometheus/common_metrics.yml' } diff --git a/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb b/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb index 5cdffe681eb..8c4bcecc239 100644 --- a/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb +++ b/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Metrics::UsersStarredDashboards::DeleteService do +RSpec.describe Metrics::UsersStarredDashboards::DeleteService, feature_category: :metrics do subject(:service_instance) { described_class.new(user, project, dashboard_path) } let_it_be(:user) { create(:user) } diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb index 53751b40667..f362c8da642 100644 --- a/spec/services/milestones/close_service_spec.rb +++ b/spec/services/milestones/close_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Milestones::CloseService do +RSpec.describe Milestones::CloseService, feature_category: :team_planning do let(:user) { create(:user) } let(:project) { create(:project) } let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) } diff --git a/spec/services/milestones/closed_issues_count_service_spec.rb b/spec/services/milestones/closed_issues_count_service_spec.rb index a3865d08972..f0ed0872c2d 100644 --- a/spec/services/milestones/closed_issues_count_service_spec.rb +++ b/spec/services/milestones/closed_issues_count_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Milestones::ClosedIssuesCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Milestones::ClosedIssuesCountService, :use_clean_rails_memory_store_caching, + feature_category: :team_planning do let(:project) { create(:project) } let(:milestone) { create(:milestone, project: project) } diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb index 93ca4ff653f..78cb05532eb 100644 --- a/spec/services/milestones/create_service_spec.rb +++ b/spec/services/milestones/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Milestones::CreateService do +RSpec.describe Milestones::CreateService, feature_category: :team_planning do let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb index 6c08b7db43a..209177c348b 100644 --- a/spec/services/milestones/destroy_service_spec.rb +++ b/spec/services/milestones/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Milestones::DestroyService do +RSpec.describe Milestones::DestroyService, feature_category: :team_planning do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:milestone) { create(:milestone, title: 'Milestone v1.0', project: project) } diff --git a/spec/services/milestones/find_or_create_service_spec.rb b/spec/services/milestones/find_or_create_service_spec.rb index 1bcaf578441..8a72778a22a 100644 --- a/spec/services/milestones/find_or_create_service_spec.rb +++ b/spec/services/milestones/find_or_create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Milestones::FindOrCreateService do +RSpec.describe Milestones::FindOrCreateService, feature_category: :team_planning do describe '#execute' do subject(:service) { described_class.new(project, user, params) } diff --git a/spec/services/milestones/issues_count_service_spec.rb b/spec/services/milestones/issues_count_service_spec.rb index c944055e4e7..a80b27822b6 100644 --- a/spec/services/milestones/issues_count_service_spec.rb +++ b/spec/services/milestones/issues_count_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Milestones::IssuesCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Milestones::IssuesCountService, :use_clean_rails_memory_store_caching, + feature_category: :team_planning do let(:project) { create(:project) } let(:milestone) { create(:milestone, project: project) } diff --git a/spec/services/milestones/merge_requests_count_service_spec.rb b/spec/services/milestones/merge_requests_count_service_spec.rb index aecc7d5ef52..00b7b95aeb6 100644 --- a/spec/services/milestones/merge_requests_count_service_spec.rb +++ b/spec/services/milestones/merge_requests_count_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Milestones::MergeRequestsCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Milestones::MergeRequestsCountService, :use_clean_rails_memory_store_caching, + feature_category: :team_planning do let_it_be(:project) { create(:project, :empty_repo) } let_it_be(:milestone) { create(:milestone, project: project) } diff --git a/spec/services/milestones/promote_service_spec.rb b/spec/services/milestones/promote_service_spec.rb index 8f4201d8d94..203ac2d3f40 100644 --- a/spec/services/milestones/promote_service_spec.rb +++ b/spec/services/milestones/promote_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Milestones::PromoteService do +RSpec.describe Milestones::PromoteService, feature_category: :team_planning do let(:group) { create(:group) } let(:project) { create(:project, namespace: group) } let(:user) { create(:user) } diff --git a/spec/services/milestones/transfer_service_spec.rb b/spec/services/milestones/transfer_service_spec.rb index de02226661c..ea65f713902 100644 --- a/spec/services/milestones/transfer_service_spec.rb +++ b/spec/services/milestones/transfer_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Milestones::TransferService do +RSpec.describe Milestones::TransferService, feature_category: :team_planning do describe '#execute' do subject(:service) { described_class.new(user, old_group, project) } diff --git a/spec/services/milestones/update_service_spec.rb b/spec/services/milestones/update_service_spec.rb index 85fd89c11ac..76110af2514 100644 --- a/spec/services/milestones/update_service_spec.rb +++ b/spec/services/milestones/update_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Milestones::UpdateService do +RSpec.describe Milestones::UpdateService, feature_category: :team_planning do let(:project) { create(:project) } let(:user) { build(:user) } let(:milestone) { create(:milestone, project: project) } diff --git a/spec/services/ml/experiment_tracking/candidate_repository_spec.rb b/spec/services/ml/experiment_tracking/candidate_repository_spec.rb index e3c05178025..079c36c9613 100644 --- a/spec/services/ml/experiment_tracking/candidate_repository_spec.rb +++ b/spec/services/ml/experiment_tracking/candidate_repository_spec.rb @@ -2,23 +2,23 @@ require 'spec_helper' -RSpec.describe ::Ml::ExperimentTracking::CandidateRepository do +RSpec.describe ::Ml::ExperimentTracking::CandidateRepository, feature_category: :experimentation_activation do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:experiment) { create(:ml_experiments, user: user, project: project) } - let_it_be(:candidate) { create(:ml_candidates, user: user, experiment: experiment) } + let_it_be(:candidate) { create(:ml_candidates, user: user, experiment: experiment, project: project) } let(:repository) { described_class.new(project, user) } - describe '#by_iid' do - let(:iid) { candidate.iid } + describe '#by_eid' do + let(:eid) { candidate.eid } - subject { repository.by_iid(iid) } + subject { repository.by_eid(eid) } it { is_expected.to eq(candidate) } context 'when iid does not exist' do - let(:iid) { non_existing_record_iid.to_s } + let(:eid) { non_existing_record_iid.to_s } it { is_expected.to be_nil } end @@ -38,7 +38,7 @@ RSpec.describe ::Ml::ExperimentTracking::CandidateRepository do it 'creates the candidate' do expect(subject.start_time).to eq(1234) - expect(subject.iid).not_to be_nil + expect(subject.eid).not_to be_nil expect(subject.end_time).to be_nil expect(subject.name).to eq('some_candidate') end @@ -166,6 +166,14 @@ RSpec.describe ::Ml::ExperimentTracking::CandidateRepository do expect { repository.add_tag!(candidate, 'new', props[:value]) }.to raise_error(ActiveRecord::RecordInvalid) end end + + context 'when tag starts with gitlab.' do + it 'calls HandleCandidateGitlabMetadataService' do + expect(Ml::ExperimentTracking::HandleCandidateGitlabMetadataService).to receive(:new).and_call_original + + repository.add_tag!(candidate, 'gitlab.CI_USER_ID', user.id) + end + end end describe "#add_params" do @@ -291,5 +299,15 @@ RSpec.describe ::Ml::ExperimentTracking::CandidateRepository do expect { subject }.to change { candidate.reload.metadata.size }.by(1) end end + + context 'when tags is nil' do + let(:tags) { nil } + + it 'does not handle gitlab tags' do + expect(repository).not_to receive(:handle_gitlab_tags) + + subject + end + end end end diff --git a/spec/services/ml/experiment_tracking/experiment_repository_spec.rb b/spec/services/ml/experiment_tracking/experiment_repository_spec.rb index c3c716b831a..3c645fa84b4 100644 --- a/spec/services/ml/experiment_tracking/experiment_repository_spec.rb +++ b/spec/services/ml/experiment_tracking/experiment_repository_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Ml::ExperimentTracking::ExperimentRepository do +RSpec.describe ::Ml::ExperimentTracking::ExperimentRepository, feature_category: :experimentation_activation do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:experiment) { create(:ml_experiments, user: user, project: project) } diff --git a/spec/services/ml/experiment_tracking/handle_candidate_gitlab_metadata_service_spec.rb b/spec/services/ml/experiment_tracking/handle_candidate_gitlab_metadata_service_spec.rb new file mode 100644 index 00000000000..f0e7c241d5d --- /dev/null +++ b/spec/services/ml/experiment_tracking/handle_candidate_gitlab_metadata_service_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Ml::ExperimentTracking::HandleCandidateGitlabMetadataService, feature_category: :experimentation_activation do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.owner } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:build) { create(:ci_build, :success, pipeline: pipeline) } + + let(:metadata) { [] } + let(:candidate) { create(:ml_candidates, project: project, user: user) } + + describe 'execute' do + subject { described_class.new(candidate, metadata).execute } + + context 'when metadata includes gitlab.CI_JOB_ID', 'and gitlab.CI_JOB_ID is valid' do + let(:metadata) do + [ + { key: 'gitlab.CI_JOB_ID', value: build.id.to_s } + ] + end + + it 'updates candidate correctly', :aggregate_failures do + subject + + expect(candidate.ci_build).to eq(build) + end + end + + context 'when metadata includes gitlab.CI_JOB_ID and gitlab.CI_JOB_ID is invalid' do + let(:metadata) { [{ key: 'gitlab.CI_JOB_ID', value: non_existing_record_id.to_s }] } + + it 'raises error' do + expect { subject } + .to raise_error(ArgumentError, 'gitlab.CI_JOB_ID must refer to an existing build') + end + end + end +end diff --git a/spec/services/namespace_settings/update_service_spec.rb b/spec/services/namespace_settings/update_service_spec.rb index e0f32cb3821..5f1ff6746bc 100644 --- a/spec/services/namespace_settings/update_service_spec.rb +++ b/spec/services/namespace_settings/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe NamespaceSettings::UpdateService do +RSpec.describe NamespaceSettings::UpdateService, feature_category: :subgroups do let(:user) { create(:user) } let(:group) { create(:group) } let(:settings) { {} } diff --git a/spec/services/namespaces/in_product_marketing_emails_service_spec.rb b/spec/services/namespaces/in_product_marketing_emails_service_spec.rb index b44c256802f..8a2ecd5c3e0 100644 --- a/spec/services/namespaces/in_product_marketing_emails_service_spec.rb +++ b/spec/services/namespaces/in_product_marketing_emails_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute' do +RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute', feature_category: :purchase do subject(:execute_service) { described_class.new(track, interval).execute } let(:track) { :create } diff --git a/spec/services/namespaces/package_settings/update_service_spec.rb b/spec/services/namespaces/package_settings/update_service_spec.rb index 10926c5ef57..e21c9a8f1b9 100644 --- a/spec/services/namespaces/package_settings/update_service_spec.rb +++ b/spec/services/namespaces/package_settings/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Namespaces::PackageSettings::UpdateService do +RSpec.describe ::Namespaces::PackageSettings::UpdateService, feature_category: :package_registry do using RSpec::Parameterized::TableSyntax let_it_be_with_reload(:namespace) { create(:group) } diff --git a/spec/services/namespaces/statistics_refresher_service_spec.rb b/spec/services/namespaces/statistics_refresher_service_spec.rb index 2d5f9235bd4..750f98615cc 100644 --- a/spec/services/namespaces/statistics_refresher_service_spec.rb +++ b/spec/services/namespaces/statistics_refresher_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Namespaces::StatisticsRefresherService, '#execute' do +RSpec.describe Namespaces::StatisticsRefresherService, '#execute', feature_category: :subgroups do let(:group) { create(:group) } let(:subgroup) { create(:group, parent: group) } let(:projects) { create_list(:project, 5, namespace: group) } diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb index ad244f62292..1cbbb68205d 100644 --- a/spec/services/note_summary_spec.rb +++ b/spec/services/note_summary_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe NoteSummary do +RSpec.describe NoteSummary, feature_category: :code_review_workflow do let(:project) { build(:project) } let(:noteable) { build(:issue) } let(:user) { build(:user) } diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb index 67d8b37f809..b300a1621de 100644 --- a/spec/services/notes/build_service_spec.rb +++ b/spec/services/notes/build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Notes::BuildService do +RSpec.describe Notes::BuildService, feature_category: :team_planning do include AdminModeHelper let_it_be(:project) { create(:project, :repository) } @@ -67,10 +67,12 @@ RSpec.describe Notes::BuildService do context 'personal snippet note' do def reply(note, user = other_user) - described_class.new(nil, - user, - note: 'Test', - in_reply_to_discussion_id: note.discussion_id).execute + described_class.new( + nil, + user, + note: 'Test', + in_reply_to_discussion_id: note.discussion_id + ).execute end let_it_be(:snippet_author) { noteable_author } diff --git a/spec/services/notes/copy_service_spec.rb b/spec/services/notes/copy_service_spec.rb index 2fa9a462bb9..a7b5e37ccce 100644 --- a/spec/services/notes/copy_service_spec.rb +++ b/spec/services/notes/copy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Notes::CopyService do +RSpec.describe Notes::CopyService, feature_category: :team_planning do describe '#initialize' do let_it_be(:noteable) { create(:issue) } @@ -25,8 +25,10 @@ RSpec.describe Notes::CopyService do context 'simple notes' do let!(:notes) do [ - create(:note, noteable: from_noteable, project: from_noteable.project, - created_at: 2.weeks.ago, updated_at: 1.week.ago), + create( + :note, noteable: from_noteable, project: from_noteable.project, + created_at: 2.weeks.ago, updated_at: 1.week.ago + ), create(:note, noteable: from_noteable, project: from_noteable.project), create(:note, system: true, noteable: from_noteable, project: from_noteable.project) ] diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 1ee9e51433e..240d81bb485 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -108,7 +108,6 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do end it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { issue.namespace } let(:category) { described_class.to_s } let(:action) { 'incident_management_incident_comment' } @@ -124,10 +123,6 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do let(:execute_create_service) { described_class.new(project, user, opts).execute } - before do - stub_feature_flags(notes_create_service_tracking: false) - end - it 'tracks commit comment usage data', :clean_gitlab_redis_shared_state do expect(counter).to receive(:count).with(:create, 'Commit').and_call_original @@ -141,7 +136,6 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do let(:action) { 'create_commit_comment' } let(:label) { 'counts.commit_comment' } let(:namespace) { project.namespace } - let(:feature_flag_name) { :route_hll_to_snowplow_phase4 } end end @@ -157,7 +151,9 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do .and_call_original expect do execute_create_service - end.to change { counter.unique_events(event_names: event, start_date: 1.day.ago, end_date: 1.day.from_now) }.by(1) + end.to change { + counter.unique_events(event_names: event, start_date: Date.today.beginning_of_week, end_date: 1.week.from_now) + }.by(1) end it 'does not track merge request usage data' do @@ -178,22 +174,45 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do create(:merge_request, source_project: project_with_repo, target_project: project_with_repo) end + let(:new_opts) { opts.merge(noteable_type: 'MergeRequest', noteable_id: merge_request.id) } + + it 'calls MergeRequests::MarkReviewerReviewedService service' do + expect_next_instance_of( + MergeRequests::MarkReviewerReviewedService, + project: project_with_repo, current_user: user + ) do |service| + expect(service).to receive(:execute).with(merge_request) + end + + described_class.new(project_with_repo, user, new_opts).execute + end + + it 'does not call MergeRequests::MarkReviewerReviewedService service when skip_set_reviewed is true' do + expect(MergeRequests::MarkReviewerReviewedService).not_to receive(:new) + + described_class.new(project_with_repo, user, new_opts).execute(skip_set_reviewed: true) + end + context 'noteable highlight cache clearing' do let(:position) do - Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: 14, - diff_refs: merge_request.diff_refs) + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: merge_request.diff_refs + ) end let(:new_opts) do - opts.merge(in_reply_to_discussion_id: nil, - type: 'DiffNote', - noteable_type: 'MergeRequest', - noteable_id: merge_request.id, - position: position.to_h, - confidential: false) + opts.merge( + in_reply_to_discussion_id: nil, + type: 'DiffNote', + noteable_type: 'MergeRequest', + noteable_id: merge_request.id, + position: position.to_h, + confidential: false + ) end before do @@ -223,12 +242,14 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do prev_note = create(:diff_note_on_merge_request, noteable: merge_request, project: project_with_repo) reply_opts = - opts.merge(in_reply_to_discussion_id: prev_note.discussion_id, - type: 'DiffNote', - noteable_type: 'MergeRequest', - noteable_id: merge_request.id, - position: position.to_h, - confidential: false) + opts.merge( + in_reply_to_discussion_id: prev_note.discussion_id, + type: 'DiffNote', + noteable_type: 'MergeRequest', + noteable_id: merge_request.id, + position: position.to_h, + confidential: false + ) expect(merge_request).not_to receive(:diffs) @@ -239,11 +260,13 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do context 'note diff file' do let(:line_number) { 14 } let(:position) do - Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: line_number, - diff_refs: merge_request.diff_refs) + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: line_number, + diff_refs: merge_request.diff_refs + ) end let(:previous_note) do @@ -255,12 +278,14 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do end context 'when eligible to have a note diff file' do let(:new_opts) do - opts.merge(in_reply_to_discussion_id: nil, - type: 'DiffNote', - noteable_type: 'MergeRequest', - noteable_id: merge_request.id, - position: position.to_h, - confidential: false) + opts.merge( + in_reply_to_discussion_id: nil, + type: 'DiffNote', + noteable_type: 'MergeRequest', + noteable_id: merge_request.id, + position: position.to_h, + confidential: false + ) end it_behaves_like 'triggers GraphQL subscription mergeRequestMergeStatusUpdated' do @@ -331,12 +356,14 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do context 'when DiffNote is a reply' do let(:new_opts) do - opts.merge(in_reply_to_discussion_id: previous_note.discussion_id, - type: 'DiffNote', - noteable_type: 'MergeRequest', - noteable_id: merge_request.id, - position: position.to_h, - confidential: false) + opts.merge( + in_reply_to_discussion_id: previous_note.discussion_id, + type: 'DiffNote', + noteable_type: 'MergeRequest', + noteable_id: merge_request.id, + position: position.to_h, + confidential: false + ) end it 'note is not associated with a note diff file' do @@ -350,23 +377,27 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do context 'when DiffNote from an image' do let(:image_position) do - Gitlab::Diff::Position.new(old_path: "files/images/6049019_460s.jpg", - new_path: "files/images/6049019_460s.jpg", - width: 100, - height: 100, - x: 1, - y: 100, - diff_refs: merge_request.diff_refs, - position_type: 'image') + Gitlab::Diff::Position.new( + old_path: "files/images/6049019_460s.jpg", + new_path: "files/images/6049019_460s.jpg", + width: 100, + height: 100, + x: 1, + y: 100, + diff_refs: merge_request.diff_refs, + position_type: 'image' + ) end let(:new_opts) do - opts.merge(in_reply_to_discussion_id: nil, - type: 'DiffNote', - noteable_type: 'MergeRequest', - noteable_id: merge_request.id, - position: image_position.to_h, - confidential: false) + opts.merge( + in_reply_to_discussion_id: nil, + type: 'DiffNote', + noteable_type: 'MergeRequest', + noteable_id: merge_request.id, + position: image_position.to_h, + confidential: false + ) end it 'note is not associated with a note diff file' do diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb index 744808525f5..396e23351c9 100644 --- a/spec/services/notes/destroy_service_spec.rb +++ b/spec/services/notes/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Notes::DestroyService do +RSpec.describe Notes::DestroyService, feature_category: :team_planning do let_it_be(:project) { create(:project, :public) } let_it_be(:issue) { create(:issue, project: project) } @@ -38,7 +38,9 @@ RSpec.describe Notes::DestroyService do .and_call_original expect do service_action - end.to change { counter.unique_events(event_names: property, start_date: 1.day.ago, end_date: 1.day.from_now) }.by(1) + end.to change { + counter.unique_events(event_names: property, start_date: Date.today.beginning_of_week, end_date: 1.week.from_now) + }.by(1) end it_behaves_like 'issue_edit snowplow tracking' do @@ -94,8 +96,12 @@ RSpec.describe Notes::DestroyService do it 'tracks design comment removal' do note = create(:note_on_design, project: project) - expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive(:track_issue_design_comment_removed_action).with(author: note.author, - project: project) + expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).to receive( + :track_issue_design_comment_removed_action + ).with( + author: note.author, + project: project + ) described_class.new(project, user).execute(note) end diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb index 17001733c5b..0bcfd6b63d2 100644 --- a/spec/services/notes/post_process_service_spec.rb +++ b/spec/services/notes/post_process_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Notes::PostProcessService do +RSpec.describe Notes::PostProcessService, feature_category: :team_planning do let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index bca954c3959..c65a077f907 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Notes::QuickActionsService do +RSpec.describe Notes::QuickActionsService, feature_category: :team_planning do shared_context 'note on noteable' do let_it_be(:project) { create(:project, :repository) } let_it_be(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } } @@ -182,7 +182,7 @@ RSpec.describe Notes::QuickActionsService do context 'on an incident' do before do - issue.update!(issue_type: :incident) + issue.update!(issue_type: :incident, work_item_type: WorkItems::Type.default_by_type(:incident)) end it 'leaves the note empty' do @@ -224,7 +224,7 @@ RSpec.describe Notes::QuickActionsService do context 'on an incident' do before do - issue.update!(issue_type: :incident) + issue.update!(issue_type: :incident, work_item_type: WorkItems::Type.default_by_type(:incident)) end it 'leaves the note empty' do @@ -250,28 +250,6 @@ RSpec.describe Notes::QuickActionsService do end end - describe '.noteable_update_service_class' do - include_context 'note on noteable' - - it 'returns Issues::UpdateService for a note on an issue' do - note = create(:note_on_issue, project: project) - - expect(described_class.noteable_update_service_class(note)).to eq(Issues::UpdateService) - end - - it 'returns MergeRequests::UpdateService for a note on a merge request' do - note = create(:note_on_merge_request, project: project) - - expect(described_class.noteable_update_service_class(note)).to eq(MergeRequests::UpdateService) - end - - it 'returns Commits::TagService for a note on a commit' do - note = create(:note_on_commit, project: project) - - expect(described_class.noteable_update_service_class(note)).to eq(Commits::TagService) - end - end - describe '.supported?' do include_context 'note on noteable' @@ -322,6 +300,84 @@ RSpec.describe Notes::QuickActionsService do let(:merge_request) { create(:merge_request, source_project: project) } let(:note) { build(:note_on_merge_request, project: project, noteable: merge_request) } end + + context 'note on work item that supports quick actions' do + include_context 'note on noteable' + + let_it_be(:work_item, reload: true) { create(:work_item, project: project) } + + let(:note) { build(:note_on_work_item, project: project, noteable: work_item) } + + let!(:labels) { create_pair(:label, project: project) } + + before do + note.note = note_text + end + + describe 'note with only command' do + describe '/close, /label & /assign' do + let(:note_text) do + %(/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\n) + end + + it 'closes noteable, sets labels, assigns and leave no note' do + content = execute(note) + + expect(content).to be_empty + expect(note.noteable).to be_closed + expect(note.noteable.labels).to match_array(labels) + expect(note.noteable.assignees).to eq([assignee]) + end + end + + describe '/reopen' do + before do + note.noteable.close! + expect(note.noteable).to be_closed + end + let(:note_text) { '/reopen' } + + it 'opens the noteable, and leave no note' do + content = execute(note) + + expect(content).to be_empty + expect(note.noteable).to be_open + end + end + end + + describe 'note with command & text' do + describe '/close, /label, /assign' do + let(:note_text) do + %(HELLO\n/close\n/label ~#{labels.first.name} ~#{labels.last.name}\n/assign @#{assignee.username}\nWORLD) + end + + it 'closes noteable, sets labels, assigns, and sets milestone to noteable' do + content = execute(note) + + expect(content).to eq "HELLO\nWORLD" + expect(note.noteable).to be_closed + expect(note.noteable.labels).to match_array(labels) + expect(note.noteable.assignees).to eq([assignee]) + end + end + + describe '/reopen' do + before do + note.noteable.close + expect(note.noteable).to be_closed + end + let(:note_text) { "HELLO\n/reopen\nWORLD" } + + it 'opens the noteable' do + content = execute(note) + + expect(content).to eq "HELLO\nWORLD" + expect(note.noteable).to be_open + end + end + end + end end context 'CE restriction for issue assignees' do diff --git a/spec/services/notes/render_service_spec.rb b/spec/services/notes/render_service_spec.rb index 09cd7dc572b..d633cdd0448 100644 --- a/spec/services/notes/render_service_spec.rb +++ b/spec/services/notes/render_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Notes::RenderService do +RSpec.describe Notes::RenderService, feature_category: :team_planning do describe '#execute' do it 'renders a Note' do note = double(:note) @@ -27,12 +27,14 @@ RSpec.describe Notes::RenderService do .to receive(:render) .with([note], :note) - described_class.new(user).execute([note], - requested_path: 'foo', - project_wiki: wiki, - ref: 'bar', - only_path: nil, - xhtml: false) + described_class.new(user).execute( + [note], + requested_path: 'foo', + project_wiki: wiki, + ref: 'bar', + only_path: nil, + xhtml: false + ) end end end diff --git a/spec/services/notes/resolve_service_spec.rb b/spec/services/notes/resolve_service_spec.rb index 1c5b308aed1..1b5586ee1b3 100644 --- a/spec/services/notes/resolve_service_spec.rb +++ b/spec/services/notes/resolve_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Notes::ResolveService do +RSpec.describe Notes::ResolveService, feature_category: :team_planning do let(:merge_request) { create(:merge_request) } let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project) } let(:user) { merge_request.author } diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb index 05703ac548d..245cc046775 100644 --- a/spec/services/notes/update_service_spec.rb +++ b/spec/services/notes/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Notes::UpdateService do +RSpec.describe Notes::UpdateService, feature_category: :team_planning do let(:group) { create(:group, :public) } let(:project) { create(:project, :public, group: group) } let(:private_group) { create(:group, :private) } @@ -65,7 +65,7 @@ RSpec.describe Notes::UpdateService do .and_call_original expect do update_note(note: 'new text') - end.to change { counter.unique_events(event_names: event, start_date: 1.day.ago, end_date: 1.day.from_now) }.by(1) + end.to change { counter.unique_events(event_names: event, start_date: Date.today.beginning_of_week, end_date: 1.week.from_now) }.by(1) end it_behaves_like 'issue_edit snowplow tracking' do diff --git a/spec/services/notification_recipients/build_service_spec.rb b/spec/services/notification_recipients/build_service_spec.rb index 899d23ec641..bfd1dcd7d80 100644 --- a/spec/services/notification_recipients/build_service_spec.rb +++ b/spec/services/notification_recipients/build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe NotificationRecipients::BuildService do +RSpec.describe NotificationRecipients::BuildService, feature_category: :team_planning do let(:service) { described_class } let(:assignee) { create(:user) } let(:project) { create(:project, :public) } diff --git a/spec/services/notification_recipients/builder/default_spec.rb b/spec/services/notification_recipients/builder/default_spec.rb index 4d0ddc7c4f7..da991b5951a 100644 --- a/spec/services/notification_recipients/builder/default_spec.rb +++ b/spec/services/notification_recipients/builder/default_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe NotificationRecipients::Builder::Default do +RSpec.describe NotificationRecipients::Builder::Default, feature_category: :team_planning do describe '#build!' do let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :public, group: group).tap { |p| p.add_developer(project_watcher) if project_watcher } } diff --git a/spec/services/notification_recipients/builder/new_note_spec.rb b/spec/services/notification_recipients/builder/new_note_spec.rb index 7d2a4f682c5..e87824f3156 100644 --- a/spec/services/notification_recipients/builder/new_note_spec.rb +++ b/spec/services/notification_recipients/builder/new_note_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe NotificationRecipients::Builder::NewNote do +RSpec.describe NotificationRecipients::Builder::NewNote, feature_category: :team_planning do describe '#notification_recipients' do let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :public, group: group) } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 4161f93cdac..f63f982708d 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -253,6 +253,16 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do it_behaves_like 'participating by assignee notification', check_delivery_jobs_queue: check_delivery_jobs_queue end + shared_examples 'declines the invite' do + specify do + member = source.members.last + + expect do + notification.decline_invite(member) + end.to change { ActionMailer::Base.deliveries.size }.by(1) + end + end + describe '.permitted_actions' do it 'includes public methods' do expect(described_class.permitted_actions).to include(:access_token_created) @@ -518,8 +528,8 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do allow(Notify).to receive(:service_desk_new_note_email) .with(Integer, Integer, String).and_return(mailer) - allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true } - allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true } + allow(::Gitlab::Email::IncomingEmail).to receive(:enabled?) { true } + allow(::Gitlab::Email::IncomingEmail).to receive(:supports_wildcard?) { true } end let(:subject) { NotificationService.new } @@ -3029,7 +3039,7 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do end end - describe '#decline_group_invite' do + describe '#decline_invite' do let(:creator) { create(:user) } let(:group) { create(:group) } let(:member) { create(:user) } @@ -3039,12 +3049,8 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do group.add_developer(member, creator) end - it do - group_member = group.members.last - - expect do - notification.decline_group_invite(group_member) - end.to change { ActionMailer::Base.deliveries.size }.by(1) + it_behaves_like 'declines the invite' do + let(:source) { group } end end @@ -3201,19 +3207,15 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do end end - describe '#decline_project_invite' do + describe '#decline_invite' do let(:member) { create(:user) } before do project.add_developer(member, current_user: project.first_owner) end - it do - project_member = project.members.last - - expect do - notification.decline_project_invite(project_member) - end.to change { ActionMailer::Base.deliveries.size }.by(1) + it_behaves_like 'declines the invite' do + let(:source) { project } end end diff --git a/spec/services/onboarding/progress_service_spec.rb b/spec/services/onboarding/progress_service_spec.rb index 8f3f723613e..e1d6b4cd44b 100644 --- a/spec/services/onboarding/progress_service_spec.rb +++ b/spec/services/onboarding/progress_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Onboarding::ProgressService do +RSpec.describe Onboarding::ProgressService, feature_category: :onboarding do describe '.async' do let_it_be(:namespace) { create(:namespace) } let_it_be(:action) { :git_pull } diff --git a/spec/services/packages/cleanup/execute_policy_service_spec.rb b/spec/services/packages/cleanup/execute_policy_service_spec.rb index 93335c4a821..a083dc0d4ea 100644 --- a/spec/services/packages/cleanup/execute_policy_service_spec.rb +++ b/spec/services/packages/cleanup/execute_policy_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Cleanup::ExecutePolicyService do +RSpec.describe Packages::Cleanup::ExecutePolicyService, feature_category: :package_registry do let_it_be(:project) { create(:project) } let_it_be_with_reload(:policy) { create(:packages_cleanup_policy, project: project) } diff --git a/spec/services/packages/cleanup/update_policy_service_spec.rb b/spec/services/packages/cleanup/update_policy_service_spec.rb index a11fbb766f5..8068c351e5f 100644 --- a/spec/services/packages/cleanup/update_policy_service_spec.rb +++ b/spec/services/packages/cleanup/update_policy_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Cleanup::UpdatePolicyService do +RSpec.describe Packages::Cleanup::UpdatePolicyService, feature_category: :package_registry do using RSpec::Parameterized::TableSyntax let_it_be_with_reload(:project) { create(:project) } diff --git a/spec/services/packages/composer/composer_json_service_spec.rb b/spec/services/packages/composer/composer_json_service_spec.rb index d2187688c4c..15acd79c49e 100644 --- a/spec/services/packages/composer/composer_json_service_spec.rb +++ b/spec/services/packages/composer/composer_json_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Composer::ComposerJsonService do +RSpec.describe Packages::Composer::ComposerJsonService, feature_category: :package_registry do describe '#execute' do let(:branch) { project.repository.find_branch('master') } let(:target) { branch.target } diff --git a/spec/services/packages/composer/create_package_service_spec.rb b/spec/services/packages/composer/create_package_service_spec.rb index 26429a7b5d9..78d5d76fe4f 100644 --- a/spec/services/packages/composer/create_package_service_spec.rb +++ b/spec/services/packages/composer/create_package_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Composer::CreatePackageService do +RSpec.describe Packages::Composer::CreatePackageService, feature_category: :package_registry do include PackagesManagerApiSpecHelpers let_it_be(:package_name) { 'composer-package-name' } diff --git a/spec/services/packages/composer/version_parser_service_spec.rb b/spec/services/packages/composer/version_parser_service_spec.rb index 69253ff934e..ac50f2e2e55 100644 --- a/spec/services/packages/composer/version_parser_service_spec.rb +++ b/spec/services/packages/composer/version_parser_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Composer::VersionParserService do +RSpec.describe Packages::Composer::VersionParserService, feature_category: :package_registry do let_it_be(:params) { {} } describe '#execute' do diff --git a/spec/services/packages/conan/create_package_file_service_spec.rb b/spec/services/packages/conan/create_package_file_service_spec.rb index e655b8d1f9e..6859e52560a 100644 --- a/spec/services/packages/conan/create_package_file_service_spec.rb +++ b/spec/services/packages/conan/create_package_file_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Conan::CreatePackageFileService do +RSpec.describe Packages::Conan::CreatePackageFileService, feature_category: :package_registry do include WorkhorseHelpers let_it_be(:package) { create(:conan_package) } diff --git a/spec/services/packages/conan/create_package_service_spec.rb b/spec/services/packages/conan/create_package_service_spec.rb index 6f644f5ef95..db06463b7fa 100644 --- a/spec/services/packages/conan/create_package_service_spec.rb +++ b/spec/services/packages/conan/create_package_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Conan::CreatePackageService do +RSpec.describe Packages::Conan::CreatePackageService, feature_category: :package_registry do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } diff --git a/spec/services/packages/conan/search_service_spec.rb b/spec/services/packages/conan/search_service_spec.rb index 9e8be164d8c..83ece404d5f 100644 --- a/spec/services/packages/conan/search_service_spec.rb +++ b/spec/services/packages/conan/search_service_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Packages::Conan::SearchService, feature_category: :package_regist let!(:conan_package) { create(:conan_package, project: project) } let!(:conan_package2) { create(:conan_package, project: project) } - subject { described_class.new(user, query: query) } + subject { described_class.new(project, user, query: query) } before do project.add_developer(user) @@ -24,7 +24,7 @@ RSpec.describe Packages::Conan::SearchService, feature_category: :package_regist result = subject.execute expect(result.status).to eq :success - expect(result.payload).to eq(results: [conan_package.conan_recipe, conan_package2.conan_recipe]) + expect(result.payload).to eq(results: [conan_package2.conan_recipe, conan_package.conan_recipe]) end end @@ -71,5 +71,29 @@ RSpec.describe Packages::Conan::SearchService, feature_category: :package_regist expect(result.payload).to eq(results: []) end end + + context 'for project' do + let_it_be(:project2) { create(:project, :public) } + let(:query) { conan_package.name } + let!(:conan_package3) { create(:conan_package, name: conan_package.name, project: project2) } + + context 'when passing a project' do + it 'returns only packages of the given project' do + result = subject.execute + + expect(result.status).to eq :success + expect(result[:results]).to match_array([conan_package.conan_recipe]) + end + end + + context 'when passing a project with nil' do + it 'returns all packages' do + result = described_class.new(nil, user, query: query).execute + + expect(result.status).to eq :success + expect(result[:results]).to eq([conan_package3.conan_recipe, conan_package.conan_recipe]) + end + end + end end end diff --git a/spec/services/packages/conan/single_package_search_service_spec.rb b/spec/services/packages/conan/single_package_search_service_spec.rb new file mode 100644 index 00000000000..1d95d1d4f64 --- /dev/null +++ b/spec/services/packages/conan/single_package_search_service_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Conan::SinglePackageSearchService, feature_category: :package_registry do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + + let!(:conan_package) { create(:conan_package, project: project) } + let!(:conan_package2) { create(:conan_package, project: project) } + + describe '#execute' do + context 'with a valid query and user with permissions' do + before do + allow_next_instance_of(described_class) do |service| + allow(service).to receive(:can_access_project_package?).and_return(true) + end + end + + it 'returns the correct package' do + [conan_package, conan_package2].each do |package| + result = described_class.new(package.conan_recipe, user).execute + + expect(result.status).to eq :success + expect(result[:results]).to match_array([package.conan_recipe]) + end + end + end + + context 'with a user without permissions' do + before do + allow_next_instance_of(described_class) do |service| + allow(service).to receive(:can_access_project_package?).and_return(false) + end + end + + it 'returns an empty array' do + result = described_class.new(conan_package.conan_recipe, user).execute + + expect(result.status).to eq :success + expect(result[:results]).to match_array([]) + end + end + end +end diff --git a/spec/services/packages/create_dependency_service_spec.rb b/spec/services/packages/create_dependency_service_spec.rb index f95e21cd045..06a7a13bdd9 100644 --- a/spec/services/packages/create_dependency_service_spec.rb +++ b/spec/services/packages/create_dependency_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::CreateDependencyService do +RSpec.describe Packages::CreateDependencyService, feature_category: :package_registry do describe '#execute' do let_it_be(:namespace) { create(:namespace) } let_it_be(:version) { '1.0.1' } diff --git a/spec/services/packages/create_event_service_spec.rb b/spec/services/packages/create_event_service_spec.rb index 58fa68b11fe..45c758ec866 100644 --- a/spec/services/packages/create_event_service_spec.rb +++ b/spec/services/packages/create_event_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::CreateEventService do +RSpec.describe Packages::CreateEventService, feature_category: :package_registry do let(:scope) { 'generic' } let(:event_name) { 'push_package' } @@ -15,47 +15,6 @@ RSpec.describe Packages::CreateEventService do subject { described_class.new(nil, user, params).execute } describe '#execute' do - shared_examples 'db package event creation' do |originator_type, expected_scope| - before do - allow(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event) - end - - context 'with feature flag disable' do - before do - stub_feature_flags(collect_package_events: false) - end - - it 'does not create an event' do - expect { subject }.not_to change { Packages::Event.count } - end - end - - context 'with feature flag enabled' do - before do - stub_feature_flags(collect_package_events: true) - end - - it 'creates the event' do - expect { subject }.to change { Packages::Event.count }.by(1) - - expect(subject.originator_type).to eq(originator_type) - expect(subject.originator).to eq(user&.id) - expect(subject.event_scope).to eq(expected_scope) - expect(subject.event_type).to eq(event_name) - end - - context 'on a read-only instance' do - before do - allow(Gitlab::Database).to receive(:read_only?).and_return(true) - end - - it 'does not create an event' do - expect { subject }.not_to change { Packages::Event.count } - end - end - end - end - shared_examples 'redis package unique event creation' do |originator_type, expected_scope| it 'tracks the event' do expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(/package/, values: user.id) @@ -75,7 +34,6 @@ RSpec.describe Packages::CreateEventService do context 'with a user' do let(:user) { create(:user) } - it_behaves_like 'db package event creation', 'user', 'generic' it_behaves_like 'redis package unique event creation', 'user', 'generic' it_behaves_like 'redis package count event creation', 'user', 'generic' end @@ -83,7 +41,6 @@ RSpec.describe Packages::CreateEventService do context 'with a deploy token' do let(:user) { create(:deploy_token) } - it_behaves_like 'db package event creation', 'deploy_token', 'generic' it_behaves_like 'redis package unique event creation', 'deploy_token', 'generic' it_behaves_like 'redis package count event creation', 'deploy_token', 'generic' end @@ -91,7 +48,6 @@ RSpec.describe Packages::CreateEventService do context 'with no user' do let(:user) { nil } - it_behaves_like 'db package event creation', 'guest', 'generic' it_behaves_like 'redis package count event creation', 'guest', 'generic' end @@ -101,14 +57,12 @@ RSpec.describe Packages::CreateEventService do context 'as guest' do let(:user) { nil } - it_behaves_like 'db package event creation', 'guest', 'npm' it_behaves_like 'redis package count event creation', 'guest', 'npm' end context 'with user' do let(:user) { create(:user) } - it_behaves_like 'db package event creation', 'user', 'npm' it_behaves_like 'redis package unique event creation', 'user', 'npm' it_behaves_like 'redis package count event creation', 'user', 'npm' end diff --git a/spec/services/packages/create_package_file_service_spec.rb b/spec/services/packages/create_package_file_service_spec.rb index 2ff00ea8568..5b4ea3e1530 100644 --- a/spec/services/packages/create_package_file_service_spec.rb +++ b/spec/services/packages/create_package_file_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::CreatePackageFileService do +RSpec.describe Packages::CreatePackageFileService, feature_category: :package_registry do let_it_be(:package) { create(:maven_package) } let_it_be(:user) { create(:user) } diff --git a/spec/services/packages/create_temporary_package_service_spec.rb b/spec/services/packages/create_temporary_package_service_spec.rb index 4b8d37401d8..be8b5afc1e0 100644 --- a/spec/services/packages/create_temporary_package_service_spec.rb +++ b/spec/services/packages/create_temporary_package_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::CreateTemporaryPackageService do +RSpec.describe Packages::CreateTemporaryPackageService, feature_category: :package_registry do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:params) { {} } diff --git a/spec/services/packages/debian/create_package_file_service_spec.rb b/spec/services/packages/debian/create_package_file_service_spec.rb index 43928669eb1..b527bf8c1de 100644 --- a/spec/services/packages/debian/create_package_file_service_spec.rb +++ b/spec/services/packages/debian/create_package_file_service_spec.rb @@ -57,7 +57,7 @@ RSpec.describe Packages::Debian::CreatePackageFileService, feature_category: :pa expect(package_file).to be_valid expect(package_file.file.read).to start_with('Format: 1.8') - expect(package_file.size).to eq(2143) + expect(package_file.size).to eq(2422) expect(package_file.file_name).to eq(file_name) expect(package_file.file_sha1).to eq('54321') expect(package_file.file_sha256).to eq('543212345') diff --git a/spec/services/packages/debian/extract_changes_metadata_service_spec.rb b/spec/services/packages/debian/extract_changes_metadata_service_spec.rb index 4d6acac219b..a22c1fc7acc 100644 --- a/spec/services/packages/debian/extract_changes_metadata_service_spec.rb +++ b/spec/services/packages/debian/extract_changes_metadata_service_spec.rb @@ -19,7 +19,7 @@ RSpec.describe Packages::Debian::ExtractChangesMetadataService, feature_category expect(subject[:file_type]).to eq(:changes) expect(subject[:architecture]).to be_nil expect(subject[:fields]).to include(expected_fields) - expect(subject[:files].count).to eq(6) + expect(subject[:files].count).to eq(7) end end diff --git a/spec/services/packages/debian/extract_metadata_service_spec.rb b/spec/services/packages/debian/extract_metadata_service_spec.rb index 412f285152b..1983c49c6b7 100644 --- a/spec/services/packages/debian/extract_metadata_service_spec.rb +++ b/spec/services/packages/debian/extract_metadata_service_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' RSpec.describe Packages::Debian::ExtractMetadataService, feature_category: :package_registry do @@ -6,12 +7,13 @@ RSpec.describe Packages::Debian::ExtractMetadataService, feature_category: :pack subject { service.execute } - RSpec.shared_context 'Debian ExtractMetadata Service' do |trait| + RSpec.shared_context 'with Debian package file' do |trait| let(:package_file) { create(:debian_package_file, trait) } end RSpec.shared_examples 'Test Debian ExtractMetadata Service' do |expected_file_type, expected_architecture, expected_fields| - it "returns file_type #{expected_file_type.inspect}, architecture #{expected_architecture.inspect} and fields #{expected_fields.nil? ? '' : 'including '}#{expected_fields.inspect}", :aggregate_failures do + it "returns file_type #{expected_file_type.inspect}, architecture #{expected_architecture.inspect} and fields #{expected_fields.nil? ? '' : 'including '}#{expected_fields.inspect}", + :aggregate_failures do expect(subject[:file_type]).to eq(expected_file_type) expect(subject[:architecture]).to eq(expected_architecture) @@ -25,29 +27,79 @@ RSpec.describe Packages::Debian::ExtractMetadataService, feature_category: :pack using RSpec::Parameterized::TableSyntax - where(:case_name, :trait, :expected_file_type, :expected_architecture, :expected_fields) do - 'with invalid' | :invalid | :unknown | nil | nil - 'with source' | :source | :source | nil | nil - 'with dsc' | :dsc | :dsc | nil | { 'Binary' => 'sample-dev, libsample0, sample-udeb' } - 'with deb' | :deb | :deb | 'amd64' | { 'Multi-Arch' => 'same' } - 'with udeb' | :udeb | :udeb | 'amd64' | { 'Package' => 'sample-udeb' } - 'with buildinfo' | :buildinfo | :buildinfo | nil | { 'Architecture' => 'amd64 source', 'Build-Architecture' => 'amd64' } - 'with changes' | :changes | :changes | nil | { 'Architecture' => 'source amd64', 'Binary' => 'libsample0 sample-dev sample-udeb' } + context 'with valid file types' do + where(:case_name, :trait, :expected_file_type, :expected_architecture, :expected_fields) do + 'with source' | :source | :source | nil | nil + 'with dsc' | :dsc | :dsc | nil | { 'Binary' => 'sample-dev, libsample0, sample-udeb, sample-ddeb' } + 'with deb' | :deb | :deb | 'amd64' | { 'Multi-Arch' => 'same' } + 'with udeb' | :udeb | :udeb | 'amd64' | { 'Package' => 'sample-udeb' } + 'with ddeb' | :ddeb | :ddeb | 'amd64' | { 'Package' => 'sample-ddeb' } + 'with buildinfo' | :buildinfo | :buildinfo | nil | { 'Architecture' => 'amd64 source', + 'Build-Architecture' => 'amd64' } + 'with changes' | :changes | :changes | nil | { 'Architecture' => 'source amd64', + 'Binary' => 'libsample0 sample-dev sample-udeb' } + end + + with_them do + include_context 'with Debian package file', params[:trait] do + it_behaves_like 'Test Debian ExtractMetadata Service', + params[:expected_file_type], + params[:expected_architecture], + params[:expected_fields] + end + end end - with_them do - include_context 'Debian ExtractMetadata Service', params[:trait] do - it_behaves_like 'Test Debian ExtractMetadata Service', - params[:expected_file_type], - params[:expected_architecture], - params[:expected_fields] + context 'with valid source extensions' do + where(:ext) do + %i[gz bz2 lzma xz] + end + + with_them do + let(:package_file) do + create(:debian_package_file, :source, file_name: "myfile.tar.#{ext}", + file_fixture: 'spec/fixtures/packages/debian/sample_1.2.3~alpha2.tar.xz') + end + + it_behaves_like 'Test Debian ExtractMetadata Service', :source + end + end + + context 'with invalid source extensions' do + where(:ext) do + %i[gzip bzip2] + end + + with_them do + let(:package_file) do + create(:debian_package_file, :source, file_name: "myfile.tar.#{ext}", + file_fixture: 'spec/fixtures/packages/debian/sample_1.2.3~alpha2.tar.xz') + end + + it 'raises an error' do + expect do + subject + end.to raise_error(described_class::ExtractionError, + "unsupported file extension for file #{package_file.file_name}") + end + end + end + + context 'with invalid file name' do + let(:package_file) { create(:debian_package_file, :invalid) } + + it 'raises an error' do + expect do + subject + end.to raise_error(described_class::ExtractionError, + "unsupported file extension for file #{package_file.file_name}") end end context 'with invalid package file' do let(:package_file) { create(:conan_package_file) } - it 'raise error' do + it 'raises an error' do expect { subject }.to raise_error(described_class::ExtractionError, 'invalid package file') 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 index 36f96008582..c2ae3d56864 100644 --- a/spec/services/packages/debian/find_or_create_package_service_spec.rb +++ b/spec/services/packages/debian/find_or_create_package_service_spec.rb @@ -4,13 +4,17 @@ 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) { service.execute.payload[:package] } + let(:package2) { service2.execute.payload[:package] } shared_examples 'find or create Debian package' do it 'returns the same object' do @@ -55,11 +59,24 @@ RSpec.describe Packages::Debian::FindOrCreatePackageService, feature_category: : 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) + expect { package }.to raise_error(ActiveRecord::RecordNotFound, + /^Couldn't find Packages::Debian::ProjectDistribution/) end end end diff --git a/spec/services/packages/debian/generate_distribution_service_spec.rb b/spec/services/packages/debian/generate_distribution_service_spec.rb index 6d179c791a3..27206b847e4 100644 --- a/spec/services/packages/debian/generate_distribution_service_spec.rb +++ b/spec/services/packages/debian/generate_distribution_service_spec.rb @@ -3,20 +3,32 @@ require 'spec_helper' RSpec.describe Packages::Debian::GenerateDistributionService, feature_category: :package_registry do - describe '#execute' do - subject { described_class.new(distribution).execute } + include_context 'with published Debian package' - let(:subject2) { described_class.new(distribution).execute } - let(:subject3) { described_class.new(distribution).execute } + let(:service) { described_class.new(distribution) } - include_context 'with published Debian package' + [:project, :group].each do |container_type| + context "for #{container_type}" do + include_context 'with Debian distribution', container_type - [:project, :group].each do |container_type| - context "for #{container_type}" do - include_context 'with Debian distribution', container_type + describe '#execute' do + subject { service.execute } + + let(:subject2) { described_class.new(distribution).execute } + let(:subject3) { described_class.new(distribution).execute } it_behaves_like 'Generate Debian Distribution and component files' end + + describe '#lease_key' do + subject { service.send(:lease_key) } + + let(:prefix) { "packages:debian:generate_distribution_service:" } + + it 'returns an unique key' do + is_expected.to eq "#{prefix}#{container_type}_distribution:#{distribution.id}" + end + end end end end diff --git a/spec/services/packages/debian/parse_debian822_service_spec.rb b/spec/services/packages/debian/parse_debian822_service_spec.rb index 35b7ead9209..624d4d95e5a 100644 --- a/spec/services/packages/debian/parse_debian822_service_spec.rb +++ b/spec/services/packages/debian/parse_debian822_service_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' RSpec.describe Packages::Debian::ParseDebian822Service, feature_category: :package_registry do @@ -76,14 +77,19 @@ RSpec.describe Packages::Debian::ParseDebian822Service, feature_category: :packa 'Multi-Arch' => 'same', 'Depends' => '${shlibs:Depends}, ${misc:Depends}', 'Description' => "Some mostly empty lib\nUsed in GitLab tests.\n\nTesting another paragraph." - }, + }, 'Package: sample-udeb' => { - 'Package' => 'sample-udeb', - 'Package-Type' => 'udeb', - 'Architecture' => 'any', - 'Depends' => 'installed-base', - 'Description' => 'Some mostly empty udeb' - } + 'Package' => 'sample-udeb', + 'Package-Type' => 'udeb', + 'Architecture' => 'any', + 'Depends' => 'installed-base', + 'Description' => 'Some mostly empty udeb' + }, + 'Package: sample-ddeb' => { + 'Package' => 'sample-ddeb', + 'Architecture' => 'any', + 'Description' => 'Some fake Ubuntu ddeb' + } } expect(subject.execute.to_s).to eq(expected.to_s) diff --git a/spec/services/packages/debian/process_changes_service_spec.rb b/spec/services/packages/debian/process_changes_service_spec.rb index e3ed744377e..dbfcc359f9c 100644 --- a/spec/services/packages/debian/process_changes_service_spec.rb +++ b/spec/services/packages/debian/process_changes_service_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' RSpec.describe Packages::Debian::ProcessChangesService, feature_category: :package_registry do @@ -18,7 +19,7 @@ RSpec.describe Packages::Debian::ProcessChangesService, feature_category: :packa 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(7).to(0) + .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 @@ -55,7 +56,7 @@ RSpec.describe Packages::Debian::ProcessChangesService, feature_category: :packa it_behaves_like 'raises error with missing field', 'Distribution' end - context 'with existing package' do + 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 @@ -64,10 +65,37 @@ RSpec.describe Packages::Debian::ProcessChangesService, feature_category: :packa expect { subject.execute } .to not_change { Packages::Package.count } .and not_change { Packages::PackageFile.count } - .and change(package_file, :package).to(existing_package) + .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 'marked as pending_destruction' do + context 'and marked as pending_destruction' do it 'does not re-use the existing package' do existing_package.pending_destruction! diff --git a/spec/services/packages/debian/process_package_file_service_spec.rb b/spec/services/packages/debian/process_package_file_service_spec.rb index caf29cfc4fa..7782b5fc1a6 100644 --- a/spec/services/packages/debian/process_package_file_service_spec.rb +++ b/spec/services/packages/debian/process_package_file_service_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spec_helper' RSpec.describe Packages::Debian::ProcessPackageFileService, feature_category: :package_registry do @@ -19,14 +20,14 @@ RSpec.describe Packages::Debian::ProcessPackageFileService, feature_category: :p expect { subject.execute } .to not_change(Packages::Package, :count) .and not_change(Packages::PackageFile, :count) - .and change(Packages::Debian::Publication, :count).by(1) + .and change { Packages::Debian::Publication.count }.by(1) .and not_change(package.package_files, :count) .and change { package.reload.name }.to('sample') .and change { package.reload.version }.to('1.2.3~alpha2') .and change { package.reload.status }.from('processing').to('default') .and change { package.reload.debian_publication }.from(nil) - .and change(debian_file_metadatum, :file_type).from('unknown').to(expected_file_type) - .and change(debian_file_metadatum, :component).from(nil).to(component_name) + .and change { debian_file_metadatum.file_type }.from('unknown').to(expected_file_type) + .and change { debian_file_metadatum.component }.from(nil).to(component_name) end end @@ -35,6 +36,7 @@ RSpec.describe Packages::Debian::ProcessPackageFileService, feature_category: :p where(:case_name, :expected_file_type, :file_name, :component_name) do 'with a deb' | 'deb' | 'libsample0_1.2.3~alpha2_amd64.deb' | 'main' 'with an udeb' | 'udeb' | 'sample-udeb_1.2.3~alpha2_amd64.udeb' | 'contrib' + 'with an ddeb' | 'ddeb' | 'sample-ddeb_1.2.3~alpha2_amd64.ddeb' | 'main' end with_them do @@ -66,21 +68,42 @@ RSpec.describe Packages::Debian::ProcessPackageFileService, feature_category: :p expect(::Packages::Debian::GenerateDistributionWorker) .to receive(:perform_async).with(:project, distribution.id) expect { subject.execute } - .to change(Packages::Package, :count).from(2).to(1) - .and change(Packages::PackageFile, :count).from(14).to(8) + .to change { Packages::Package.count }.from(2).to(1) + .and change { Packages::PackageFile.count }.from(16).to(9) .and not_change(Packages::Debian::Publication, :count) - .and change(package.package_files, :count).from(7).to(0) - .and change(package_file, :package).from(package).to(matching_package) + .and change { package.package_files.count }.from(8).to(0) + .and change { package_file.package }.from(package).to(matching_package) .and not_change(matching_package, :name) .and not_change(matching_package, :version) - .and change(debian_file_metadatum, :file_type).from('unknown').to(expected_file_type) - .and change(debian_file_metadatum, :component).from(nil).to(component_name) + .and change { debian_file_metadatum.file_type }.from('unknown').to(expected_file_type) + .and change { debian_file_metadatum.component }.from(nil).to(component_name) expect { package.reload } .to raise_error(ActiveRecord::RecordNotFound) end end + context 'when there is a matching published package in another distribution' do + let!(:matching_package) do + create( + :debian_package, + project: distribution.project, + name: 'sample', + version: '1.2.3~alpha2' + ) + end + + it 'raise ArgumentError', :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(package.package_files, :count) + .and raise_error(ArgumentError, "Debian package sample 1.2.3~alpha2 exists " \ + "in distribution #{matching_package.debian_distribution.codename}") + end + end + context 'when there is a matching published package pending destruction' do let!(:matching_package) do create( diff --git a/spec/services/packages/generic/create_package_file_service_spec.rb b/spec/services/packages/generic/create_package_file_service_spec.rb index 9d6784b7721..06a78c7820f 100644 --- a/spec/services/packages/generic/create_package_file_service_spec.rb +++ b/spec/services/packages/generic/create_package_file_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::Generic::CreatePackageFileService do +RSpec.describe Packages::Generic::CreatePackageFileService, feature_category: :package_registry do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:pipeline) { create(:ci_pipeline, user: user) } @@ -81,9 +81,7 @@ RSpec.describe Packages::Generic::CreatePackageFileService do it_behaves_like 'assigns build to package file' context 'with existing package' do - before do - create(:package_file, package: package, file_name: file_name) - end + let_it_be(:duplicate_file) { create(:package_file, package: package, file_name: file_name) } it { expect { execute_service }.to change { project.package_files.count }.by(1) } @@ -97,6 +95,16 @@ RSpec.describe Packages::Generic::CreatePackageFileService do .and change { project.package_files.count }.by(0) end + context 'when the file is pending destruction' do + before do + duplicate_file.update_column(:status, :pending_destruction) + end + + it 'allows creating the file' do + expect { execute_service }.to change { project.package_files.count }.by(1) + end + end + context 'when the package name matches the exception regex' do before do package.project.namespace.package_settings.update!(generic_duplicate_exception_regex: '.*') diff --git a/spec/services/packages/generic/find_or_create_package_service_spec.rb b/spec/services/packages/generic/find_or_create_package_service_spec.rb index 10ec917bc99..07054fe3651 100644 --- a/spec/services/packages/generic/find_or_create_package_service_spec.rb +++ b/spec/services/packages/generic/find_or_create_package_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::Generic::FindOrCreatePackageService do +RSpec.describe Packages::Generic::FindOrCreatePackageService, feature_category: :package_registry do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:ci_build) { create(:ci_build, :running, user: user) } diff --git a/spec/services/packages/go/create_package_service_spec.rb b/spec/services/packages/go/create_package_service_spec.rb index 4ca1119fbaa..f552af81077 100644 --- a/spec/services/packages/go/create_package_service_spec.rb +++ b/spec/services/packages/go/create_package_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::Go::CreatePackageService do +RSpec.describe Packages::Go::CreatePackageService, feature_category: :package_registry do let_it_be(:project) { create :project_empty_repo, path: 'my-go-lib' } let_it_be(:mod) { create :go_module, project: project } diff --git a/spec/services/packages/go/sync_packages_service_spec.rb b/spec/services/packages/go/sync_packages_service_spec.rb index 565b0f252ce..2881b6fdac9 100644 --- a/spec/services/packages/go/sync_packages_service_spec.rb +++ b/spec/services/packages/go/sync_packages_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::Go::SyncPackagesService do +RSpec.describe Packages::Go::SyncPackagesService, feature_category: :package_registry do include_context 'basic Go module' let(:params) { { info: true, mod: true, zip: true } } diff --git a/spec/services/packages/helm/extract_file_metadata_service_spec.rb b/spec/services/packages/helm/extract_file_metadata_service_spec.rb index f4c61c12344..861d326d12a 100644 --- a/spec/services/packages/helm/extract_file_metadata_service_spec.rb +++ b/spec/services/packages/helm/extract_file_metadata_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Helm::ExtractFileMetadataService do +RSpec.describe Packages::Helm::ExtractFileMetadataService, feature_category: :package_registry do let_it_be(:package_file) { create(:helm_package_file) } let(:service) { described_class.new(package_file) } diff --git a/spec/services/packages/helm/process_file_service_spec.rb b/spec/services/packages/helm/process_file_service_spec.rb index 1be0153a4a5..a1f53e8756c 100644 --- a/spec/services/packages/helm/process_file_service_spec.rb +++ b/spec/services/packages/helm/process_file_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Helm::ProcessFileService do +RSpec.describe Packages::Helm::ProcessFileService, feature_category: :package_registry do let(:package) { create(:helm_package, without_package_files: true, status: 'processing') } let!(:package_file) { create(:helm_package_file, without_loaded_metadatum: true, package: package) } let(:channel) { 'stable' } diff --git a/spec/services/packages/mark_package_files_for_destruction_service_spec.rb b/spec/services/packages/mark_package_files_for_destruction_service_spec.rb index 66534338003..a00a0b79854 100644 --- a/spec/services/packages/mark_package_files_for_destruction_service_spec.rb +++ b/spec/services/packages/mark_package_files_for_destruction_service_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Packages::MarkPackageFilesForDestructionService, :aggregate_failures do +RSpec.describe Packages::MarkPackageFilesForDestructionService, :aggregate_failures, + feature_category: :package_registry do let(:service) { described_class.new(package_files) } describe '#execute', :aggregate_failures do diff --git a/spec/services/packages/mark_package_for_destruction_service_spec.rb b/spec/services/packages/mark_package_for_destruction_service_spec.rb index 125ec53ad61..d65e62b84a6 100644 --- a/spec/services/packages/mark_package_for_destruction_service_spec.rb +++ b/spec/services/packages/mark_package_for_destruction_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::MarkPackageForDestructionService do +RSpec.describe Packages::MarkPackageForDestructionService, feature_category: :package_registry do let_it_be(:user) { create(:user) } let_it_be_with_reload(:package) { create(:npm_package) } @@ -36,6 +36,12 @@ RSpec.describe Packages::MarkPackageForDestructionService do end it 'returns an error ServiceResponse' do + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( + instance_of(StandardError), + project_id: package.project_id, + package_id: package.id + ) + response = service.execute expect(package).not_to receive(:sync_maven_metadata) diff --git a/spec/services/packages/mark_packages_for_destruction_service_spec.rb b/spec/services/packages/mark_packages_for_destruction_service_spec.rb index 5c043b89de8..22278f9927d 100644 --- a/spec/services/packages/mark_packages_for_destruction_service_spec.rb +++ b/spec/services/packages/mark_packages_for_destruction_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline do +RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline, feature_category: :package_registry do let_it_be(:project) { create(:project) } let_it_be_with_reload(:packages) { create_list(:npm_package, 3, project: project) } @@ -76,6 +76,11 @@ RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline do it 'returns an error ServiceResponse' do expect(::Packages::Maven::Metadata::SyncService).not_to receive(:new) + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( + instance_of(StandardError), + package_ids: package_ids + ) + expect { subject }.to not_change { ::Packages::Package.pending_destruction.count } .and not_change { ::Packages::PackageFile.pending_destruction.count } diff --git a/spec/services/packages/maven/create_package_service_spec.rb b/spec/services/packages/maven/create_package_service_spec.rb index 11bf00c1399..2c528c3591e 100644 --- a/spec/services/packages/maven/create_package_service_spec.rb +++ b/spec/services/packages/maven/create_package_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Maven::CreatePackageService do +RSpec.describe Packages::Maven::CreatePackageService, feature_category: :package_registry do let(:project) { create(:project) } let(:user) { create(:user) } let(:app_name) { 'my-app' } diff --git a/spec/services/packages/maven/find_or_create_package_service_spec.rb b/spec/services/packages/maven/find_or_create_package_service_spec.rb index cca074e2fa6..8b84d2541eb 100644 --- a/spec/services/packages/maven/find_or_create_package_service_spec.rb +++ b/spec/services/packages/maven/find_or_create_package_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Maven::FindOrCreatePackageService do +RSpec.describe Packages::Maven::FindOrCreatePackageService, feature_category: :package_registry do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } @@ -44,6 +44,15 @@ RSpec.describe Packages::Maven::FindOrCreatePackageService do end end + shared_examples 'returning an error' do |with_message: ''| + it { expect { subject }.not_to change { project.package_files.count } } + + it 'returns an error', :aggregate_failures do + expect(subject.payload).to be_empty + expect(subject.errors).to include(with_message) + end + end + context 'path with version' do # Note that "path with version" and "file type maven metadata xml" only exists for snapshot versions # In other words, we will never have an metadata xml upload on a path with version for a non snapshot version @@ -128,11 +137,19 @@ RSpec.describe Packages::Maven::FindOrCreatePackageService do let!(:existing_package) { create(:maven_package, name: path, version: version, project: project) } - it { expect { subject }.not_to change { project.package_files.count } } + let(:existing_file_name) { file_name } + let(:jar_file) { existing_package.package_files.with_file_name_like('%.jar').first } - it 'returns an error', :aggregate_failures do - expect(subject.payload).to be_empty - expect(subject.errors).to include('Duplicate package is not allowed') + before do + jar_file.update_column(:file_name, existing_file_name) + end + + it_behaves_like 'returning an error', with_message: 'Duplicate package is not allowed' + + context 'for a SNAPSHOT version' do + let(:version) { '1.0.0-SNAPSHOT' } + + it_behaves_like 'returning an error', with_message: 'Duplicate package is not allowed' end context 'when uploading to the versionless package which contains metadata about all versions' do @@ -144,8 +161,7 @@ RSpec.describe Packages::Maven::FindOrCreatePackageService do context 'when uploading different non-duplicate files to the same package' do before do - package_file = existing_package.package_files.find_by(file_name: 'my-app-1.0-20180724.124855-1.jar') - package_file.destroy! + jar_file.destroy! end it_behaves_like 'reuse existing package' @@ -166,6 +182,27 @@ RSpec.describe Packages::Maven::FindOrCreatePackageService do it_behaves_like 'reuse existing package' end + + context 'when uploading a similar package file name with a classifier' do + let(:existing_file_name) { 'test.jar' } + let(:file_name) { 'test-javadoc.jar' } + + it_behaves_like 'reuse existing package' + + context 'for a SNAPSHOT version' do + let(:version) { '1.0.0-SNAPSHOT' } + let(:existing_file_name) { 'test-1.0-20230303.163304-1.jar' } + let(:file_name) { 'test-1.0-20230303.163304-1-javadoc.jar' } + + it_behaves_like 'reuse existing package' + end + end + end + + context 'with a very large file name' do + let(:params) { super().merge(file_name: 'a' * (described_class::MAX_FILE_NAME_LENGTH + 1)) } + + it_behaves_like 'returning an error', with_message: 'File name is too long' end end end diff --git a/spec/services/packages/maven/metadata/append_package_file_service_spec.rb b/spec/services/packages/maven/metadata/append_package_file_service_spec.rb index f3a90d31158..f65029f7b64 100644 --- a/spec/services/packages/maven/metadata/append_package_file_service_spec.rb +++ b/spec/services/packages/maven/metadata/append_package_file_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Packages::Maven::Metadata::AppendPackageFileService do +RSpec.describe ::Packages::Maven::Metadata::AppendPackageFileService, feature_category: :package_registry do let_it_be(:package) { create(:maven_package, version: nil) } let(:service) { described_class.new(package: package, metadata_content: content) } diff --git a/spec/services/packages/maven/metadata/create_plugins_xml_service_spec.rb b/spec/services/packages/maven/metadata/create_plugins_xml_service_spec.rb index 6fc1087940d..d0ef037b2d9 100644 --- a/spec/services/packages/maven/metadata/create_plugins_xml_service_spec.rb +++ b/spec/services/packages/maven/metadata/create_plugins_xml_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Packages::Maven::Metadata::CreatePluginsXmlService do +RSpec.describe ::Packages::Maven::Metadata::CreatePluginsXmlService, feature_category: :package_registry do let_it_be(:group_id) { 'my/test' } let_it_be(:package) { create(:maven_package, name: group_id, version: nil) } diff --git a/spec/services/packages/maven/metadata/create_versions_xml_service_spec.rb b/spec/services/packages/maven/metadata/create_versions_xml_service_spec.rb index 70c2bbad87a..6ae84b5df4e 100644 --- a/spec/services/packages/maven/metadata/create_versions_xml_service_spec.rb +++ b/spec/services/packages/maven/metadata/create_versions_xml_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Packages::Maven::Metadata::CreateVersionsXmlService do +RSpec.describe ::Packages::Maven::Metadata::CreateVersionsXmlService, feature_category: :package_registry do let_it_be(:package) { create(:maven_package, version: nil) } let(:versions_in_database) { %w[1.3 2.0-SNAPSHOT 1.6 1.4 1.5-SNAPSHOT] } diff --git a/spec/services/packages/maven/metadata/sync_service_spec.rb b/spec/services/packages/maven/metadata/sync_service_spec.rb index 9a704d749b3..eaed54d959b 100644 --- a/spec/services/packages/maven/metadata/sync_service_spec.rb +++ b/spec/services/packages/maven/metadata/sync_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Packages::Maven::Metadata::SyncService do +RSpec.describe ::Packages::Maven::Metadata::SyncService, feature_category: :package_registry do using RSpec::Parameterized::TableSyntax let_it_be(:project) { create(:project) } diff --git a/spec/services/packages/npm/create_metadata_cache_service_spec.rb b/spec/services/packages/npm/create_metadata_cache_service_spec.rb new file mode 100644 index 00000000000..75f822f0ddb --- /dev/null +++ b/spec/services/packages/npm/create_metadata_cache_service_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Npm::CreateMetadataCacheService, :clean_gitlab_redis_shared_state, feature_category: :package_registry do + include ExclusiveLeaseHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:package_name) { "@#{project.root_namespace.path}/npm-test" } + let_it_be(:package) { create(:npm_package, version: '1.0.0', project: project, name: package_name) } + + let(:packages) { project.packages } + let(:lease_key) { "packages:npm:create_metadata_cache_service:metadata_caches:#{project.id}_#{package_name}" } + let(:service) { described_class.new(project, package_name, packages) } + + describe '#execute' do + let(:npm_metadata_cache) { Packages::Npm::MetadataCache.last } + + subject { service.execute } + + it 'creates a new metadata cache', :aggregate_failures do + expect { subject }.to change { Packages::Npm::MetadataCache.count }.by(1) + + metadata = Gitlab::Json.parse(npm_metadata_cache.file.read) + + expect(npm_metadata_cache.package_name).to eq(package_name) + expect(npm_metadata_cache.project_id).to eq(project.id) + expect(npm_metadata_cache.size).to eq(metadata.to_json.bytesize) + expect(metadata['name']).to eq(package_name) + expect(metadata['versions'].keys).to contain_exactly('1.0.0') + end + + context 'with existing metadata cache' do + let_it_be(:npm_metadata_cache) { create(:npm_metadata_cache, package_name: package_name, project_id: project.id) } + let_it_be(:metadata) { Gitlab::Json.parse(npm_metadata_cache.file.read) } + let_it_be(:metadata_size) { npm_metadata_cache.size } + let_it_be(:tag_name) { 'new-tag' } + let_it_be(:tag) { create(:packages_tag, package: package, name: tag_name) } + + it 'does not create a new metadata cache' do + expect { subject }.to change { Packages::Npm::MetadataCache.count }.by(0) + end + + it 'updates the metadata cache', :aggregate_failures do + subject + + 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(npm_metadata_cache.reload.size).not_to eq(metadata_size) + end + end + + it 'obtains a lease to create a new metadata cache' do + expect_to_obtain_exclusive_lease(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT) + + subject + end + + context 'when the lease is already taken' do + before do + stub_exclusive_lease_taken(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT) + end + + it 'does not create a new metadata cache' do + expect { subject }.to change { Packages::Npm::MetadataCache.count }.by(0) + end + + it 'returns nil' do + expect(subject).to be_nil + end + end + end + + describe '#lease_key' do + subject { service.send(:lease_key) } + + it 'returns an unique key' do + is_expected.to eq lease_key + end + 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 ef8cdf2e8ab..a12d86412d8 100644 --- a/spec/services/packages/npm/create_package_service_spec.rb +++ b/spec/services/packages/npm/create_package_service_spec.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Npm::CreatePackageService do +RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_registry do + include ExclusiveLeaseHelpers + let(:namespace) { create(:namespace) } let(:project) { create(:project, namespace: namespace) } let(:user) { create(:user) } @@ -14,9 +16,11 @@ RSpec.describe Packages::Npm::CreatePackageService do end let(:package_name) { "@#{namespace.path}/my-app" } - let(:version_data) { params.dig('versions', '1.0.1') } + let(:version_data) { params.dig('versions', version) } + let(:lease_key) { "packages:npm:create_package_service:packages:#{project.id}_#{package_name}_#{version}" } + let(:service) { described_class.new(project, user, params) } - subject { described_class.new(project, user, params).execute } + subject { service.execute } shared_examples 'valid package' do it 'creates a package' do @@ -57,17 +61,90 @@ RSpec.describe Packages::Npm::CreatePackageService do end end - context 'with a too large metadata structure' do - before do - params[:versions][version][:test] = 'test' * 10000 + context 'when the npm metadatum creation results in a size error' do + shared_examples 'a package json structure size too large error' do + it 'does not create the package' do + expect(Gitlab::ErrorTracking).to receive(:track_exception).with( + instance_of(ActiveRecord::RecordInvalid), + field_sizes: expected_field_sizes + ) + + expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Package json structure is too large') + .and not_change { Packages::Package.count } + .and not_change { Packages::Package.npm.count } + .and not_change { Packages::Tag.count } + .and not_change { Packages::Npm::Metadatum.count } + end + end + + context 'when some of the field sizes are above the error tracking size' do + let(:package_json) do + params[:versions][version].except(*::Packages::Npm::CreatePackageService::PACKAGE_JSON_NOT_ALLOWED_FIELDS) + end + + # Only the fields that exceed the field size limit should be passed to error tracking + let(:expected_field_sizes) do + { + 'test' => ('test' * 10000).size, + 'field2' => ('a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING + 1)).size + } + end + + before do + params[:versions][version][:test] = 'test' * 10000 + params[:versions][version][:field1] = + 'a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING - 1) + params[:versions][version][:field2] = + 'a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING + 1) + end + + it_behaves_like 'a package json structure size too large error' + end + + context 'when all of the field sizes are below the error tracking size' do + let(:package_json) do + params[:versions][version].except(*::Packages::Npm::CreatePackageService::PACKAGE_JSON_NOT_ALLOWED_FIELDS) + end + + let(:expected_size) { ('a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING - 1)).size } + # Only the five largest fields should be passed to error tracking + let(:expected_field_sizes) do + { + 'field1' => expected_size, + 'field2' => expected_size, + 'field3' => expected_size, + 'field4' => expected_size, + 'field5' => expected_size + } + end + + before do + 5.times do |i| + params[:versions][version]["field#{i + 1}"] = + 'a' * (::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING - 1) + end + end + + it_behaves_like 'a package json structure size too large error' end + end + + context 'when the npm metadatum creation results in a different error' do + it 'does not track the error' do + error_message = 'boom' + invalid_npm_metadatum_error = ActiveRecord::RecordInvalid.new( + build(:npm_metadatum).tap do |metadatum| + metadatum.errors.add(:base, error_message) + end + ) + + allow_next_instance_of(::Packages::Package) do |package| + allow(package).to receive(:create_npm_metadatum!).and_raise(invalid_npm_metadatum_error) + end - it 'does not create the package' do - expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Package json structure is too large') - .and not_change { Packages::Package.count } - .and not_change { Packages::Package.npm.count } - .and not_change { Packages::Tag.count } - .and not_change { Packages::Npm::Metadatum.count } + expect(Gitlab::ErrorTracking).not_to receive(:track_exception) + + expect { subject }.to raise_error(ActiveRecord::RecordInvalid, /#{error_message}/) end end @@ -216,5 +293,82 @@ RSpec.describe Packages::Npm::CreatePackageService do it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid') } end end + + context 'with empty attachment data' do + let(:params) { super().merge({ _attachments: { "#{package_name}-#{version}.tgz" => { data: '' } } }) } + + it { expect(subject[:http_status]).to eq 400 } + it { expect(subject[:message]).to eq 'Attachment data is empty.' } + end + + it 'obtains a lease to create a new package' do + expect_to_obtain_exclusive_lease(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT) + + subject + end + + context 'when the lease is already taken' do + before do + stub_exclusive_lease_taken(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT) + end + + it { expect(subject[:http_status]).to eq 400 } + it { expect(subject[:message]).to eq 'Could not obtain package lease.' } + end + + context 'when many of the same packages are created at the same time', :delete do + it 'only creates one package' do + expect { create_packages(project, user, params) }.to change { Packages::Package.count }.by(1) + end + end + + context 'when many packages with different versions are created at the same time', :delete do + it 'creates all packages' do + expect { create_packages_with_versions(project, user, params) }.to change { Packages::Package.count }.by(5) + end + end + + def create_packages(project, user, params) + with_threads do + described_class.new(project, user, params).execute + end + end + + def create_packages_with_versions(project, user, params) + with_threads do |i| + # Modify the package's version + modified_params = Gitlab::Json.parse(params.to_json + .gsub(version, "1.0.#{i}")).with_indifferent_access + + described_class.new(project, user, modified_params).execute + end + end + + def with_threads(count: 5, &block) + return unless block + + # create a race condition - structure from https://blog.arkency.com/2015/09/testing-race-conditions/ + wait_for_it = true + + threads = Array.new(count) do |i| + Thread.new do + # A loop to make threads busy until we `join` them + true while wait_for_it + + yield(i) + end + end + + wait_for_it = false + threads.each(&:join) + end + end + + describe '#lease_key' do + subject { service.send(:lease_key) } + + it 'returns an unique key' do + is_expected.to eq lease_key + end end end diff --git a/spec/services/packages/npm/create_tag_service_spec.rb b/spec/services/packages/npm/create_tag_service_spec.rb index a4b07bf97cc..682effc9f4f 100644 --- a/spec/services/packages/npm/create_tag_service_spec.rb +++ b/spec/services/packages/npm/create_tag_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Npm::CreateTagService do +RSpec.describe Packages::Npm::CreateTagService, feature_category: :package_registry do let(:package) { create(:npm_package) } let(:tag_name) { 'test-tag' } diff --git a/spec/services/packages/npm/deprecate_package_service_spec.rb b/spec/services/packages/npm/deprecate_package_service_spec.rb new file mode 100644 index 00000000000..a3686e3a8b5 --- /dev/null +++ b/spec/services/packages/npm/deprecate_package_service_spec.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Npm::DeprecatePackageService, feature_category: :package_registry do + let_it_be(:namespace) { create(:namespace) } + let_it_be(:project) { create(:project, namespace: namespace) } + + let_it_be(:package_name) { "@#{namespace.path}/my-app" } + let_it_be_with_reload(:package_1) do + create(:npm_package, project: project, name: package_name, version: '1.0.1').tap do |package| + create(:npm_metadatum, package: package) + end + end + + let_it_be(:package_2) do + create(:npm_package, project: project, name: package_name, version: '1.0.2').tap do |package| + create(:npm_metadatum, package: package) + end + end + + let(:service) { described_class.new(project, params) } + + subject(:execute) { service.execute } + + describe '#execute' do + context 'when passing deprecatation message' do + let(:params) do + { + 'package_name' => package_name, + 'versions' => { + '1.0.1' => { + 'name' => package_name, + 'deprecated' => 'This version is deprecated' + }, + '1.0.2' => { + 'name' => package_name, + 'deprecated' => 'This version is deprecated' + } + } + } + end + + before do + package_json = package_2.npm_metadatum.package_json + package_2.npm_metadatum.update!(package_json: package_json.merge('deprecated' => 'old deprecation message')) + end + + it 'adds or updates the deprecated field' do + expect { execute } + .to change { package_1.reload.npm_metadatum.package_json['deprecated'] }.to('This version is deprecated') + .and change { package_2.reload.npm_metadatum.package_json['deprecated'] } + .from('old deprecation message').to('This version is deprecated') + end + + it 'executes 5 queries' do + queries = ActiveRecord::QueryRecorder.new do + execute + end + + # 1. each_batch lower bound + # 2. each_batch upper bound + # 3. SELECT packages_packages.id, packages_packages.version FROM packages_packages + # 4. SELECT packages_npm_metadata.* FROM packages_npm_metadata + # 5. UPDATE packages_npm_metadata SET package_json = + expect(queries.count).to eq(5) + end + end + + context 'when passing deprecated as empty string' do + let(:params) do + { + 'package_name' => package_name, + 'versions' => { + '1.0.1' => { + 'name' => package_name, + 'deprecated' => '' + } + } + } + end + + before do + package_json = package_1.npm_metadatum.package_json + package_1.npm_metadatum.update!(package_json: package_json.merge('deprecated' => 'This version is deprecated')) + end + + it 'removes the deprecation warning' do + expect { execute } + .to change { package_1.reload.npm_metadatum.package_json['deprecated'] } + .from('This version is deprecated').to(nil) + end + end + + context 'when passing async: true to execute' do + let(:params) do + { + package_name: package_name, + versions: { + '1.0.1': { + deprecated: 'This version is deprecated' + } + } + } + end + + it 'calls the worker and return' do + expect(::Packages::Npm::DeprecatePackageWorker).to receive(:perform_async).with(project.id, params) + expect(service).not_to receive(:packages) + + service.execute(async: true) + end + end + end +end diff --git a/spec/services/packages/npm/generate_metadata_service_spec.rb b/spec/services/packages/npm/generate_metadata_service_spec.rb new file mode 100644 index 00000000000..1e3b0f71972 --- /dev/null +++ b/spec/services/packages/npm/generate_metadata_service_spec.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Packages::Npm::GenerateMetadataService, feature_category: :package_registry do + using RSpec::Parameterized::TableSyntax + + let_it_be(:project) { create(:project) } + let_it_be(:package_name) { "@#{project.root_namespace.path}/test" } + let_it_be(:package1) { create(:npm_package, version: '2.0.4', project: project, name: package_name) } + 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(:metadata) { described_class.new(package_name, packages).execute } + + describe '#versions' do + let_it_be(:version_schema) { 'public_api/v4/packages/npm_package_version' } + let_it_be(:package_json) do + { + name: package_name, + version: '2.0.4', + deprecated: 'warning!', + bin: './cli.js', + directories: ['lib'], + engines: { npm: '^7.5.6' }, + _hasShrinkwrap: false, + dist: { + tarball: 'http://localhost/tarball.tgz', + shasum: '1234567890' + }, + custom_field: 'foo_bar' + } + end + + subject { metadata[:versions] } + + where(:has_dependencies, :has_metadatum) do + true | true + false | true + true | false + false | false + end + + with_them do + if params[:has_dependencies] + ::Packages::DependencyLink.dependency_types.each_key do |dependency_type| # rubocop:disable RSpec/UselessDynamicDefinition + let_it_be("package_dependency_link_for_#{dependency_type}") do + create(:packages_dependency_link, package: package1, dependency_type: dependency_type) + end + end + end + + if params[:has_metadatum] + let_it_be(:package_metadatadum) { create(:npm_metadatum, package: package1, package_json: package_json) } + end + + it { is_expected.to be_a(Hash) } + it { expect(subject[package1.version].with_indifferent_access).to match_schema(version_schema) } + it { expect(subject[package2.version].with_indifferent_access).to match_schema(version_schema) } + it { expect(subject[package1.version]['custom_field']).to be_blank } + + context 'for dependencies' do + ::Packages::DependencyLink.dependency_types.each_key do |dependency_type| + if params[:has_dependencies] + it { expect(subject.dig(package1.version, dependency_type.to_s)).to be_any } + else + it { expect(subject.dig(package1.version, dependency_type)).to be nil } + end + + it { expect(subject.dig(package2.version, dependency_type)).to be nil } + end + end + + context 'for metadatum' do + ::Packages::Npm::GenerateMetadataService::PACKAGE_JSON_ALLOWED_FIELDS.each do |metadata_field| + if params[:has_metadatum] + it { expect(subject.dig(package1.version, metadata_field)).not_to be nil } + else + it { expect(subject.dig(package1.version, metadata_field)).to be nil } + end + + it { expect(subject.dig(package2.version, metadata_field)).to be nil } + end + end + + it 'avoids N+1 database queries' do + check_n_plus_one do + create_list(:npm_package, 5, project: project, name: package_name).each do |npm_package| + next unless has_dependencies + + ::Packages::DependencyLink.dependency_types.each_key do |dependency_type| + create(:packages_dependency_link, package: npm_package, dependency_type: dependency_type) + end + end + end + end + end + + context 'with package files pending destruction' do + let_it_be(:package_file_pending_destruction) do + create(:package_file, :pending_destruction, package: package2, file_sha1: 'pending_destruction_sha1') + end + + let(:shasums) { subject.values.map { |v| v.dig(:dist, :shasum) } } + + it 'does not return them' do + expect(shasums).not_to include(package_file_pending_destruction.file_sha1) + end + end + end + + describe '#dist_tags' do + subject { metadata[:dist_tags] } + + context 'for packages without tags' do + it { is_expected.to be_a(Hash) } + it { expect(subject['latest']).to eq(latest_package.version) } + + it 'avoids N+1 database queries' do + check_n_plus_one(only_dist_tags: true) do + create_list(:npm_package, 5, project: project, name: package_name) + end + end + end + + context 'for packages with tags' do + let_it_be(:package_tag1) { create(:packages_tag, package: package1, name: 'release_a') } + let_it_be(:package_tag2) { create(:packages_tag, package: package1, name: 'test_release') } + let_it_be(:package_tag3) { create(:packages_tag, package: package2, name: 'release_b') } + let_it_be(:package_tag4) { create(:packages_tag, package: latest_package, name: 'release_c') } + let_it_be(:package_tag5) { create(:packages_tag, package: latest_package, name: 'latest') } + + it { is_expected.to be_a(Hash) } + it { expect(subject[package_tag1.name]).to eq(package1.version) } + it { expect(subject[package_tag2.name]).to eq(package1.version) } + it { expect(subject[package_tag3.name]).to eq(package2.version) } + it { expect(subject[package_tag4.name]).to eq(latest_package.version) } + it { expect(subject[package_tag5.name]).to eq(latest_package.version) } + + it 'avoids N+1 database queries' do + check_n_plus_one(only_dist_tags: true) do + create_list(:npm_package, 5, project: project, name: package_name).each_with_index do |npm_package, index| + create(:packages_tag, package: npm_package, name: "tag_#{index}") + end + end + end + end + end + + context 'when passing only_dist_tags: true' do + subject { described_class.new(package_name, packages).execute(only_dist_tags: true) } + + it 'returns only dist tags' do + expect(subject.payload.keys).to contain_exactly(:dist_tags) + end + end + + def check_n_plus_one(only_dist_tags: false) + pkgs = project.packages.npm.with_name(package_name).last_of_each_version.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 + + expect do + described_class.new(package_name, pkgs).execute(only_dist_tags: only_dist_tags) + end.not_to exceed_query_limit(control) + end +end diff --git a/spec/services/packages/nuget/create_dependency_service_spec.rb b/spec/services/packages/nuget/create_dependency_service_spec.rb index 268c8837e25..10daec8b871 100644 --- a/spec/services/packages/nuget/create_dependency_service_spec.rb +++ b/spec/services/packages/nuget/create_dependency_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Nuget::CreateDependencyService do +RSpec.describe Packages::Nuget::CreateDependencyService, feature_category: :package_registry do let_it_be(:package, reload: true) { create(:nuget_package) } describe '#execute' do diff --git a/spec/services/packages/nuget/metadata_extraction_service_spec.rb b/spec/services/packages/nuget/metadata_extraction_service_spec.rb index 12bab30b4a7..9177a5379d9 100644 --- a/spec/services/packages/nuget/metadata_extraction_service_spec.rb +++ b/spec/services/packages/nuget/metadata_extraction_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::Nuget::MetadataExtractionService do +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) } diff --git a/spec/services/packages/nuget/search_service_spec.rb b/spec/services/packages/nuget/search_service_spec.rb index 66c91487a8f..b5f32c9b727 100644 --- a/spec/services/packages/nuget/search_service_spec.rb +++ b/spec/services/packages/nuget/search_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::Nuget::SearchService do +RSpec.describe Packages::Nuget::SearchService, feature_category: :package_registry do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:subgroup) { create(:group, parent: group) } diff --git a/spec/services/packages/nuget/sync_metadatum_service_spec.rb b/spec/services/packages/nuget/sync_metadatum_service_spec.rb index 32093c48b76..ae07f312fcc 100644 --- a/spec/services/packages/nuget/sync_metadatum_service_spec.rb +++ b/spec/services/packages/nuget/sync_metadatum_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::Nuget::SyncMetadatumService do +RSpec.describe Packages::Nuget::SyncMetadatumService, feature_category: :package_registry do let_it_be(:package, reload: true) { create(:nuget_package) } let_it_be(:metadata) do { 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 6a4dbeb10dc..c35863030b0 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 @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state do +RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state, feature_category: :package_registry do include ExclusiveLeaseHelpers let!(:package) { create(:nuget_package, :processing, :with_symbol_package) } @@ -259,11 +259,13 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_ ] invalid_names.each do |invalid_name| - before do - allow(service).to receive(:package_name).and_return(invalid_name) - end + context "with #{invalid_name}" do + before do + allow(service).to receive(:package_name).and_return(invalid_name) + end - it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError + it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError + end end end @@ -271,18 +273,19 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_ invalid_versions = [ '', '555', - '1.2', '1./2.3', '../../../../../1.2.3', '%2e%2e%2f1.2.3' ] invalid_versions.each do |invalid_version| - before do - allow(service).to receive(:package_version).and_return(invalid_version) - end + context "with #{invalid_version}" do + before do + allow(service).to receive(:package_version).and_return(invalid_version) + end - it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError + it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError + end end end end diff --git a/spec/services/packages/pypi/create_package_service_spec.rb b/spec/services/packages/pypi/create_package_service_spec.rb index 6794ab4d9d6..0d278e32e89 100644 --- a/spec/services/packages/pypi/create_package_service_spec.rb +++ b/spec/services/packages/pypi/create_package_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Pypi::CreatePackageService, :aggregate_failures do +RSpec.describe Packages::Pypi::CreatePackageService, :aggregate_failures, feature_category: :package_registry do include PackagesManagerApiSpecHelpers let_it_be(:project) { create(:project) } diff --git a/spec/services/packages/remove_tag_service_spec.rb b/spec/services/packages/remove_tag_service_spec.rb index 084635824e5..4ad478d487a 100644 --- a/spec/services/packages/remove_tag_service_spec.rb +++ b/spec/services/packages/remove_tag_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::RemoveTagService do +RSpec.describe Packages::RemoveTagService, feature_category: :package_registry do let!(:package_tag) { create(:packages_tag) } describe '#execute' do diff --git a/spec/services/packages/rpm/parse_package_service_spec.rb b/spec/services/packages/rpm/parse_package_service_spec.rb index f330587bfa0..80907d8f43f 100644 --- a/spec/services/packages/rpm/parse_package_service_spec.rb +++ b/spec/services/packages/rpm/parse_package_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rpm::ParsePackageService do +RSpec.describe Packages::Rpm::ParsePackageService, feature_category: :package_registry do let(:package_file) { File.open('spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm') } describe 'dynamic private methods' do diff --git a/spec/services/packages/rpm/repository_metadata/build_filelist_xml_service_spec.rb b/spec/services/packages/rpm/repository_metadata/build_filelist_xml_service_spec.rb index d93d6ab9fcb..e0d9e192d97 100644 --- a/spec/services/packages/rpm/repository_metadata/build_filelist_xml_service_spec.rb +++ b/spec/services/packages/rpm/repository_metadata/build_filelist_xml_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rpm::RepositoryMetadata::BuildFilelistXmlService do +RSpec.describe Packages::Rpm::RepositoryMetadata::BuildFilelistXmlService, feature_category: :package_registry do describe '#execute' do subject { described_class.new(data).execute } diff --git a/spec/services/packages/rpm/repository_metadata/build_other_xml_service_spec.rb b/spec/services/packages/rpm/repository_metadata/build_other_xml_service_spec.rb index 201f9e67ce9..e81a436e006 100644 --- a/spec/services/packages/rpm/repository_metadata/build_other_xml_service_spec.rb +++ b/spec/services/packages/rpm/repository_metadata/build_other_xml_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rpm::RepositoryMetadata::BuildOtherXmlService do +RSpec.describe Packages::Rpm::RepositoryMetadata::BuildOtherXmlService, feature_category: :package_registry do describe '#execute' do subject { described_class.new(data).execute } diff --git a/spec/services/packages/rpm/repository_metadata/build_primary_xml_service_spec.rb b/spec/services/packages/rpm/repository_metadata/build_primary_xml_service_spec.rb index 9bbfa5c9863..1e534782841 100644 --- a/spec/services/packages/rpm/repository_metadata/build_primary_xml_service_spec.rb +++ b/spec/services/packages/rpm/repository_metadata/build_primary_xml_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rpm::RepositoryMetadata::BuildPrimaryXmlService do +RSpec.describe Packages::Rpm::RepositoryMetadata::BuildPrimaryXmlService, feature_category: :package_registry do describe '#execute' do subject { described_class.new(data).execute } diff --git a/spec/services/packages/rpm/repository_metadata/build_repomd_xml_service_spec.rb b/spec/services/packages/rpm/repository_metadata/build_repomd_xml_service_spec.rb index cf28301fa2c..99fcf0fabbf 100644 --- a/spec/services/packages/rpm/repository_metadata/build_repomd_xml_service_spec.rb +++ b/spec/services/packages/rpm/repository_metadata/build_repomd_xml_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rpm::RepositoryMetadata::BuildRepomdXmlService do +RSpec.describe Packages::Rpm::RepositoryMetadata::BuildRepomdXmlService, feature_category: :package_registry do describe '#execute' do subject { described_class.new(data).execute } diff --git a/spec/services/packages/rpm/repository_metadata/update_xml_service_spec.rb b/spec/services/packages/rpm/repository_metadata/update_xml_service_spec.rb index e351392ba1c..68a3ac7d82f 100644 --- a/spec/services/packages/rpm/repository_metadata/update_xml_service_spec.rb +++ b/spec/services/packages/rpm/repository_metadata/update_xml_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rpm::RepositoryMetadata::UpdateXmlService do +RSpec.describe Packages::Rpm::RepositoryMetadata::UpdateXmlService, feature_category: :package_registry do describe '#execute' do subject { described_class.new(filename: filename, xml: xml, data: data).execute } diff --git a/spec/services/packages/rubygems/create_dependencies_service_spec.rb b/spec/services/packages/rubygems/create_dependencies_service_spec.rb index b6e12b1cc61..d689bae96ff 100644 --- a/spec/services/packages/rubygems/create_dependencies_service_spec.rb +++ b/spec/services/packages/rubygems/create_dependencies_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rubygems::CreateDependenciesService do +RSpec.describe Packages::Rubygems::CreateDependenciesService, feature_category: :package_registry do include RubygemsHelpers let_it_be(:package) { create(:rubygems_package) } diff --git a/spec/services/packages/rubygems/create_gemspec_service_spec.rb b/spec/services/packages/rubygems/create_gemspec_service_spec.rb index 839fb4d955a..17890100b93 100644 --- a/spec/services/packages/rubygems/create_gemspec_service_spec.rb +++ b/spec/services/packages/rubygems/create_gemspec_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rubygems::CreateGemspecService do +RSpec.describe Packages::Rubygems::CreateGemspecService, feature_category: :package_registry do include RubygemsHelpers let_it_be(:package_file) { create(:package_file, :gem) } diff --git a/spec/services/packages/rubygems/dependency_resolver_service_spec.rb b/spec/services/packages/rubygems/dependency_resolver_service_spec.rb index bb84e0cd361..9a72c51e99c 100644 --- a/spec/services/packages/rubygems/dependency_resolver_service_spec.rb +++ b/spec/services/packages/rubygems/dependency_resolver_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::Rubygems::DependencyResolverService do +RSpec.describe Packages::Rubygems::DependencyResolverService, feature_category: :package_registry do let_it_be(:project) { create(:project, :private) } let_it_be(:package) { create(:package, project: project) } let_it_be(:user) { create(:user) } diff --git a/spec/services/packages/rubygems/metadata_extraction_service_spec.rb b/spec/services/packages/rubygems/metadata_extraction_service_spec.rb index bbd5b6f3d59..87d63eff311 100644 --- a/spec/services/packages/rubygems/metadata_extraction_service_spec.rb +++ b/spec/services/packages/rubygems/metadata_extraction_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' require 'rubygems/package' -RSpec.describe Packages::Rubygems::MetadataExtractionService do +RSpec.describe Packages::Rubygems::MetadataExtractionService, feature_category: :package_registry do include RubygemsHelpers let_it_be(:package) { create(:rubygems_package) } diff --git a/spec/services/packages/rubygems/process_gem_service_spec.rb b/spec/services/packages/rubygems/process_gem_service_spec.rb index caff338ef53..a1b4eae9655 100644 --- a/spec/services/packages/rubygems/process_gem_service_spec.rb +++ b/spec/services/packages/rubygems/process_gem_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Packages::Rubygems::ProcessGemService do +RSpec.describe Packages::Rubygems::ProcessGemService, feature_category: :package_registry do include ExclusiveLeaseHelpers include RubygemsHelpers diff --git a/spec/services/packages/terraform_module/create_package_service_spec.rb b/spec/services/packages/terraform_module/create_package_service_spec.rb index f73b5682835..3355dfcf5ec 100644 --- a/spec/services/packages/terraform_module/create_package_service_spec.rb +++ b/spec/services/packages/terraform_module/create_package_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::TerraformModule::CreatePackageService do +RSpec.describe Packages::TerraformModule::CreatePackageService, feature_category: :package_registry do let_it_be(:namespace) { create(:namespace) } let_it_be(:project) { create(:project, namespace: namespace) } let_it_be(:user) { create(:user) } diff --git a/spec/services/packages/update_package_file_service_spec.rb b/spec/services/packages/update_package_file_service_spec.rb index d988049c43a..5d081059105 100644 --- a/spec/services/packages/update_package_file_service_spec.rb +++ b/spec/services/packages/update_package_file_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::UpdatePackageFileService do +RSpec.describe Packages::UpdatePackageFileService, feature_category: :package_registry do let_it_be(:another_package) { create(:package) } let_it_be(:old_file_name) { 'old_file_name.txt' } let_it_be(:new_file_name) { 'new_file_name.txt' } diff --git a/spec/services/packages/update_tags_service_spec.rb b/spec/services/packages/update_tags_service_spec.rb index c4256699c94..d8f572fff32 100644 --- a/spec/services/packages/update_tags_service_spec.rb +++ b/spec/services/packages/update_tags_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Packages::UpdateTagsService do +RSpec.describe Packages::UpdateTagsService, feature_category: :package_registry do let_it_be(:package, reload: true) { create(:nuget_package) } let(:tags) { %w(test-tag tag1 tag2 tag3) } diff --git a/spec/services/pages/delete_service_spec.rb b/spec/services/pages/delete_service_spec.rb index 8b9e72ac9b1..590378af22b 100644 --- a/spec/services/pages/delete_service_spec.rb +++ b/spec/services/pages/delete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Pages::DeleteService do +RSpec.describe Pages::DeleteService, feature_category: :pages do let_it_be(:admin) { create(:admin) } let(:project) { create(:project, path: "my.project") } diff --git a/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb b/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb index 177467aac85..b18f62c1c28 100644 --- a/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb +++ b/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Pages::MigrateLegacyStorageToDeploymentService do +RSpec.describe Pages::MigrateLegacyStorageToDeploymentService, feature_category: :pages do let(:project) { create(:project, :repository) } let(:service) { described_class.new(project) } diff --git a/spec/services/pages/zip_directory_service_spec.rb b/spec/services/pages/zip_directory_service_spec.rb index 00fe75dbbfd..4917bc65a02 100644 --- a/spec/services/pages/zip_directory_service_spec.rb +++ b/spec/services/pages/zip_directory_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Pages::ZipDirectoryService do +RSpec.describe Pages::ZipDirectoryService, feature_category: :pages do around do |example| Dir.mktmpdir do |dir| @work_dir = dir diff --git a/spec/services/pages_domains/create_acme_order_service_spec.rb b/spec/services/pages_domains/create_acme_order_service_spec.rb index 35b2cc56973..97534d52c67 100644 --- a/spec/services/pages_domains/create_acme_order_service_spec.rb +++ b/spec/services/pages_domains/create_acme_order_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PagesDomains::CreateAcmeOrderService do +RSpec.describe PagesDomains::CreateAcmeOrderService, feature_category: :pages do include LetsEncryptHelpers let(:pages_domain) { create(:pages_domain) } diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb index ecb445fa441..2377fbcf003 100644 --- a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb +++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PagesDomains::ObtainLetsEncryptCertificateService do +RSpec.describe PagesDomains::ObtainLetsEncryptCertificateService, feature_category: :pages do include LetsEncryptHelpers let(:pages_domain) { create(:pages_domain, :without_certificate, :without_key) } diff --git a/spec/services/personal_access_tokens/create_service_spec.rb b/spec/services/personal_access_tokens/create_service_spec.rb index b8a4c8f30d2..d80be5cccce 100644 --- a/spec/services/personal_access_tokens/create_service_spec.rb +++ b/spec/services/personal_access_tokens/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PersonalAccessTokens::CreateService do +RSpec.describe PersonalAccessTokens::CreateService, feature_category: :system_access do shared_examples_for 'a successfully created token' do it 'creates personal access token record' do expect(subject.success?).to be true @@ -40,7 +40,7 @@ RSpec.describe PersonalAccessTokens::CreateService do let(:current_user) { create(:user) } let(:user) { create(:user) } let(:params) { { name: 'Test token', impersonation: false, scopes: [:api], expires_at: Date.today + 1.month } } - let(:service) { described_class.new(current_user: current_user, target_user: user, params: params) } + let(:service) { described_class.new(current_user: current_user, target_user: user, params: params, concatenate_errors: false) } let(:token) { subject.payload[:personal_access_token] } context 'when current_user is an administrator' do @@ -66,5 +66,21 @@ RSpec.describe PersonalAccessTokens::CreateService do it_behaves_like 'a successfully created token' end end + + context 'when invalid scope' do + let(:params) { { name: 'Test token', impersonation: false, scopes: [:no_valid], expires_at: Date.today + 1.month } } + + context 'when concatenate_errors: true' do + let(:service) { described_class.new(current_user: user, target_user: user, params: params) } + + it { expect(subject.message).to be_an_instance_of(String) } + end + + context 'when concatenate_errors: false' do + let(:service) { described_class.new(current_user: user, target_user: user, params: params, concatenate_errors: false) } + + it { expect(subject.message).to be_an_instance_of(Array) } + end + end end end 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 6fc74e27dd9..20eabc20338 100644 --- a/spec/services/personal_access_tokens/last_used_service_spec.rb +++ b/spec/services/personal_access_tokens/last_used_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PersonalAccessTokens::LastUsedService do +RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_access do describe '#execute' do subject { described_class.new(personal_access_token).execute } diff --git a/spec/services/personal_access_tokens/revoke_service_spec.rb b/spec/services/personal_access_tokens/revoke_service_spec.rb index a9b4df9749f..4c5d106660a 100644 --- a/spec/services/personal_access_tokens/revoke_service_spec.rb +++ b/spec/services/personal_access_tokens/revoke_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PersonalAccessTokens::RevokeService do +RSpec.describe PersonalAccessTokens::RevokeService, feature_category: :system_access do shared_examples_for 'a successfully revoked token' do it { expect(subject.success?).to be true } it { expect(service.token.revoked?).to be true } diff --git a/spec/services/personal_access_tokens/rotate_service_spec.rb b/spec/services/personal_access_tokens/rotate_service_spec.rb new file mode 100644 index 00000000000..e026b0b6485 --- /dev/null +++ b/spec/services/personal_access_tokens/rotate_service_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe PersonalAccessTokens::RotateService, feature_category: :system_access do + describe '#execute' do + let_it_be(:token, reload: true) { create(:personal_access_token) } + + subject(:response) { described_class.new(token.user, token).execute } + + it "rotates user's own token", :freeze_time do + expect(response).to be_success + + new_token = response.payload[:personal_access_token] + + expect(new_token.token).not_to eq(token.token) + expect(new_token.expires_at).to eq(Date.today + 1.week) + expect(new_token.user).to eq(token.user) + end + + it 'revokes the previous token' do + expect { response }.to change { token.reload.revoked? }.from(false).to(true) + + new_token = response.payload[:personal_access_token] + expect(new_token).not_to be_revoked + end + + context 'when user tries to rotate already revoked token' do + let_it_be(:token, reload: true) { create(:personal_access_token, :revoked) } + + it 'returns an error' do + expect { response }.not_to change { token.reload.revoked? }.from(true) + expect(response).to be_error + expect(response.message).to eq('token already revoked') + end + end + + context 'when revoking previous token fails' do + it 'returns an error' do + expect(token).to receive(:revoke!).and_return(false) + + expect(response).to be_error + end + end + + context 'when creating the new token fails' do + let(:error_message) { 'boom!' } + + before do + allow_next_instance_of(PersonalAccessToken) do |token| + allow(token).to receive_message_chain(:errors, :full_messages, :to_sentence).and_return(error_message) + allow(token).to receive_message_chain(:errors, :clear) + allow(token).to receive_message_chain(:errors, :empty?).and_return(false) + end + end + + it 'returns an error' do + expect(response).to be_error + expect(response.message).to eq(error_message) + end + + it 'reverts the changes' do + expect { response }.not_to change { token.reload.revoked? }.from(false) + end + end + end +end diff --git a/spec/services/post_receive_service_spec.rb b/spec/services/post_receive_service_spec.rb index aa955b3445b..13bd103003f 100644 --- a/spec/services/post_receive_service_spec.rb +++ b/spec/services/post_receive_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PostReceiveService do +RSpec.describe PostReceiveService, feature_category: :team_planning do include GitlabShellHelpers include Gitlab::Routing diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index d1bc10cfd28..6fa44310ae5 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PreviewMarkdownService do +RSpec.describe PreviewMarkdownService, feature_category: :team_planning do let(:user) { create(:user) } let(:project) { create(:project, :repository) } @@ -117,6 +117,16 @@ RSpec.describe PreviewMarkdownService do expect(result[:text]).to eq 'Please do it' end + context 'when render_quick_actions' do + it 'keeps quick actions' do + params[:render_quick_actions] = true + + result = service.execute + + expect(result[:text]).to eq "Please do it\n\n/assign #{user.to_reference}" + end + end + it 'explains quick actions effect' do result = service.execute diff --git a/spec/services/product_analytics/build_activity_graph_service_spec.rb b/spec/services/product_analytics/build_activity_graph_service_spec.rb index e303656da34..cd1bc42e156 100644 --- a/spec/services/product_analytics/build_activity_graph_service_spec.rb +++ b/spec/services/product_analytics/build_activity_graph_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProductAnalytics::BuildActivityGraphService do +RSpec.describe ProductAnalytics::BuildActivityGraphService, feature_category: :product_analytics do let_it_be(:project) { create(:project) } let_it_be(:time_now) { Time.zone.now } let_it_be(:time_ago) { Time.zone.now - 5.days } diff --git a/spec/services/product_analytics/build_graph_service_spec.rb b/spec/services/product_analytics/build_graph_service_spec.rb index 933a2bfee92..ee0e2190501 100644 --- a/spec/services/product_analytics/build_graph_service_spec.rb +++ b/spec/services/product_analytics/build_graph_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProductAnalytics::BuildGraphService do +RSpec.describe ProductAnalytics::BuildGraphService, feature_category: :product_analytics do let_it_be(:project) { create(:project) } let_it_be(:events) do diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb index 72bb0adbf56..3097d6d1498 100644 --- a/spec/services/projects/after_rename_service_spec.rb +++ b/spec/services/projects/after_rename_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::AfterRenameService do +RSpec.describe Projects::AfterRenameService, feature_category: :projects do let(:legacy_storage) { Storage::LegacyProject.new(project) } let(:hashed_storage) { Storage::Hashed.new(project) } let!(:path_before_rename) { project.path } diff --git a/spec/services/projects/alerting/notify_service_spec.rb b/spec/services/projects/alerting/notify_service_spec.rb index aa2ef39bf98..8cd9b5d3e00 100644 --- a/spec/services/projects/alerting/notify_service_spec.rb +++ b/spec/services/projects/alerting/notify_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Alerting::NotifyService do +RSpec.describe Projects::Alerting::NotifyService, feature_category: :projects do let_it_be_with_reload(:project) { create(:project) } let(:payload) { ActionController::Parameters.new(payload_raw).permit! } diff --git a/spec/services/projects/all_issues_count_service_spec.rb b/spec/services/projects/all_issues_count_service_spec.rb index d7e35991940..e8e08a25c45 100644 --- a/spec/services/projects/all_issues_count_service_spec.rb +++ b/spec/services/projects/all_issues_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::AllIssuesCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Projects::AllIssuesCountService, :use_clean_rails_memory_store_caching, feature_category: :projects do let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :public, namespace: group) } let_it_be(:banned_user) { create(:user, :banned) } diff --git a/spec/services/projects/all_merge_requests_count_service_spec.rb b/spec/services/projects/all_merge_requests_count_service_spec.rb index 13954d688aa..ca10fbc00ad 100644 --- a/spec/services/projects/all_merge_requests_count_service_spec.rb +++ b/spec/services/projects/all_merge_requests_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::AllMergeRequestsCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Projects::AllMergeRequestsCountService, :use_clean_rails_memory_store_caching, feature_category: :projects do let_it_be(:project) { create(:project) } subject { described_class.new(project) } @@ -11,18 +11,9 @@ RSpec.describe Projects::AllMergeRequestsCountService, :use_clean_rails_memory_s describe '#count' do it 'returns the number of all merge requests' do - create(:merge_request, - :opened, - source_project: project, - target_project: project) - create(:merge_request, - :closed, - source_project: project, - target_project: project) - create(:merge_request, - :merged, - source_project: project, - target_project: project) + create(:merge_request, :opened, source_project: project, target_project: project) + create(:merge_request, :closed, source_project: project, target_project: project) + create(:merge_request, :merged, source_project: project, target_project: project) expect(subject.count).to eq(3) end diff --git a/spec/services/projects/android_target_platform_detector_service_spec.rb b/spec/services/projects/android_target_platform_detector_service_spec.rb deleted file mode 100644 index 74fd320bb48..00000000000 --- a/spec/services/projects/android_target_platform_detector_service_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Projects::AndroidTargetPlatformDetectorService do - let_it_be(:project) { build(:project) } - - subject { described_class.new(project).execute } - - before do - allow(Gitlab::FileFinder).to receive(:new) { finder } - end - - context 'when project is not an Android project' do - let(:finder) { instance_double(Gitlab::FileFinder, find: []) } - - it { is_expected.to be_nil } - end - - context 'when project is an Android project' do - let(:finder) { instance_double(Gitlab::FileFinder) } - - before do - query = described_class::MANIFEST_FILE_SEARCH_QUERY - allow(finder).to receive(:find).with(query) { [instance_double(Gitlab::Search::FoundBlob)] } - end - - it { is_expected.to eq :android } - end -end diff --git a/spec/services/projects/apple_target_platform_detector_service_spec.rb b/spec/services/projects/apple_target_platform_detector_service_spec.rb index 6391161824c..787faaa0f79 100644 --- a/spec/services/projects/apple_target_platform_detector_service_spec.rb +++ b/spec/services/projects/apple_target_platform_detector_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::AppleTargetPlatformDetectorService do +RSpec.describe Projects::AppleTargetPlatformDetectorService, feature_category: :projects do let_it_be(:project) { build(:project) } subject { described_class.new(project).execute } diff --git a/spec/services/projects/auto_devops/disable_service_spec.rb b/spec/services/projects/auto_devops/disable_service_spec.rb index 1f161990fb2..fd70362a53f 100644 --- a/spec/services/projects/auto_devops/disable_service_spec.rb +++ b/spec/services/projects/auto_devops/disable_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Projects::AutoDevops::DisableService, '#execute' do +RSpec.describe Projects::AutoDevops::DisableService, '#execute', feature_category: :auto_devops do let(:project) { create(:project, :repository, :auto_devops) } let(:auto_devops) { project.auto_devops } diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb index bc95a1f3c8b..9d3075874a2 100644 --- a/spec/services/projects/autocomplete_service_spec.rb +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::AutocompleteService do +RSpec.describe Projects::AutocompleteService, feature_category: :projects do describe '#issues' do describe 'confidential issues' do let(:author) { create(:user) } diff --git a/spec/services/projects/batch_open_issues_count_service_spec.rb b/spec/services/projects/batch_open_issues_count_service_spec.rb index 89a4abbf9c9..d29115a697f 100644 --- a/spec/services/projects/batch_open_issues_count_service_spec.rb +++ b/spec/services/projects/batch_open_issues_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::BatchOpenIssuesCountService do +RSpec.describe Projects::BatchOpenIssuesCountService, feature_category: :projects do let!(:project_1) { create(:project) } let!(:project_2) { create(:project) } diff --git a/spec/services/projects/batch_open_merge_requests_count_service_spec.rb b/spec/services/projects/batch_open_merge_requests_count_service_spec.rb new file mode 100644 index 00000000000..96fc6c5e9dd --- /dev/null +++ b/spec/services/projects/batch_open_merge_requests_count_service_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::BatchOpenMergeRequestsCountService, feature_category: :code_review_workflow do + subject { described_class.new([project_1, project_2]) } + + let_it_be(:project_1) { create(:project) } + let_it_be(:project_2) { create(:project) } + + describe '#refresh_cache_and_retrieve_data', :use_clean_rails_memory_store_caching do + before do + create(:merge_request, source_project: project_1, target_project: project_1) + create(:merge_request, source_project: project_2, target_project: project_2) + end + + it 'refreshes cache keys correctly when cache is clean', :aggregate_failures do + subject.refresh_cache_and_retrieve_data + + expect(Rails.cache.read(get_cache_key(subject, project_1))).to eq(1) + expect(Rails.cache.read(get_cache_key(subject, project_2))).to eq(1) + + expect { subject.refresh_cache_and_retrieve_data }.not_to exceed_query_limit(0) + end + end + + def get_cache_key(subject, project) + subject.count_service + .new(project) + .cache_key + end +end diff --git a/spec/services/projects/blame_service_spec.rb b/spec/services/projects/blame_service_spec.rb deleted file mode 100644 index 52b0ed3412d..00000000000 --- a/spec/services/projects/blame_service_spec.rb +++ /dev/null @@ -1,131 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Projects::BlameService, :aggregate_failures do - subject(:service) { described_class.new(blob, commit, params) } - - let_it_be(:project) { create(:project, :repository) } - let_it_be(:commit) { project.repository.commit } - let_it_be(:blob) { project.repository.blob_at('HEAD', 'README.md') } - - let(:params) { { page: page } } - let(:page) { nil } - - before do - stub_const("#{described_class.name}::PER_PAGE", 2) - end - - describe '#blame' do - subject { service.blame } - - it 'returns a correct Gitlab::Blame object' do - is_expected.to be_kind_of(Gitlab::Blame) - - expect(subject.blob).to eq(blob) - expect(subject.commit).to eq(commit) - expect(subject.range).to eq(1..2) - end - - describe 'Pagination range calculation' do - subject { service.blame.range } - - context 'with page = 1' do - let(:page) { 1 } - - it { is_expected.to eq(1..2) } - end - - context 'with page = 2' do - let(:page) { 2 } - - it { is_expected.to eq(3..4) } - end - - context 'with page = 3 (overlimit)' do - let(:page) { 3 } - - it { is_expected.to eq(1..2) } - end - - context 'with page = 0 (incorrect)' do - let(:page) { 0 } - - it { is_expected.to eq(1..2) } - end - - context 'when user disabled the pagination' do - let(:params) { super().merge(no_pagination: 1) } - - it { is_expected.to be_nil } - end - - context 'when feature flag disabled' do - before do - stub_feature_flags(blame_page_pagination: false) - end - - it { is_expected.to be_nil } - end - end - end - - describe '#pagination' do - subject { service.pagination } - - it 'returns a pagination object' do - is_expected.to be_kind_of(Kaminari::PaginatableArray) - - expect(subject.current_page).to eq(1) - expect(subject.total_pages).to eq(2) - expect(subject.total_count).to eq(4) - end - - context 'when user disabled the pagination' do - let(:params) { super().merge(no_pagination: 1) } - - it { is_expected.to be_nil } - end - - context 'when feature flag disabled' do - before do - stub_feature_flags(blame_page_pagination: false) - end - - it { is_expected.to be_nil } - end - - context 'when per_page is above the global max per page limit' do - before do - stub_const("#{described_class.name}::PER_PAGE", 1000) - allow(blob).to receive_message_chain(:data, :lines, :count) { 500 } - end - - it 'returns a correct pagination object' do - is_expected.to be_kind_of(Kaminari::PaginatableArray) - - expect(subject.current_page).to eq(1) - expect(subject.total_pages).to eq(1) - expect(subject.total_count).to eq(500) - end - end - - describe 'Pagination attributes' do - using RSpec::Parameterized::TableSyntax - - where(:page, :current_page, :total_pages) do - 1 | 1 | 2 - 2 | 2 | 2 - 3 | 1 | 2 # Overlimit - 0 | 1 | 2 # Incorrect - end - - with_them do - it 'returns the correct pagination attributes' do - expect(subject.current_page).to eq(current_page) - expect(subject.total_pages).to eq(total_pages) - end - end - end - end -end diff --git a/spec/services/projects/branches_by_mode_service_spec.rb b/spec/services/projects/branches_by_mode_service_spec.rb index 9a63563b37b..bfe76b34310 100644 --- a/spec/services/projects/branches_by_mode_service_spec.rb +++ b/spec/services/projects/branches_by_mode_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::BranchesByModeService do +RSpec.describe Projects::BranchesByModeService, feature_category: :source_code_management do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :repository) } diff --git a/spec/services/projects/cleanup_service_spec.rb b/spec/services/projects/cleanup_service_spec.rb index f2c052d9397..533a09f7bc7 100644 --- a/spec/services/projects/cleanup_service_spec.rb +++ b/spec/services/projects/cleanup_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::CleanupService do +RSpec.describe Projects::CleanupService, feature_category: :source_code_management do subject(:service) { described_class.new(project) } describe '.enqueue' do diff --git a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb index 8311c4e4d9b..b8ad63d9b8a 100644 --- a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb +++ b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ContainerRepository::CleanupTagsService do +RSpec.describe Projects::ContainerRepository::CleanupTagsService, feature_category: :container_registry do let_it_be_with_reload(:container_repository) { create(:container_repository) } let_it_be(:user) { container_repository.project.owner } @@ -77,7 +77,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do context 'with a migrated repository' do before do - container_repository.update_column(:migration_state, :import_done) + allow(container_repository).to receive(:migrated?).and_return(true) end context 'supporting the gitlab api' do @@ -99,8 +99,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do context 'with a non migrated repository' do before do - container_repository.update_column(:migration_state, :default) - container_repository.update!(created_at: ContainerRepository::MIGRATION_PHASE_1_ENDED_AT - 1.week) + allow(container_repository).to receive(:migrated?).and_return(false) end it_behaves_like 'calling service', ::Projects::ContainerRepository::ThirdParty::CleanupTagsService, extra_log_data: { third_party_cleanup_tags_service: true } diff --git a/spec/services/projects/container_repository/delete_tags_service_spec.rb b/spec/services/projects/container_repository/delete_tags_service_spec.rb index 9e6849aa514..5b67d614dfb 100644 --- a/spec/services/projects/container_repository/delete_tags_service_spec.rb +++ b/spec/services/projects/container_repository/delete_tags_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ContainerRepository::DeleteTagsService do +RSpec.describe Projects::ContainerRepository::DeleteTagsService, feature_category: :container_registry do using RSpec::Parameterized::TableSyntax include_context 'container repository delete tags service shared context' diff --git a/spec/services/projects/container_repository/destroy_service_spec.rb b/spec/services/projects/container_repository/destroy_service_spec.rb index fed1d13daa5..a142360f99d 100644 --- a/spec/services/projects/container_repository/destroy_service_spec.rb +++ b/spec/services/projects/container_repository/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ContainerRepository::DestroyService do +RSpec.describe Projects::ContainerRepository::DestroyService, feature_category: :container_registry do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :private) } let_it_be(:params) { {} } diff --git a/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb index b06a5709bd5..f662d8bfc0c 100644 --- a/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb +++ b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do +RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService, feature_category: :container_registry do using RSpec::Parameterized::TableSyntax include_context 'for a cleanup tags service' @@ -11,11 +11,13 @@ RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, :private) } - let(:repository) { create(:container_repository, :root, :import_done, project: project) } + let(:repository) { create(:container_repository, :root, project: project) } let(:service) { described_class.new(container_repository: repository, current_user: user, params: params) } let(:tags) { %w[latest A Ba Bb C D E] } before do + allow(repository).to receive(:migrated?).and_return(true) + project.add_maintainer(user) if user stub_container_registry_config(enabled: true) @@ -47,23 +49,23 @@ RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do let(:tags_page_size) { 2 } it_behaves_like 'when regex matching everything is specified', - delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]] + delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]] it_behaves_like 'when regex matching everything is specified and latest is not kept', - delete_expectations: [%w[latest A], %w[Ba Bb], %w[C D], %w[E]] + delete_expectations: [%w[latest A], %w[Ba Bb], %w[C D], %w[E]] it_behaves_like 'when delete regex matching specific tags is used' it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex' it_behaves_like 'with allow regex value', - delete_expectations: [%w[A], %w[C D], %w[E]] + delete_expectations: [%w[A], %w[C D], %w[E]] it_behaves_like 'when keeping only N tags', - delete_expectations: [%w[Bb]] + delete_expectations: [%w[Bb]] it_behaves_like 'when not keeping N tags', - delete_expectations: [%w[A], %w[Ba Bb], %w[C]] + delete_expectations: [%w[A], %w[Ba Bb], %w[C]] context 'when removing keeping only 3' do let(:params) do @@ -77,13 +79,13 @@ RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do end it_behaves_like 'when removing older than 1 day', - delete_expectations: [%w[Ba Bb], %w[C]] + delete_expectations: [%w[Ba Bb], %w[C]] it_behaves_like 'when combining all parameters', - delete_expectations: [%w[Bb], %w[C]] + delete_expectations: [%w[Bb], %w[C]] it_behaves_like 'when running a container_expiration_policy', - delete_expectations: [%w[Bb], %w[C]] + delete_expectations: [%w[Bb], %w[C]] context 'with a timeout' do let(:params) do @@ -111,7 +113,7 @@ RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do end it_behaves_like 'when regex matching everything is specified', - delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]] + delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]] end end end @@ -120,32 +122,46 @@ RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do let(:tags_page_size) { 1000 } it_behaves_like 'when regex matching everything is specified', - delete_expectations: [%w[A Ba Bb C D E]] + delete_expectations: [%w[A Ba Bb C D E]] it_behaves_like 'when delete regex matching specific tags is used' it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex' it_behaves_like 'with allow regex value', - delete_expectations: [%w[A C D E]] + delete_expectations: [%w[A C D E]] it_behaves_like 'when keeping only N tags', - delete_expectations: [%w[Ba Bb C]] + delete_expectations: [%w[Ba Bb C]] it_behaves_like 'when not keeping N tags', - delete_expectations: [%w[A Ba Bb C]] + delete_expectations: [%w[A Ba Bb C]] it_behaves_like 'when removing keeping only 3', - delete_expectations: [%w[Ba Bb C]] + delete_expectations: [%w[Ba Bb C]] it_behaves_like 'when removing older than 1 day', - delete_expectations: [%w[Ba Bb C]] + delete_expectations: [%w[Ba Bb C]] it_behaves_like 'when combining all parameters', - delete_expectations: [%w[Ba Bb C]] + delete_expectations: [%w[Ba Bb C]] it_behaves_like 'when running a container_expiration_policy', - delete_expectations: [%w[Ba Bb C]] + delete_expectations: [%w[Ba Bb C]] + end + + context 'with no tags page' do + let(:tags_page_size) { 1000 } + let(:deleted) { [] } + let(:params) { {} } + + before do + allow(repository.gitlab_api_client) + .to receive(:tags) + .and_return({}) + end + + it { is_expected.to eq(expected_service_response(status: :success, deleted: [], original_size: 0)) } end end diff --git a/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb index f03912dba80..c4e6c7f4a11 100644 --- a/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb +++ b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do +RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService, feature_category: :container_registry do include_context 'container repository delete tags service shared context' let(:service) { described_class.new(repository, tags) } diff --git a/spec/services/projects/container_repository/third_party/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/third_party/cleanup_tags_service_spec.rb index 7227834b131..836e722eb99 100644 --- a/spec/services/projects/container_repository/third_party/cleanup_tags_service_spec.rb +++ b/spec/services/projects/container_repository/third_party/cleanup_tags_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ContainerRepository::ThirdParty::CleanupTagsService, :clean_gitlab_redis_cache do +RSpec.describe Projects::ContainerRepository::ThirdParty::CleanupTagsService, :clean_gitlab_redis_cache, feature_category: :container_registry do using RSpec::Parameterized::TableSyntax include_context 'for a cleanup tags service' @@ -42,112 +42,112 @@ RSpec.describe Projects::ContainerRepository::ThirdParty::CleanupTagsService, :c subject { service.execute } it_behaves_like 'when regex matching everything is specified', - delete_expectations: [%w[A Ba Bb C D E]], - service_response_extra: { - before_truncate_size: 6, - after_truncate_size: 6, - before_delete_size: 6, - cached_tags_count: 0 - }, - supports_caching: true + delete_expectations: [%w[A Ba Bb C D E]], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 6, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'when regex matching everything is specified and latest is not kept', - delete_expectations: [%w[A Ba Bb C D E latest]], - service_response_extra: { - before_truncate_size: 7, - after_truncate_size: 7, - before_delete_size: 7, - cached_tags_count: 0 - }, - supports_caching: true + delete_expectations: [%w[A Ba Bb C D E latest]], + service_response_extra: { + before_truncate_size: 7, + after_truncate_size: 7, + before_delete_size: 7, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'when delete regex matching specific tags is used', - service_response_extra: { - before_truncate_size: 2, - after_truncate_size: 2, - before_delete_size: 2, - cached_tags_count: 0 - }, - supports_caching: true + service_response_extra: { + before_truncate_size: 2, + after_truncate_size: 2, + before_delete_size: 2, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex', - service_response_extra: { - before_truncate_size: 1, - after_truncate_size: 1, - before_delete_size: 1, - cached_tags_count: 0 - }, - supports_caching: true + service_response_extra: { + before_truncate_size: 1, + after_truncate_size: 1, + before_delete_size: 1, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'with allow regex value', - delete_expectations: [%w[A C D E]], - service_response_extra: { - before_truncate_size: 4, - after_truncate_size: 4, - before_delete_size: 4, - cached_tags_count: 0 - }, - supports_caching: true + delete_expectations: [%w[A C D E]], + service_response_extra: { + before_truncate_size: 4, + after_truncate_size: 4, + before_delete_size: 4, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'when keeping only N tags', - delete_expectations: [%w[Bb Ba C]], - service_response_extra: { - before_truncate_size: 4, - after_truncate_size: 4, - before_delete_size: 3, - cached_tags_count: 0 - }, - supports_caching: true + delete_expectations: [%w[Bb Ba C]], + service_response_extra: { + before_truncate_size: 4, + after_truncate_size: 4, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'when not keeping N tags', - delete_expectations: [%w[A Ba Bb C]], - service_response_extra: { - before_truncate_size: 4, - after_truncate_size: 4, - before_delete_size: 4, - cached_tags_count: 0 - }, - supports_caching: true + delete_expectations: [%w[A Ba Bb C]], + service_response_extra: { + before_truncate_size: 4, + after_truncate_size: 4, + before_delete_size: 4, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'when removing keeping only 3', - delete_expectations: [%w[Bb Ba C]], - service_response_extra: { - before_truncate_size: 6, - after_truncate_size: 6, - before_delete_size: 3, - cached_tags_count: 0 - }, - supports_caching: true + delete_expectations: [%w[Bb Ba C]], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'when removing older than 1 day', - delete_expectations: [%w[Ba Bb C]], - service_response_extra: { - before_truncate_size: 6, - after_truncate_size: 6, - before_delete_size: 3, - cached_tags_count: 0 - }, - supports_caching: true + delete_expectations: [%w[Ba Bb C]], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'when combining all parameters', - delete_expectations: [%w[Bb Ba C]], - service_response_extra: { - before_truncate_size: 6, - after_truncate_size: 6, - before_delete_size: 3, - cached_tags_count: 0 - }, - supports_caching: true + delete_expectations: [%w[Bb Ba C]], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true it_behaves_like 'when running a container_expiration_policy', - delete_expectations: [%w[Bb Ba C]], - service_response_extra: { - before_truncate_size: 6, - after_truncate_size: 6, - before_delete_size: 3, - cached_tags_count: 0 - }, - supports_caching: true + delete_expectations: [%w[Bb Ba C]], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true context 'when running a container_expiration_policy with caching' do let(:user) { nil } diff --git a/spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb b/spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb index 4de36452684..0c297b6e1f7 100644 --- a/spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb +++ b/spec/services/projects/container_repository/third_party/delete_tags_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ContainerRepository::ThirdParty::DeleteTagsService do +RSpec.describe Projects::ContainerRepository::ThirdParty::DeleteTagsService, feature_category: :container_registry do include_context 'container repository delete tags service shared context' let(:service) { described_class.new(repository, tags) } diff --git a/spec/services/projects/count_service_spec.rb b/spec/services/projects/count_service_spec.rb index 11b2b57a277..71940fa396e 100644 --- a/spec/services/projects/count_service_spec.rb +++ b/spec/services/projects/count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::CountService do +RSpec.describe Projects::CountService, feature_category: :projects do let(:project) { build(:project, id: 1) } let(:service) { described_class.new(project) } diff --git a/spec/services/projects/create_from_template_service_spec.rb b/spec/services/projects/create_from_template_service_spec.rb index fba6225b87a..a3fdb258f75 100644 --- a/spec/services/projects/create_from_template_service_spec.rb +++ b/spec/services/projects/create_from_template_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::CreateFromTemplateService do +RSpec.describe Projects::CreateFromTemplateService, feature_category: :projects do let(:user) { create(:user) } let(:template_name) { 'rails' } let(:project_params) do diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index e435db4efa6..303a98cb35b 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -254,6 +254,27 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :projects end it_behaves_like 'has sync-ed traversal_ids' + + context 'when project is an import' do + before do + stub_application_setting(import_sources: ['gitlab_project']) + end + + context 'when user is not allowed to import projects' do + let(:group) do + create(:group).tap do |group| + group.add_developer(user) + end + end + + it 'does not create the project' do + project = create_project(user, opts.merge!(namespace_id: group.id, import_type: 'gitlab_project')) + + expect(project).not_to be_persisted + expect(project.errors.messages[:user].first).to eq('is not allowed to import projects') + end + end + end end context 'group sharing', :sidekiq_inline do @@ -339,9 +360,12 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :projects before do group.add_maintainer(group_maintainer) - create(:group_group_link, shared_group: subgroup_for_projects, - shared_with_group: subgroup_for_access, - group_access: share_max_access_level) + create( + :group_group_link, + shared_group: subgroup_for_projects, + shared_with_group: subgroup_for_access, + group_access: share_max_access_level + ) end context 'membership is higher from group hierarchy' do @@ -716,16 +740,34 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :projects end end - context 'and a default_branch_name is specified' do + context 'and default_branch is specified' do before do - allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return('example_branch') + opts[:default_branch] = 'example_branch' end it 'creates the correct branch' do - branches = project.repository.branches + expect(project.repository.branch_names).to contain_exactly('example_branch') + end - expect(branches.size).to eq(1) - expect(branches.collect(&:name)).to contain_exactly('example_branch') + it_behaves_like 'a repo with a README.md' do + let(:expected_content) do + <<~MARKDOWN + cd existing_repo + git remote add origin #{project.http_url_to_repo} + git branch -M example_branch + git push -uf origin example_branch + MARKDOWN + end + end + end + + context 'and the default branch setting is configured' do + before do + allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return('example_branch') + end + + it 'creates the correct branch' do + expect(project.repository.branch_names).to contain_exactly('example_branch') end it_behaves_like 'a repo with a README.md' do @@ -956,11 +998,11 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :projects receive(:perform_async).and_call_original ) expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to( - receive(:bulk_perform_in) - .with(1.hour, - array_including([user.id], [other_user.id]), - batch_delay: 30.seconds, batch_size: 100) - .and_call_original + receive(:bulk_perform_in).with( + 1.hour, + array_including([user.id], [other_user.id]), + batch_delay: 30.seconds, batch_size: 100 + ).and_call_original ) project = create_project(user, opts) diff --git a/spec/services/projects/deploy_tokens/create_service_spec.rb b/spec/services/projects/deploy_tokens/create_service_spec.rb index 831dbc06588..96458a51fb4 100644 --- a/spec/services/projects/deploy_tokens/create_service_spec.rb +++ b/spec/services/projects/deploy_tokens/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::DeployTokens::CreateService do +RSpec.describe Projects::DeployTokens::CreateService, feature_category: :continuous_delivery do it_behaves_like 'a deploy token creation service' do let(:entity) { create(:project) } let(:deploy_token_class) { ProjectDeployToken } diff --git a/spec/services/projects/deploy_tokens/destroy_service_spec.rb b/spec/services/projects/deploy_tokens/destroy_service_spec.rb index edb2345aa6c..3d0323c60ba 100644 --- a/spec/services/projects/deploy_tokens/destroy_service_spec.rb +++ b/spec/services/projects/deploy_tokens/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::DeployTokens::DestroyService do +RSpec.describe Projects::DeployTokens::DestroyService, feature_category: :continuous_delivery do it_behaves_like 'a deploy token deletion service' do let_it_be(:entity) { create(:project) } let_it_be(:deploy_token_class) { ProjectDeployToken } diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 0689a65c2f4..665f930a0a8 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -207,9 +207,11 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi context 'when project has exports' do let!(:project_with_export) do create(:project, :repository, namespace: user.namespace).tap do |project| - create(:import_export_upload, - project: project, - export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz')) + create( + :import_export_upload, + project: project, + export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz') + ) end end @@ -337,8 +339,7 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi let(:container_repository) { create(:container_repository) } before do - stub_container_registry_tags(repository: project.full_path + '/image', - tags: ['tag']) + stub_container_registry_tags(repository: project.full_path + '/image', tags: ['tag']) project.container_repositories << container_repository end @@ -387,8 +388,7 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi context 'when there are tags for legacy root repository' do before do - stub_container_registry_tags(repository: project.full_path, - tags: ['tag']) + stub_container_registry_tags(repository: project.full_path, tags: ['tag']) end context 'when image repository tags deletion succeeds' do @@ -414,8 +414,7 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi context 'when there are no tags for legacy root repository' do before do - stub_container_registry_tags(repository: project.full_path, - tags: []) + stub_container_registry_tags(repository: project.full_path, tags: []) end it 'does not try to destroy the repository' do diff --git a/spec/services/projects/detect_repository_languages_service_spec.rb b/spec/services/projects/detect_repository_languages_service_spec.rb index cf4c7a5024d..5759f8128d0 100644 --- a/spec/services/projects/detect_repository_languages_service_spec.rb +++ b/spec/services/projects/detect_repository_languages_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state do +RSpec.describe Projects::DetectRepositoryLanguagesService, :clean_gitlab_redis_shared_state, feature_category: :projects do let_it_be(:project, reload: true) { create(:project, :repository) } subject { described_class.new(project) } diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb index f158b11a9fa..52bdbefe01a 100644 --- a/spec/services/projects/download_service_spec.rb +++ b/spec/services/projects/download_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::DownloadService do +RSpec.describe Projects::DownloadService, feature_category: :projects do describe 'File service' do before do @user = create(:user) diff --git a/spec/services/projects/enable_deploy_key_service_spec.rb b/spec/services/projects/enable_deploy_key_service_spec.rb index c0b3992037e..59c76a96d07 100644 --- a/spec/services/projects/enable_deploy_key_service_spec.rb +++ b/spec/services/projects/enable_deploy_key_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::EnableDeployKeyService do +RSpec.describe Projects::EnableDeployKeyService, feature_category: :continuous_delivery do let(:deploy_key) { create(:deploy_key, public: true) } let(:project) { create(:project) } let(:user) { project.creator } diff --git a/spec/services/projects/fetch_statistics_increment_service_spec.rb b/spec/services/projects/fetch_statistics_increment_service_spec.rb index 16121a42c39..9e24e68fa98 100644 --- a/spec/services/projects/fetch_statistics_increment_service_spec.rb +++ b/spec/services/projects/fetch_statistics_increment_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' module Projects - RSpec.describe FetchStatisticsIncrementService do + RSpec.describe FetchStatisticsIncrementService, feature_category: :projects do let(:project) { create(:project) } describe '#execute' do diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 48756cf774b..4ba72b5870d 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ForkService do +RSpec.describe Projects::ForkService, feature_category: :source_code_management do include ProjectForksHelper shared_examples 'forks count cache refresh' do @@ -22,14 +22,16 @@ RSpec.describe Projects::ForkService do @from_user = create(:user) @from_namespace = @from_user.namespace avatar = fixture_file_upload("spec/fixtures/dk.png", "image/png") - @from_project = create(:project, - :repository, - creator_id: @from_user.id, - namespace: @from_namespace, - star_count: 107, - avatar: avatar, - description: 'wow such project', - external_authorization_classification_label: 'classification-label') + @from_project = create( + :project, + :repository, + creator_id: @from_user.id, + namespace: @from_namespace, + star_count: 107, + avatar: avatar, + description: 'wow such project', + external_authorization_classification_label: 'classification-label' + ) @to_user = create(:user) @to_namespace = @to_user.namespace @from_project.add_member(@to_user, :developer) @@ -148,12 +150,11 @@ RSpec.describe Projects::ForkService do context 'project already exists' do it "fails due to validation, not transaction failure" do - @existing_project = create(:project, :repository, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) + @existing_project = create(:project, :repository, creator_id: @to_user.id, path: @from_project.path, namespace: @to_namespace) @to_project = fork_project(@from_project, @to_user, namespace: @to_namespace, using_service: true) expect(@existing_project).to be_persisted expect(@to_project).not_to be_persisted - expect(@to_project.errors[:name]).to eq(['has already been taken']) expect(@to_project.errors[:path]).to eq(['has already been taken']) end end @@ -258,11 +259,13 @@ RSpec.describe Projects::ForkService do before do @group_owner = create(:user) @developer = create(:user) - @project = create(:project, :repository, - creator_id: @group_owner.id, - star_count: 777, - description: 'Wow, such a cool project!', - ci_config_path: 'debian/salsa-ci.yml') + @project = create( + :project, :repository, + creator_id: @group_owner.id, + star_count: 777, + description: 'Wow, such a cool project!', + ci_config_path: 'debian/salsa-ci.yml' + ) @group = create(:group) @group.add_member(@group_owner, GroupMember::OWNER) @group.add_member(@developer, GroupMember::DEVELOPER) @@ -297,12 +300,9 @@ RSpec.describe Projects::ForkService do context 'project already exists in group' do it 'fails due to validation, not transaction failure' do - existing_project = create(:project, :repository, - name: @project.name, - namespace: @group) + existing_project = create(:project, :repository, path: @project.path, namespace: @group) to_project = fork_project(@project, @group_owner, @opts) expect(existing_project.persisted?).to be_truthy - expect(to_project.errors[:name]).to eq(['has already been taken']) expect(to_project.errors[:path]).to eq(['has already been taken']) end end diff --git a/spec/services/projects/forks/sync_service_spec.rb b/spec/services/projects/forks/sync_service_spec.rb new file mode 100644 index 00000000000..aeb53992ed4 --- /dev/null +++ b/spec/services/projects/forks/sync_service_spec.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::Forks::SyncService, feature_category: :source_code_management do + include ProjectForksHelper + include RepoHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:source_project) { create(:project, :repository, :public) } + let_it_be(:project) { fork_project(source_project, user, { repository: true }) } + + let(:fork_branch) { project.default_branch } + let(:service) { described_class.new(project, user, fork_branch) } + + def details + Projects::Forks::Details.new(project, fork_branch) + end + + def expect_to_cancel_exclusive_lease + expect(Gitlab::ExclusiveLease).to receive(:cancel) + end + + describe '#execute' do + context 'when fork is up-to-date with the upstream' do + it 'does not perform merge' do + expect_to_cancel_exclusive_lease + expect(project.repository).not_to receive(:merge_to_branch) + expect(project.repository).not_to receive(:ff_merge) + + expect(service.execute).to be_success + end + end + + context 'when fork is behind the upstream' do + let_it_be(:base_commit) { source_project.commit.sha } + + before_all do + source_project.repository.commit_files( + user, + branch_name: source_project.repository.root_ref, message: 'Commit to root ref', + actions: [{ action: :create, file_path: 'encoding/CHANGELOG', content: 'One more' }] + ) + + source_project.repository.commit_files( + user, + branch_name: source_project.repository.root_ref, message: 'Another commit to root ref', + actions: [{ action: :create, file_path: 'encoding/NEW-CHANGELOG', content: 'One more time' }] + ) + end + + before do + project.repository.create_branch(fork_branch, base_commit) + end + + context 'when fork is not ahead of the upstream' do + let(:fork_branch) { 'fork-without-new-commits' } + + it 'updates the fork using ff merge' do + expect_to_cancel_exclusive_lease + expect(project.commit(fork_branch).sha).to eq(base_commit) + expect(project.repository).to receive(:ff_merge) + .with(user, source_project.commit.sha, fork_branch, target_sha: base_commit) + .and_call_original + + expect do + expect(service.execute).to be_success + end.to change { details.counts }.from({ ahead: 0, behind: 2 }).to({ ahead: 0, behind: 0 }) + end + end + + context 'when fork is ahead of the upstream' do + context 'and has conflicts with the upstream', :use_clean_rails_redis_caching do + let(:fork_branch) { 'fork-with-conflicts' } + + it 'returns an error' do + project.repository.commit_files( + user, + branch_name: fork_branch, message: 'Committing something', + actions: [{ action: :create, file_path: 'encoding/CHANGELOG', content: 'New file' }] + ) + + expect_to_cancel_exclusive_lease + expect(details).not_to have_conflicts + + expect do + result = service.execute + + expect(result).to be_error + expect(result.message).to eq("9:merging commits: merge: there are conflicting files.") + end.not_to change { details.counts } + + expect(details).to have_conflicts + end + end + + context 'and does not have conflicts with the upstream' do + let(:fork_branch) { 'fork-with-new-commits' } + + it 'updates the fork using merge' do + project.repository.commit_files( + user, + branch_name: fork_branch, message: 'Committing completely new changelog', + actions: [{ action: :create, file_path: 'encoding/COMPLETELY-NEW-CHANGELOG', content: 'New file' }] + ) + + commit_message = "Merge branch #{source_project.path}:#{source_project.default_branch} into #{fork_branch}" + expect(project.repository).to receive(:merge_to_branch).with( + user, + source_sha: source_project.commit.sha, + target_branch: fork_branch, + target_sha: project.commit(fork_branch).sha, + message: commit_message + ).and_call_original + expect_to_cancel_exclusive_lease + + expect do + expect(service.execute).to be_success + end.to change { details.counts }.from({ ahead: 1, behind: 2 }).to({ ahead: 2, behind: 0 }) + + commits = project.repository.commits_between(source_project.commit.sha, project.commit(fork_branch).sha) + expect(commits.map(&:message)).to eq([ + "Committing completely new changelog", + commit_message + ]) + end + end + end + + context 'when a merge cannot happen due to another ongoing merge' do + it 'does not merge' do + expect(service).to receive(:perform_merge).and_return(nil) + + result = service.execute + + expect(result).to be_error + expect(result.message).to eq(described_class::ONGOING_MERGE_ERROR) + end + end + + context 'when upstream branch contains lfs reference' do + let(:source_project) { create(:project, :repository, :public) } + let(:project) { fork_project(source_project, user, { repository: true }) } + let(:fork_branch) { 'fork-fetches-lfs-pointers' } + + before do + source_project.change_head('lfs') + + allow(source_project).to receive(:lfs_enabled?).and_return(true) + allow(project).to receive(:lfs_enabled?).and_return(true) + + create_file_in_repo(source_project, 'lfs', 'lfs', 'one.lfs', 'One') + create_file_in_repo(source_project, 'lfs', 'lfs', 'two.lfs', 'Two') + end + + it 'links fetched lfs objects to the fork project', :aggregate_failures do + expect_to_cancel_exclusive_lease + + expect do + expect(service.execute).to be_success + end.to change { project.reload.lfs_objects.size }.from(0).to(2) + .and change { details.counts }.from({ ahead: 0, behind: 3 }).to({ ahead: 0, behind: 0 }) + + expect(project.lfs_objects).to match_array(source_project.lfs_objects) + end + + context 'and there are too many of them for a single sync' do + let(:fork_branch) { 'fork-too-many-lfs-pointers' } + + it 'updates the fork successfully' do + expect_to_cancel_exclusive_lease + stub_const('Projects::LfsPointers::LfsLinkService::MAX_OIDS', 1) + + expect do + result = service.execute + + expect(result).to be_error + expect(result.message).to eq('Too many LFS object ids to link, please push them manually') + end.not_to change { details.counts } + end + end + end + end + end +end diff --git a/spec/services/projects/forks_count_service_spec.rb b/spec/services/projects/forks_count_service_spec.rb index 31662f78973..403d8656b7c 100644 --- a/spec/services/projects/forks_count_service_spec.rb +++ b/spec/services/projects/forks_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ForksCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Projects::ForksCountService, :use_clean_rails_memory_store_caching, feature_category: :source_code_management do let(:project) { build(:project) } subject { described_class.new(project) } diff --git a/spec/services/projects/git_deduplication_service_spec.rb b/spec/services/projects/git_deduplication_service_spec.rb index e6eff936de7..2b9f0974ae2 100644 --- a/spec/services/projects/git_deduplication_service_spec.rb +++ b/spec/services/projects/git_deduplication_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GitDeduplicationService do +RSpec.describe Projects::GitDeduplicationService, feature_category: :source_code_management do include ExclusiveLeaseHelpers let(:pool) { create(:pool_repository, :ready) } @@ -139,7 +139,7 @@ RSpec.describe Projects::GitDeduplicationService do end it 'fails when a lease is already out' do - expect(service).to receive(:log_error).with("Cannot obtain an exclusive lease for #{lease_key}. There must be another instance already in execution.") + expect(Gitlab::AppJsonLogger).to receive(:error).with({ message: "Cannot obtain an exclusive lease. There must be another instance already in execution.", lease_key: lease_key, class_name: described_class.name, lease_timeout: lease_timeout }) service.execute end diff --git a/spec/services/projects/gitlab_projects_import_service_spec.rb b/spec/services/projects/gitlab_projects_import_service_spec.rb index d32e720a49f..b1468a40212 100644 --- a/spec/services/projects/gitlab_projects_import_service_spec.rb +++ b/spec/services/projects/gitlab_projects_import_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GitlabProjectsImportService do +RSpec.describe Projects::GitlabProjectsImportService, feature_category: :importers do let_it_be(:namespace) { create(:namespace) } let(:path) { 'test-path' } diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb index eae898b4f68..4f2f480cf1c 100644 --- a/spec/services/projects/group_links/create_service_spec.rb +++ b/spec/services/projects/group_links/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GroupLinks::CreateService, '#execute' do +RSpec.describe Projects::GroupLinks::CreateService, '#execute', feature_category: :subgroups do let_it_be(:user) { create :user } let_it_be(:group) { create :group } let_it_be(:project) { create(:project, namespace: create(:namespace, :with_namespace_settings)) } @@ -69,11 +69,11 @@ RSpec.describe Projects::GroupLinks::CreateService, '#execute' do .and_call_original ) expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to( - receive(:bulk_perform_in) - .with(1.hour, - array_including([user.id], [other_user.id]), - batch_delay: 30.seconds, batch_size: 100) - .and_call_original + receive(:bulk_perform_in).with( + 1.hour, + array_including([user.id], [other_user.id]), + batch_delay: 30.seconds, batch_size: 100 + ).and_call_original ) subject.execute @@ -82,8 +82,7 @@ RSpec.describe Projects::GroupLinks::CreateService, '#execute' do context 'when sharing outside the hierarchy is disabled' do let_it_be(:shared_group_parent) do - create(:group, - namespace_settings: create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: true)) + create(:group, namespace_settings: create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: true)) end let_it_be(:project, reload: true) { create(:project, group: shared_group_parent) } diff --git a/spec/services/projects/group_links/destroy_service_spec.rb b/spec/services/projects/group_links/destroy_service_spec.rb index 89865d6bc3b..76bdd536a0d 100644 --- a/spec/services/projects/group_links/destroy_service_spec.rb +++ b/spec/services/projects/group_links/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GroupLinks::DestroyService, '#execute' do +RSpec.describe Projects::GroupLinks::DestroyService, '#execute', feature_category: :subgroups do let_it_be(:user) { create :user } let_it_be(:project) { create(:project, :private) } let_it_be(:group) { create(:group) } @@ -31,10 +31,11 @@ RSpec.describe Projects::GroupLinks::DestroyService, '#execute' do stub_feature_flags(do_not_run_safety_net_auth_refresh_jobs: false) expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to( - receive(:bulk_perform_in) - .with(1.hour, - [[user.id]], - batch_delay: 30.seconds, batch_size: 100) + receive(:bulk_perform_in).with( + 1.hour, + [[user.id]], + batch_delay: 30.seconds, batch_size: 100 + ) ) subject.execute(group_link) diff --git a/spec/services/projects/group_links/update_service_spec.rb b/spec/services/projects/group_links/update_service_spec.rb index 1acbb770763..4232412cf54 100644 --- a/spec/services/projects/group_links/update_service_spec.rb +++ b/spec/services/projects/group_links/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GroupLinks::UpdateService, '#execute' do +RSpec.describe Projects::GroupLinks::UpdateService, '#execute', feature_category: :subgroups do let_it_be(:user) { create :user } let_it_be(:group) { create :group } let_it_be(:project) { create :project } @@ -45,10 +45,11 @@ RSpec.describe Projects::GroupLinks::UpdateService, '#execute' do stub_feature_flags(do_not_run_safety_net_auth_refresh_jobs: false) expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to( - receive(:bulk_perform_in) - .with(1.hour, - [[user.id]], - batch_delay: 30.seconds, batch_size: 100) + receive(:bulk_perform_in).with( + 1.hour, + [[user.id]], + batch_delay: 30.seconds, batch_size: 100 + ) ) subject diff --git a/spec/services/projects/hashed_storage/base_attachment_service_spec.rb b/spec/services/projects/hashed_storage/base_attachment_service_spec.rb index 86e3fb3820c..01036fc2d9c 100644 --- a/spec/services/projects/hashed_storage/base_attachment_service_spec.rb +++ b/spec/services/projects/hashed_storage/base_attachment_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::HashedStorage::BaseAttachmentService do +RSpec.describe Projects::HashedStorage::BaseAttachmentService, feature_category: :projects do let(:project) { create(:project, :repository, storage_version: 0, skip_disk_validation: true) } subject(:service) { described_class.new(project: project, old_disk_path: project.full_path, logger: nil) } diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb index c8f24c6ce00..39263506bca 100644 --- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::HashedStorage::MigrateAttachmentsService do +RSpec.describe Projects::HashedStorage::MigrateAttachmentsService, feature_category: :projects do subject(:service) { described_class.new(project: project, old_disk_path: project.full_path, logger: nil) } let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) } diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index eb8d94ebfa5..bcc914e72b5 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::HashedStorage::MigrateRepositoryService do +RSpec.describe Projects::HashedStorage::MigrateRepositoryService, feature_category: :projects do let(:gitlab_shell) { Gitlab::Shell.new } let(:project) { create(:project, :legacy_storage, :repository, :wiki_repo, :design_repo) } let(:legacy_storage) { Storage::LegacyProject.new(project) } diff --git a/spec/services/projects/hashed_storage/migration_service_spec.rb b/spec/services/projects/hashed_storage/migration_service_spec.rb index ef96c17dd85..89bc55dbaf6 100644 --- a/spec/services/projects/hashed_storage/migration_service_spec.rb +++ b/spec/services/projects/hashed_storage/migration_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::HashedStorage::MigrationService do +RSpec.describe Projects::HashedStorage::MigrationService, feature_category: :projects do let(:project) { create(:project, :empty_repo, :wiki_repo, :legacy_storage) } let(:logger) { double } let!(:project_attachment) { build(:file_uploader, project: project) } @@ -16,9 +16,11 @@ RSpec.describe Projects::HashedStorage::MigrationService do describe '#execute' do context 'repository migration' do let(:repository_service) do - Projects::HashedStorage::MigrateRepositoryService.new(project: project, - old_disk_path: project.full_path, - logger: logger) + Projects::HashedStorage::MigrateRepositoryService.new( + project: project, + old_disk_path: project.full_path, + logger: logger + ) end it 'delegates migration to Projects::HashedStorage::MigrateRepositoryService' do @@ -53,9 +55,11 @@ RSpec.describe Projects::HashedStorage::MigrationService do let(:project) { create(:project, :empty_repo, :wiki_repo, storage_version: ::Project::HASHED_STORAGE_FEATURES[:repository]) } let(:attachments_service) do - Projects::HashedStorage::MigrateAttachmentsService.new(project: project, - old_disk_path: project.full_path, - logger: logger) + Projects::HashedStorage::MigrateAttachmentsService.new( + project: project, + old_disk_path: project.full_path, + logger: logger + ) end it 'delegates migration to Projects::HashedStorage::MigrateRepositoryService' do diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb index d4cb46c82ad..95491d63df2 100644 --- a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::HashedStorage::RollbackAttachmentsService do +RSpec.describe Projects::HashedStorage::RollbackAttachmentsService, feature_category: :projects do subject(:service) { described_class.new(project: project, old_disk_path: project.disk_path, logger: nil) } let(:project) { create(:project, :repository, skip_disk_validation: true) } diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb index 385c03e6308..19f1856e39a 100644 --- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state do +RSpec.describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state, feature_category: :projects do let(:gitlab_shell) { Gitlab::Shell.new } let(:project) { create(:project, :repository, :wiki_repo, :design_repo, storage_version: ::Project::HASHED_STORAGE_FEATURES[:repository]) } let(:legacy_storage) { Storage::LegacyProject.new(project) } diff --git a/spec/services/projects/hashed_storage/rollback_service_spec.rb b/spec/services/projects/hashed_storage/rollback_service_spec.rb index 0bd63f2da2a..6d047f856ec 100644 --- a/spec/services/projects/hashed_storage/rollback_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::HashedStorage::RollbackService do +RSpec.describe Projects::HashedStorage::RollbackService, feature_category: :projects do let(:project) { create(:project, :empty_repo, :wiki_repo) } let(:logger) { double } let!(:project_attachment) { build(:file_uploader, project: project) } diff --git a/spec/services/projects/import_error_filter_spec.rb b/spec/services/projects/import_error_filter_spec.rb index fd31cd52cc4..be07208c7f2 100644 --- a/spec/services/projects/import_error_filter_spec.rb +++ b/spec/services/projects/import_error_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ImportErrorFilter do +RSpec.describe Projects::ImportErrorFilter, feature_category: :importers do it 'filters any full paths' do message = 'Error importing into /my/folder Permission denied @ unlink_internal - /var/opt/gitlab/gitlab-rails/shared/a/b/c/uploads/file' diff --git a/spec/services/projects/import_export/relation_export_service_spec.rb b/spec/services/projects/import_export/relation_export_service_spec.rb index 94f5653ee7d..4b44a37b299 100644 --- a/spec/services/projects/import_export/relation_export_service_spec.rb +++ b/spec/services/projects/import_export/relation_export_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ImportExport::RelationExportService do +RSpec.describe Projects::ImportExport::RelationExportService, feature_category: :importers do using RSpec::Parameterized::TableSyntax subject(:service) { described_class.new(relation_export, 'jid') } @@ -49,6 +49,7 @@ RSpec.describe Projects::ImportExport::RelationExportService do expect(logger).to receive(:error).with( export_error: '', message: 'Project relation export failed', + relation: relation_export.relation, project_export_job_id: project_export_job.id, project_id: project_export_job.project.id, project_name: project_export_job.project.name @@ -78,6 +79,7 @@ RSpec.describe Projects::ImportExport::RelationExportService do expect(logger).to receive(:error).with( export_error: 'Error!', message: 'Project relation export failed', + relation: relation_export.relation, project_export_job_id: project_export_job.id, project_id: project_export_job.project.id, project_name: project_export_job.project.name diff --git a/spec/services/projects/in_product_marketing_campaign_emails_service_spec.rb b/spec/services/projects/in_product_marketing_campaign_emails_service_spec.rb index 4c51c8a4ac8..4ad6fd0edff 100644 --- a/spec/services/projects/in_product_marketing_campaign_emails_service_spec.rb +++ b/spec/services/projects/in_product_marketing_campaign_emails_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::InProductMarketingCampaignEmailsService do +RSpec.describe Projects::InProductMarketingCampaignEmailsService, feature_category: :experimentation_adoption do describe '#execute' do let(:user) { create(:user, email_opted_in: true) } let(:project) { create(:project) } diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb index 80b3c4d0403..0aaaae19f5a 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Projects::LfsPointers::LfsDownloadLinkListService do +RSpec.describe Projects::LfsPointers::LfsDownloadLinkListService, feature_category: :source_code_management do let(:import_url) { 'http://www.gitlab.com/demo/repo.git' } let(:lfs_endpoint) { "#{import_url}/info/lfs/objects/batch" } let!(:project) { create(:project, import_url: import_url) } diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb index c815ad38843..00c156ba538 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Projects::LfsPointers::LfsDownloadService do +RSpec.describe Projects::LfsPointers::LfsDownloadService, feature_category: :source_code_management do include StubRequests let_it_be(:project) { create(:project) } diff --git a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb index 32b86ade81e..f1e4db55962 100644 --- a/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_import_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Projects::LfsPointers::LfsImportService do +RSpec.describe Projects::LfsPointers::LfsImportService, feature_category: :source_code_management do let(:project) { create(:project) } let(:user) { project.creator } let(:import_url) { 'http://www.gitlab.com/demo/repo.git' } diff --git a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb index 0e7d16f18e8..fb3cc9bdac9 100644 --- a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Projects::LfsPointers::LfsLinkService do - let!(:project) { create(:project, lfs_enabled: true) } - let!(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) } +RSpec.describe Projects::LfsPointers::LfsLinkService, feature_category: :source_code_management do + let_it_be(:project) { create(:project, lfs_enabled: true) } + let_it_be(:lfs_objects_project) { create_list(:lfs_objects_project, 2, project: project) } + let(:new_oids) { { 'oid1' => 123, 'oid2' => 125 } } let(:all_oids) { LfsObject.pluck(:oid, :size).to_h.merge(new_oids) } let(:new_lfs_object) { create(:lfs_object) } @@ -17,12 +18,26 @@ RSpec.describe Projects::LfsPointers::LfsLinkService do describe '#execute' do it 'raises an error when trying to link too many objects at once' do + stub_const("#{described_class}::MAX_OIDS", 5) + oids = Array.new(described_class::MAX_OIDS) { |i| "oid-#{i}" } oids << 'the straw' expect { subject.execute(oids) }.to raise_error(described_class::TooManyOidsError) end + it 'executes a block after validation and before execution' do + block = instance_double(Proc) + + expect(subject).to receive(:validate!).ordered + expect(block).to receive(:call).ordered + expect(subject).to receive(:link_existing_lfs_objects).ordered + + subject.execute([]) do + block.call + end + end + it 'links existing lfs objects to the project' do expect(project.lfs_objects.count).to eq 2 @@ -41,13 +56,13 @@ RSpec.describe Projects::LfsPointers::LfsLinkService do it 'links in batches' do stub_const("#{described_class}::BATCH_SIZE", 3) - expect(Gitlab::Import::Logger) - .to receive(:info) - .with(class: described_class.name, - project_id: project.id, - project_path: project.full_path, - lfs_objects_linked_count: 7, - iterations: 3) + expect(Gitlab::Import::Logger).to receive(:info).with( + class: described_class.name, + project_id: project.id, + project_path: project.full_path, + lfs_objects_linked_count: 7, + iterations: 3 + ) lfs_objects = create_list(:lfs_object, 7) linked = subject.execute(lfs_objects.pluck(:oid)) diff --git a/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb index 59eb1ed7a29..f5dcae05959 100644 --- a/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_object_download_list_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Projects::LfsPointers::LfsObjectDownloadListService do +RSpec.describe Projects::LfsPointers::LfsObjectDownloadListService, feature_category: :source_code_management do let(:import_url) { 'http://www.gitlab.com/demo/repo.git' } let(:default_endpoint) { "#{import_url}/info/lfs/objects/batch" } let(:group) { create(:group, lfs_enabled: true) } diff --git a/spec/services/projects/move_access_service_spec.rb b/spec/services/projects/move_access_service_spec.rb index 45e10c3ca84..b9244002f6c 100644 --- a/spec/services/projects/move_access_service_spec.rb +++ b/spec/services/projects/move_access_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::MoveAccessService do +RSpec.describe Projects::MoveAccessService, feature_category: :projects do let(:user) { create(:user) } let(:group) { create(:group) } let(:project_with_access) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/move_deploy_keys_projects_service_spec.rb b/spec/services/projects/move_deploy_keys_projects_service_spec.rb index 59674a3a4ef..b40eb4a18d1 100644 --- a/spec/services/projects/move_deploy_keys_projects_service_spec.rb +++ b/spec/services/projects/move_deploy_keys_projects_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::MoveDeployKeysProjectsService do +RSpec.describe Projects::MoveDeployKeysProjectsService, feature_category: :continuous_delivery do let!(:user) { create(:user) } let!(:project_with_deploy_keys) { create(:project, namespace: user.namespace) } let!(:target_project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/move_forks_service_spec.rb b/spec/services/projects/move_forks_service_spec.rb index 7d3637b7758..093562207dd 100644 --- a/spec/services/projects/move_forks_service_spec.rb +++ b/spec/services/projects/move_forks_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::MoveForksService do +RSpec.describe Projects::MoveForksService, feature_category: :source_code_management do include ProjectForksHelper let!(:user) { create(:user) } diff --git a/spec/services/projects/move_lfs_objects_projects_service_spec.rb b/spec/services/projects/move_lfs_objects_projects_service_spec.rb index e3df5fed9cf..f3cc4014b1c 100644 --- a/spec/services/projects/move_lfs_objects_projects_service_spec.rb +++ b/spec/services/projects/move_lfs_objects_projects_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::MoveLfsObjectsProjectsService do +RSpec.describe Projects::MoveLfsObjectsProjectsService, feature_category: :source_code_management do let!(:user) { create(:user) } let!(:project_with_lfs_objects) { create(:project, namespace: user.namespace) } let!(:target_project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/move_notification_settings_service_spec.rb b/spec/services/projects/move_notification_settings_service_spec.rb index e381ae7590f..5ef6e8a0647 100644 --- a/spec/services/projects/move_notification_settings_service_spec.rb +++ b/spec/services/projects/move_notification_settings_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::MoveNotificationSettingsService do +RSpec.describe Projects::MoveNotificationSettingsService, feature_category: :projects do let(:user) { create(:user) } let(:project_with_notifications) { create(:project, namespace: user.namespace) } let(:target_project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/move_project_authorizations_service_spec.rb b/spec/services/projects/move_project_authorizations_service_spec.rb index d47b13ca939..6cd0b056325 100644 --- a/spec/services/projects/move_project_authorizations_service_spec.rb +++ b/spec/services/projects/move_project_authorizations_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::MoveProjectAuthorizationsService do +RSpec.describe Projects::MoveProjectAuthorizationsService, feature_category: :projects do let!(:user) { create(:user) } let(:project_with_users) { create(:project, namespace: user.namespace) } let(:target_project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/move_project_group_links_service_spec.rb b/spec/services/projects/move_project_group_links_service_spec.rb index 1fca96a0367..cfd4b51b001 100644 --- a/spec/services/projects/move_project_group_links_service_spec.rb +++ b/spec/services/projects/move_project_group_links_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::MoveProjectGroupLinksService do +RSpec.describe Projects::MoveProjectGroupLinksService, feature_category: :projects do let!(:user) { create(:user) } let(:project_with_groups) { create(:project, namespace: user.namespace) } let(:target_project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/move_project_members_service_spec.rb b/spec/services/projects/move_project_members_service_spec.rb index 8fbd0ba3270..364fb7faaf2 100644 --- a/spec/services/projects/move_project_members_service_spec.rb +++ b/spec/services/projects/move_project_members_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::MoveProjectMembersService do +RSpec.describe Projects::MoveProjectMembersService, feature_category: :projects do let!(:user) { create(:user) } let(:project_with_users) { create(:project, namespace: user.namespace) } let(:target_project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/move_users_star_projects_service_spec.rb b/spec/services/projects/move_users_star_projects_service_spec.rb index b580d3d8772..b99e51d954b 100644 --- a/spec/services/projects/move_users_star_projects_service_spec.rb +++ b/spec/services/projects/move_users_star_projects_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::MoveUsersStarProjectsService do +RSpec.describe Projects::MoveUsersStarProjectsService, feature_category: :projects do let!(:user) { create(:user) } let!(:project_with_stars) { create(:project, namespace: user.namespace) } let!(:target_project) { create(:project, namespace: user.namespace) } diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb index c739fea5ecf..89405f06f5d 100644 --- a/spec/services/projects/open_issues_count_service_spec.rb +++ b/spec/services/projects/open_issues_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_caching, feature_category: :team_planning do let(:project) { create(:project) } subject { described_class.new(project) } diff --git a/spec/services/projects/open_merge_requests_count_service_spec.rb b/spec/services/projects/open_merge_requests_count_service_spec.rb index 6caef181e77..9d94fff2d20 100644 --- a/spec/services/projects/open_merge_requests_count_service_spec.rb +++ b/spec/services/projects/open_merge_requests_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_store_caching, feature_category: :code_review_workflow do let_it_be(:project) { create(:project) } subject { described_class.new(project) } @@ -11,10 +11,7 @@ RSpec.describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_ describe '#count' do it 'returns the number of open merge requests' do - create(:merge_request, - :opened, - source_project: project, - target_project: project) + create(:merge_request, :opened, source_project: project, target_project: project) expect(subject.count).to eq(1) end diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb index 95f2176dbc0..7babaf4d0d8 100644 --- a/spec/services/projects/operations/update_service_spec.rb +++ b/spec/services/projects/operations/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Operations::UpdateService do +RSpec.describe Projects::Operations::UpdateService, feature_category: :projects do let_it_be_with_refind(:project) { create(:project) } let_it_be(:user) { create(:user) } diff --git a/spec/services/projects/overwrite_project_service_spec.rb b/spec/services/projects/overwrite_project_service_spec.rb index 7038910508f..b4faf45a1cb 100644 --- a/spec/services/projects/overwrite_project_service_spec.rb +++ b/spec/services/projects/overwrite_project_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::OverwriteProjectService do +RSpec.describe Projects::OverwriteProjectService, feature_category: :projects do include ProjectForksHelper let(:user) { create(:user) } diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index fc745cd669f..bd297343879 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ParticipantsService do +RSpec.describe Projects::ParticipantsService, feature_category: :projects do describe '#execute' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public) } diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb index 43d23023d83..0feac6c3e72 100644 --- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb +++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Prometheus::Alerts::NotifyService do +RSpec.describe Projects::Prometheus::Alerts::NotifyService, feature_category: :metrics do include PrometheusHelpers using RSpec::Parameterized::TableSyntax @@ -45,10 +45,8 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do end before do - create(:clusters_integrations_prometheus, - cluster: prd_cluster, alert_manager_token: token) - create(:clusters_integrations_prometheus, - cluster: stg_cluster, alert_manager_token: nil) + create(:clusters_integrations_prometheus, cluster: prd_cluster, alert_manager_token: token) + create(:clusters_integrations_prometheus, cluster: stg_cluster, alert_manager_token: nil) end context 'without token' do @@ -78,10 +76,12 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do cluster.update!(enabled: cluster_enabled) unless integration_enabled.nil? - create(:clusters_integrations_prometheus, - cluster: cluster, - enabled: integration_enabled, - alert_manager_token: configured_token) + create( + :clusters_integrations_prometheus, + cluster: cluster, + enabled: integration_enabled, + alert_manager_token: configured_token + ) end end @@ -118,9 +118,11 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do create(:prometheus_integration, project: project) if alerting_setting - create(:project_alerting_setting, - project: project, - token: configured_token) + create( + :project_alerting_setting, + project: project, + token: configured_token + ) end end diff --git a/spec/services/projects/prometheus/metrics/destroy_service_spec.rb b/spec/services/projects/prometheus/metrics/destroy_service_spec.rb index b4af81f2c87..4c2a959a149 100644 --- a/spec/services/projects/prometheus/metrics/destroy_service_spec.rb +++ b/spec/services/projects/prometheus/metrics/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Prometheus::Metrics::DestroyService do +RSpec.describe Projects::Prometheus::Metrics::DestroyService, feature_category: :metrics do let(:metric) { create(:prometheus_metric) } subject { described_class.new(metric) } diff --git a/spec/services/projects/protect_default_branch_service_spec.rb b/spec/services/projects/protect_default_branch_service_spec.rb index 9f9e89ff8f8..a4fdd9983b8 100644 --- a/spec/services/projects/protect_default_branch_service_spec.rb +++ b/spec/services/projects/protect_default_branch_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ProtectDefaultBranchService do +RSpec.describe Projects::ProtectDefaultBranchService, feature_category: :source_code_management do let(:service) { described_class.new(project) } let(:project) { create(:project) } @@ -247,6 +247,7 @@ RSpec.describe Projects::ProtectDefaultBranchService do context 'when feature flag `group_protected_branches` disabled' do before do stub_feature_flags(group_protected_branches: false) + stub_feature_flags(allow_protected_branches_for_group: false) end it 'return false' do @@ -257,6 +258,7 @@ RSpec.describe Projects::ProtectDefaultBranchService do context 'when feature flag `group_protected_branches` enabled' do before do stub_feature_flags(group_protected_branches: true) + stub_feature_flags(allow_protected_branches_for_group: true) end it 'return true' do diff --git a/spec/services/projects/readme_renderer_service_spec.rb b/spec/services/projects/readme_renderer_service_spec.rb index 14cdcf67640..842d75e82ee 100644 --- a/spec/services/projects/readme_renderer_service_spec.rb +++ b/spec/services/projects/readme_renderer_service_spec.rb @@ -2,14 +2,14 @@ require 'spec_helper' -RSpec.describe Projects::ReadmeRendererService, '#execute' do +RSpec.describe Projects::ReadmeRendererService, '#execute', feature_category: :projects do using RSpec::Parameterized::TableSyntax subject(:service) { described_class.new(project, nil, opts) } let_it_be(:project) { create(:project, title: 'My Project', description: '_custom_description_') } - let(:opts) { {} } + let(:opts) { { default_branch: 'master' } } it 'renders the an ERB readme template' do expect(service.execute).to start_with(<<~MARKDOWN) diff --git a/spec/services/projects/record_target_platforms_service_spec.rb b/spec/services/projects/record_target_platforms_service_spec.rb index 22ff325a62e..17aa7fd7009 100644 --- a/spec/services/projects/record_target_platforms_service_spec.rb +++ b/spec/services/projects/record_target_platforms_service_spec.rb @@ -2,41 +2,30 @@ require 'spec_helper' -RSpec.describe Projects::RecordTargetPlatformsService, '#execute' do +RSpec.describe Projects::RecordTargetPlatformsService, '#execute', feature_category: :projects do let_it_be(:project) { create(:project) } let(:detector_service) { Projects::AppleTargetPlatformDetectorService } subject(:execute) { described_class.new(project, detector_service).execute } - context 'when detector returns target platform values' do - let(:detector_result) { [:ios, :osx] } - let(:service_result) { detector_result.map(&:to_s) } + context 'when project is an XCode project' do + def project_setting + ProjectSetting.find_by_project_id(project.id) + end before do - double = instance_double(detector_service, execute: detector_result) - allow(detector_service).to receive(:new) { double } + double = instance_double(detector_service, execute: [:ios, :osx]) + allow(Projects::AppleTargetPlatformDetectorService).to receive(:new) { double } end - shared_examples 'saves and returns detected target platforms' do - it 'creates a new setting record for the project', :aggregate_failures do - expect { execute }.to change { ProjectSetting.count }.from(0).to(1) - expect(ProjectSetting.last.target_platforms).to match_array(service_result) - end - - it 'returns the array of stored target platforms' do - expect(execute).to match_array service_result - end + it 'creates a new setting record for the project', :aggregate_failures do + expect { execute }.to change { ProjectSetting.count }.from(0).to(1) + expect(ProjectSetting.last.target_platforms).to match_array(%w(ios osx)) end - it_behaves_like 'saves and returns detected target platforms' - - context 'when detector returns a non-array value' do - let(:detector_service) { Projects::AndroidTargetPlatformDetectorService } - let(:detector_result) { :android } - let(:service_result) { [detector_result.to_s] } - - it_behaves_like 'saves and returns detected target platforms' + it 'returns array of detected target platforms' do + expect(execute).to match_array %w(ios osx) end context 'when a project has an existing setting record' do @@ -44,10 +33,6 @@ RSpec.describe Projects::RecordTargetPlatformsService, '#execute' do create(:project_setting, project: project, target_platforms: saved_target_platforms) end - def project_setting - ProjectSetting.find_by_project_id(project.id) - end - context 'when target platforms changed' do let(:saved_target_platforms) { %w(tvos) } @@ -98,44 +83,23 @@ RSpec.describe Projects::RecordTargetPlatformsService, '#execute' do it_behaves_like 'tracks experiment assignment event' end - shared_examples 'does not send email' do - it 'does not execute a Projects::InProductMarketingCampaignEmailsService' do - expect(Projects::InProductMarketingCampaignEmailsService).not_to receive(:new) - - execute - end - end - context 'experiment control' do before do stub_experiments(build_ios_app_guide_email: :control) end - it_behaves_like 'does not send email' - it_behaves_like 'tracks experiment assignment event' - end - - context 'when project is not an iOS project' do - let(:detector_service) { Projects::AppleTargetPlatformDetectorService } - let(:detector_result) { :android } - - before do - stub_experiments(build_ios_app_guide_email: :candidate) - end - - it_behaves_like 'does not send email' - - it 'does not track experiment assignment event', :experiment do - expect(experiment(:build_ios_app_guide_email)) - .not_to track(:assignment) + it 'does not execute a Projects::InProductMarketingCampaignEmailsService' do + expect(Projects::InProductMarketingCampaignEmailsService).not_to receive(:new) execute end + + it_behaves_like 'tracks experiment assignment event' end end end - context 'when detector does not return any target platform values' do + context 'when project is not an XCode project' do before do double = instance_double(Projects::AppleTargetPlatformDetectorService, execute: []) allow(Projects::AppleTargetPlatformDetectorService).to receive(:new).with(project) { double } diff --git a/spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb b/spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb index 62330441d2f..591cd1cba8d 100644 --- a/spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb +++ b/spec/services/projects/refresh_build_artifacts_size_statistics_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::RefreshBuildArtifactsSizeStatisticsService, :clean_gitlab_redis_shared_state do +RSpec.describe Projects::RefreshBuildArtifactsSizeStatisticsService, :clean_gitlab_redis_shared_state, feature_category: :build_artifacts do let(:service) { described_class.new } describe '#execute' do diff --git a/spec/services/projects/repository_languages_service_spec.rb b/spec/services/projects/repository_languages_service_spec.rb index 50d5fba6b84..a02844309b2 100644 --- a/spec/services/projects/repository_languages_service_spec.rb +++ b/spec/services/projects/repository_languages_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::RepositoryLanguagesService do +RSpec.describe Projects::RepositoryLanguagesService, feature_category: :source_code_management do let(:service) { described_class.new(project, project.first_owner) } context 'when detected_repository_languages flag is set' do diff --git a/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb b/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb index 76830396104..3d89f6efa6f 100644 --- a/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb +++ b/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ScheduleBulkRepositoryShardMovesService do +RSpec.describe Projects::ScheduleBulkRepositoryShardMovesService, feature_category: :source_code_management do it_behaves_like 'moves repository shard in bulk' do let_it_be_with_reload(:container) { create(:project, :repository) } diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 32818535146..48d5935f22f 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::TransferService do +RSpec.describe Projects::TransferService, feature_category: :projects do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } let_it_be(:group_integration) { create(:integrations_slack, :group, group: group, webhook: 'http://group.slack.com') } @@ -20,12 +20,32 @@ RSpec.describe Projects::TransferService do subject(:transfer_service) { described_class.new(project, user) } - let!(:package) { create(:npm_package, project: project) } + let!(:package) { create(:npm_package, project: project, name: "@testscope/test") } context 'with a root namespace change' do + it 'allow the transfer' do + expect(transfer_service.execute(group)).to be true + expect(project.errors[:new_namespace]).to be_empty + end + end + + context 'with pending destruction package' do + before do + package.pending_destruction! + end + + it 'allow the transfer' do + expect(transfer_service.execute(group)).to be true + expect(project.errors[:new_namespace]).to be_empty + end + end + + context 'with namespaced packages present' do + let!(:package) { create(:npm_package, project: project, name: "@#{project.root_namespace.path}/test") } + it 'does not allow the transfer' do expect(transfer_service.execute(group)).to be false - expect(project.errors[:new_namespace]).to include("Root namespace can't be updated if project has NPM packages") + expect(project.errors[:new_namespace]).to include("Root namespace can't be updated if the project has NPM packages scoped to the current root level namespace.") end end @@ -39,7 +59,7 @@ RSpec.describe Projects::TransferService do other_group.add_owner(user) end - it 'does allow the transfer' do + it 'allow the transfer' do expect(transfer_service.execute(other_group)).to be true expect(project.errors[:new_namespace]).to be_empty end @@ -667,10 +687,11 @@ RSpec.describe Projects::TransferService do user_ids = [user.id, member_of_old_group.id, member_of_new_group.id].map { |id| [id] } expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to( - receive(:bulk_perform_in) - .with(1.hour, - user_ids, - batch_delay: 30.seconds, batch_size: 100) + receive(:bulk_perform_in).with( + 1.hour, + user_ids, + batch_delay: 30.seconds, batch_size: 100 + ) ) subject @@ -694,10 +715,15 @@ RSpec.describe Projects::TransferService do project.design_repository end + def clear_design_repo_memoization + project.design_management_repository.clear_memoization(:repository) + project.clear_memoization(:design_repository) + end + it 'does not create a design repository' do expect(subject.execute(group)).to be true - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository.exists?).to be false end @@ -713,7 +739,7 @@ RSpec.describe Projects::TransferService do it 'moves the repository' do expect(subject.execute(group)).to be true - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository).to have_attributes( disk_path: new_full_path, @@ -725,7 +751,7 @@ RSpec.describe Projects::TransferService do allow(subject).to receive(:execute_system_hooks).and_raise('foo') expect { subject.execute(group) }.to raise_error('foo') - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository).to have_attributes( disk_path: old_full_path, @@ -742,7 +768,7 @@ RSpec.describe Projects::TransferService do expect(subject.execute(group)).to be true - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository).to have_attributes( disk_path: old_disk_path, @@ -756,7 +782,7 @@ RSpec.describe Projects::TransferService do allow(subject).to receive(:execute_system_hooks).and_raise('foo') expect { subject.execute(group) }.to raise_error('foo') - project.clear_memoization(:design_repository) + clear_design_repo_memoization expect(design_repository).to have_attributes( disk_path: old_disk_path, diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb index d939a79b7e9..872e38aba1d 100644 --- a/spec/services/projects/unlink_fork_service_spec.rb +++ b/spec/services/projects/unlink_fork_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do +RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching, feature_category: :source_code_management do include ProjectForksHelper subject { described_class.new(forked_project, user) } @@ -116,8 +116,10 @@ RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_cachin expect(project.fork_network_member).to be_nil expect(project.fork_network).to be_nil - expect(forked_project.fork_network).to have_attributes(root_project_id: nil, - deleted_root_project_name: project.full_name) + expect(forked_project.fork_network).to have_attributes( + root_project_id: nil, + deleted_root_project_name: project.full_name + ) expect(project.forked_to_members.count).to eq(0) expect(forked_project.forked_to_members.count).to eq(1) expect(fork_of_fork.forked_to_members.count).to eq(0) diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index d908a169898..a97369c4b08 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -2,19 +2,22 @@ require "spec_helper" -RSpec.describe Projects::UpdatePagesService do +RSpec.describe Projects::UpdatePagesService, feature_category: :pages do let_it_be(:project, refind: true) { create(:project, :repository) } let_it_be(:old_pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) } let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) } - let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') } + let(:options) { {} } + let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD', options: options) } let(:invalid_file) { fixture_file_upload('spec/fixtures/dk.png') } let(:file) { fixture_file_upload("spec/fixtures/pages.zip") } + let(:custom_root_file) { fixture_file_upload("spec/fixtures/pages_with_custom_root.zip") } let(:empty_file) { fixture_file_upload("spec/fixtures/pages_empty.zip") } let(:empty_metadata_filename) { "spec/fixtures/pages_empty.zip.meta" } let(:metadata_filename) { "spec/fixtures/pages.zip.meta" } + let(:custom_root_file_metadata) { "spec/fixtures/pages_with_custom_root.zip.meta" } let(:metadata) { fixture_file_upload(metadata_filename) if File.exist?(metadata_filename) } subject { described_class.new(project, build) } @@ -97,6 +100,7 @@ RSpec.describe Projects::UpdatePagesService do expect(deployment.file_sha256).to eq(artifacts_archive.file_sha256) expect(project.pages_metadatum.reload.pages_deployment_id).to eq(deployment.id) expect(deployment.ci_build_id).to eq(build.id) + expect(deployment.root_directory).to be_nil end it 'does not fail if pages_metadata is absent' do @@ -116,9 +120,11 @@ RSpec.describe Projects::UpdatePagesService do it 'schedules a destruction of older deployments' do expect(DestroyPagesDeploymentsWorker).to( - receive(:perform_in).with(described_class::OLD_DEPLOYMENTS_DESTRUCTION_DELAY, - project.id, - instance_of(Integer)) + receive(:perform_in).with( + described_class::OLD_DEPLOYMENTS_DESTRUCTION_DELAY, + project.id, + instance_of(Integer) + ) ) execute @@ -140,7 +146,45 @@ RSpec.describe Projects::UpdatePagesService do it 'returns an error' do expect(execute).not_to eq(:success) - expect(GenericCommitStatus.last.description).to eq("Error: The `public/` folder is missing, or not declared in `.gitlab-ci.yml`.") + expect(GenericCommitStatus.last.description).to eq("Error: You need to either include a `public/` folder in your artifacts, or specify which one to use for Pages using `publish` in `.gitlab-ci.yml`") + end + end + + context 'when there is a custom root config' do + let(:file) { custom_root_file } + let(:metadata_filename) { custom_root_file_metadata } + + context 'when the directory specified with `publish` is included in the artifacts' do + let(:options) { { publish: 'foo' } } + + it 'creates pages_deployment and saves it in the metadata' do + expect(execute).to eq(:success) + + deployment = project.pages_deployments.last + expect(deployment.root_directory).to eq(options[:publish]) + end + end + + context 'when the directory specified with `publish` is not included in the artifacts' do + let(:options) { { publish: 'bar' } } + + it 'returns an error' do + expect(execute).not_to eq(:success) + + expect(GenericCommitStatus.last.description).to eq("Error: You need to either include a `public/` folder in your artifacts, or specify which one to use for Pages using `publish` in `.gitlab-ci.yml`") + end + end + + context 'when there is a folder named `public`, but `publish` specifies a different one' do + let(:options) { { publish: 'foo' } } + let(:file) { fixture_file_upload("spec/fixtures/pages.zip") } + let(:metadata_filename) { "spec/fixtures/pages.zip.meta" } + + it 'returns an error' do + expect(execute).not_to eq(:success) + + expect(GenericCommitStatus.last.description).to eq("Error: You need to either include a `public/` folder in your artifacts, or specify which one to use for Pages using `publish` in `.gitlab-ci.yml`") + end end end @@ -322,10 +366,14 @@ RSpec.describe Projects::UpdatePagesService do context 'when retrying the job' do let(:stage) { create(:ci_stage, position: 1_000_000, name: 'deploy', pipeline: pipeline) } let!(:older_deploy_job) do - create(:generic_commit_status, :failed, pipeline: pipeline, - ref: build.ref, - ci_stage: stage, - name: 'pages:deploy') + create( + :generic_commit_status, + :failed, + pipeline: pipeline, + ref: build.ref, + ci_stage: stage, + name: 'pages:deploy' + ) end before do diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb index 547641867bc..b65f7a50e4c 100644 --- a/spec/services/projects/update_remote_mirror_service_spec.rb +++ b/spec/services/projects/update_remote_mirror_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::UpdateRemoteMirrorService do +RSpec.describe Projects::UpdateRemoteMirrorService, feature_category: :source_code_management do let_it_be(:project) { create(:project, :repository, lfs_enabled: true) } let_it_be(:remote_project) { create(:forked_project_with_submodules) } let_it_be(:remote_mirror) { create(:remote_mirror, project: project, enabled: true) } diff --git a/spec/services/projects/update_repository_storage_service_spec.rb b/spec/services/projects/update_repository_storage_service_spec.rb index ee8f7fb2ef2..af920d51776 100644 --- a/spec/services/projects/update_repository_storage_service_spec.rb +++ b/spec/services/projects/update_repository_storage_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::UpdateRepositoryStorageService do +RSpec.describe Projects::UpdateRepositoryStorageService, feature_category: :source_code_management do include Gitlab::ShellAdapter subject { described_class.new(repository_storage_move) } diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 3cda6bc2627..8f55ee705ab 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Projects::UpdateService do +RSpec.describe Projects::UpdateService, feature_category: :projects do include ExternalAuthorizationServiceHelpers include ProjectForksHelper @@ -227,48 +227,16 @@ RSpec.describe Projects::UpdateService do let(:user) { project.first_owner } let(:forked_project) { fork_project(project) } - context 'and unlink forks feature flag is off' do - before do - stub_feature_flags(unlink_fork_network_upon_visibility_decrease: false) - end - - it 'updates forks visibility level when parent set to more restrictive' do - opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE } - - expect(project).to be_internal - expect(forked_project).to be_internal - - expect(update_project(project, user, opts)).to eq({ status: :success }) + it 'does not change visibility of forks' do + opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE } - expect(project).to be_private - expect(forked_project.reload).to be_private - end - - it 'does not update forks visibility level when parent set to less restrictive' do - opts = { visibility_level: Gitlab::VisibilityLevel::PUBLIC } - - expect(project).to be_internal - expect(forked_project).to be_internal + expect(project).to be_internal + expect(forked_project).to be_internal - expect(update_project(project, user, opts)).to eq({ status: :success }) + expect(update_project(project, user, opts)).to eq({ status: :success }) - expect(project).to be_public - expect(forked_project.reload).to be_internal - end - end - - context 'and unlink forks feature flag is on' do - it 'does not change visibility of forks' do - opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE } - - expect(project).to be_internal - expect(forked_project).to be_internal - - expect(update_project(project, user, opts)).to eq({ status: :success }) - - expect(project).to be_private - expect(forked_project.reload).to be_internal - end + expect(project).to be_private + expect(forked_project.reload).to be_internal end end @@ -358,7 +326,9 @@ RSpec.describe Projects::UpdateService do it 'logs an error and creates a metric when wiki can not be created' do project.project_feature.update!(wiki_access_level: ProjectFeature::DISABLED) - expect_any_instance_of(ProjectWiki).to receive(:create_wiki_repository).and_raise(Wiki::CouldNotCreateWikiError) + expect_next_instance_of(ProjectWiki) do |project_wiki| + expect(project_wiki).to receive(:create_wiki_repository).and_raise(Wiki::CouldNotCreateWikiError) + end expect_any_instance_of(described_class).to receive(:log_error).with("Could not create wiki for #{project.full_name}") counter = double(:counter) @@ -387,24 +357,26 @@ RSpec.describe Projects::UpdateService do # Using some sample features for testing. # Not using all the features because some of them must be enabled/disabled together %w[issues wiki forking].each do |feature_name| - let(:feature) { "#{feature_name}_access_level" } - let(:params) do - { project_feature_attributes: { feature => ProjectFeature::ENABLED } } - end + context "with feature_name:#{feature_name}" do + let(:feature) { "#{feature_name}_access_level" } + let(:params) do + { project_feature_attributes: { feature => ProjectFeature::ENABLED } } + end - before do - project.project_feature.update!(feature => ProjectFeature::DISABLED) - end + before do + project.project_feature.update!(feature => ProjectFeature::DISABLED) + end - it 'publishes Projects::ProjectFeaturesChangedEvent' do - expect { update_project(project, user, params) } - .to publish_event(Projects::ProjectFeaturesChangedEvent) - .with( - project_id: project.id, - namespace_id: project.namespace_id, - root_namespace_id: project.root_namespace.id, - features: ["updated_at", feature] - ) + it 'publishes Projects::ProjectFeaturesChangedEvent' do + expect { update_project(project, user, params) } + .to publish_event(Projects::ProjectFeaturesChangedEvent) + .with( + project_id: project.id, + namespace_id: project.namespace_id, + root_namespace_id: project.root_namespace.id, + features: array_including(feature, "updated_at") + ) + end end end end @@ -548,6 +520,25 @@ RSpec.describe Projects::UpdateService do end end + context 'when updating #runner_registration_enabled' do + it 'updates the attribute' do + expect { update_project(project, user, runner_registration_enabled: false) } + .to change { project.runner_registration_enabled } + .to(false) + end + + context 'when runner registration is disabled for all projects' do + before do + stub_application_setting(valid_runner_registrars: []) + end + + it 'restricts updating the attribute' do + expect { update_project(project, user, runner_registration_enabled: false) } + .not_to change { project.runner_registration_enabled } + end + end + end + context 'when updating runners settings' do let(:settings) do { instance_runners_enabled: true, namespace_traversal_ids: [123] } @@ -653,17 +644,19 @@ RSpec.describe Projects::UpdateService do context 'when updating nested attributes for prometheus integration' do context 'prometheus integration exists' do let(:prometheus_integration_attributes) do - attributes_for(:prometheus_integration, - project: project, - properties: { api_url: "http://new.prometheus.com", manual_configuration: "0" } - ) + attributes_for( + :prometheus_integration, + project: project, + properties: { api_url: "http://new.prometheus.com", manual_configuration: "0" } + ) end let!(:prometheus_integration) do - create(:prometheus_integration, - project: project, - properties: { api_url: "http://old.prometheus.com", manual_configuration: "0" } - ) + create( + :prometheus_integration, + project: project, + properties: { api_url: "http://old.prometheus.com", manual_configuration: "0" } + ) end it 'updates existing record' do @@ -677,10 +670,11 @@ RSpec.describe Projects::UpdateService do context 'prometheus integration does not exist' do context 'valid parameters' do let(:prometheus_integration_attributes) do - attributes_for(:prometheus_integration, - project: project, - properties: { api_url: "http://example.prometheus.com", manual_configuration: "0" } - ) + attributes_for( + :prometheus_integration, + project: project, + properties: { api_url: "http://example.prometheus.com", manual_configuration: "0" } + ) end it 'creates new record' do @@ -693,10 +687,11 @@ RSpec.describe Projects::UpdateService do context 'invalid parameters' do let(:prometheus_integration_attributes) do - attributes_for(:prometheus_integration, - project: project, - properties: { api_url: nil, manual_configuration: "1" } - ) + attributes_for( + :prometheus_integration, + project: project, + properties: { api_url: nil, manual_configuration: "1" } + ) end it 'does not create new record' do @@ -794,6 +789,112 @@ RSpec.describe Projects::UpdateService do expect(project.topic_list).to eq(%w[tag_list]) end end + + describe 'when updating pages unique domain', feature_category: :pages do + let(:group) { create(:group, path: 'group') } + let(:project) { create(:project, path: 'project', group: group) } + + context 'with pages_unique_domain feature flag disabled' do + before do + stub_feature_flags(pages_unique_domain: false) + end + + it 'does not change pages unique domain' do + expect(project) + .to receive(:update) + .with({ project_setting_attributes: { has_confluence: true } }) + .and_call_original + + expect do + update_project(project, user, project_setting_attributes: { + has_confluence: true, + pages_unique_domain_enabled: true + }) + end.not_to change { project.project_setting.pages_unique_domain_enabled } + end + + it 'does not remove other attributes' do + expect(project) + .to receive(:update) + .with({ name: 'True' }) + .and_call_original + + update_project(project, user, name: 'True') + end + end + + context 'with pages_unique_domain feature flag enabled' do + before do + stub_feature_flags(pages_unique_domain: true) + end + + it 'updates project pages unique domain' do + expect do + update_project(project, user, project_setting_attributes: { + pages_unique_domain_enabled: true + }) + end.to change { project.project_setting.pages_unique_domain_enabled } + + expect(project.project_setting.pages_unique_domain_enabled).to eq true + expect(project.project_setting.pages_unique_domain).to match %r{project-group-\w+} + end + + it 'does not changes unique domain when it already exists' do + project.project_setting.update!( + pages_unique_domain_enabled: false, + pages_unique_domain: 'unique-domain' + ) + + expect do + update_project(project, user, project_setting_attributes: { + pages_unique_domain_enabled: true + }) + end.to change { project.project_setting.pages_unique_domain_enabled } + + expect(project.project_setting.pages_unique_domain_enabled).to eq true + expect(project.project_setting.pages_unique_domain).to eq 'unique-domain' + end + + it 'does not changes unique domain when it disabling unique domain' do + project.project_setting.update!( + pages_unique_domain_enabled: true, + pages_unique_domain: 'unique-domain' + ) + + expect do + update_project(project, user, project_setting_attributes: { + pages_unique_domain_enabled: false + }) + end.not_to change { project.project_setting.pages_unique_domain } + + expect(project.project_setting.pages_unique_domain_enabled).to eq false + expect(project.project_setting.pages_unique_domain).to eq 'unique-domain' + end + + context 'when there is another project with the unique domain' do + it 'fails pages unique domain already exists' do + create( + :project_setting, + pages_unique_domain_enabled: true, + pages_unique_domain: 'unique-domain' + ) + + allow(Gitlab::Pages::RandomDomain) + .to receive(:generate) + .and_return('unique-domain') + + result = update_project(project, user, project_setting_attributes: { + pages_unique_domain_enabled: true + }) + + expect(result).to eq( + status: :error, + message: 'Project setting pages unique domain has already been taken' + ) + end + end + end + end end describe '#run_auto_devops_pipeline?' do diff --git a/spec/services/projects/update_statistics_service_spec.rb b/spec/services/projects/update_statistics_service_spec.rb index 1cc69e7e2fe..f685b86acc0 100644 --- a/spec/services/projects/update_statistics_service_spec.rb +++ b/spec/services/projects/update_statistics_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::UpdateStatisticsService do +RSpec.describe Projects::UpdateStatisticsService, feature_category: :projects do using RSpec::Parameterized::TableSyntax let(:service) { described_class.new(project, nil, statistics: statistics) } @@ -27,7 +27,7 @@ RSpec.describe Projects::UpdateStatisticsService do ['repository_size'] | [:size] [:repository_size] | [:size] [:lfs_objects_size] | nil - [:commit_count] | [:commit_count] # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + [:commit_count] | [:commit_count] [:repository_size, :commit_count] | %i(size commit_count) [:repository_size, :commit_count, :lfs_objects_size] | %i(size commit_count) end diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb index b78683cace7..f71662f62ad 100644 --- a/spec/services/prometheus/proxy_service_spec.rb +++ b/spec/services/prometheus/proxy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Prometheus::ProxyService do +RSpec.describe Prometheus::ProxyService, feature_category: :metrics do include ReactiveCachingHelpers let_it_be(:project) { create(:project) } diff --git a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb index d8c1fdffb98..fbee4b9c7d7 100644 --- a/spec/services/prometheus/proxy_variable_substitution_service_spec.rb +++ b/spec/services/prometheus/proxy_variable_substitution_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Prometheus::ProxyVariableSubstitutionService do +RSpec.describe Prometheus::ProxyVariableSubstitutionService, feature_category: :metrics do describe '#execute' do let_it_be(:environment) { create(:environment) } diff --git a/spec/services/protected_branches/api_service_spec.rb b/spec/services/protected_branches/api_service_spec.rb index c98e253267b..f7f5f451a49 100644 --- a/spec/services/protected_branches/api_service_spec.rb +++ b/spec/services/protected_branches/api_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProtectedBranches::ApiService do +RSpec.describe ProtectedBranches::ApiService, feature_category: :compliance_management do shared_examples 'execute with entity' do it 'creates a protected branch with prefilled defaults' do expect(::ProtectedBranches::CreateService).to receive(:new).with( diff --git a/spec/services/protected_branches/cache_service_spec.rb b/spec/services/protected_branches/cache_service_spec.rb index ea434922661..0abf8a673f9 100644 --- a/spec/services/protected_branches/cache_service_spec.rb +++ b/spec/services/protected_branches/cache_service_spec.rb @@ -3,7 +3,7 @@ # require 'spec_helper' -RSpec.describe ProtectedBranches::CacheService, :clean_gitlab_redis_cache do +RSpec.describe ProtectedBranches::CacheService, :clean_gitlab_redis_cache, feature_category: :compliance_management do shared_examples 'execute with entity' do subject(:service) { described_class.new(entity, user) } @@ -145,6 +145,7 @@ RSpec.describe ProtectedBranches::CacheService, :clean_gitlab_redis_cache do context 'when feature flag disabled' do before do stub_feature_flags(group_protected_branches: false) + stub_feature_flags(allow_protected_branches_for_group: false) end it_behaves_like 'execute with entity' diff --git a/spec/services/protected_branches/destroy_service_spec.rb b/spec/services/protected_branches/destroy_service_spec.rb index 421d4aae5bb..e02b4475c02 100644 --- a/spec/services/protected_branches/destroy_service_spec.rb +++ b/spec/services/protected_branches/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProtectedBranches::DestroyService do +RSpec.describe ProtectedBranches::DestroyService, feature_category: :compliance_management do shared_examples 'execute with entity' do subject(:service) { described_class.new(entity, user) } diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb index c70cc032a6a..8b11604aa15 100644 --- a/spec/services/protected_branches/update_service_spec.rb +++ b/spec/services/protected_branches/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProtectedBranches::UpdateService do +RSpec.describe ProtectedBranches::UpdateService, feature_category: :compliance_management do shared_examples 'execute with entity' do let(:params) { { name: new_name } } diff --git a/spec/services/protected_tags/create_service_spec.rb b/spec/services/protected_tags/create_service_spec.rb index a0b99b595e3..78b14aae029 100644 --- a/spec/services/protected_tags/create_service_spec.rb +++ b/spec/services/protected_tags/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProtectedTags::CreateService do +RSpec.describe ProtectedTags::CreateService, feature_category: :compliance_management do let(:project) { create(:project) } let(:user) { project.first_owner } let(:params) do diff --git a/spec/services/protected_tags/destroy_service_spec.rb b/spec/services/protected_tags/destroy_service_spec.rb index 658a4f5557e..fcb30d39520 100644 --- a/spec/services/protected_tags/destroy_service_spec.rb +++ b/spec/services/protected_tags/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProtectedTags::DestroyService do +RSpec.describe ProtectedTags::DestroyService, feature_category: :compliance_management do let(:protected_tag) { create(:protected_tag) } let(:project) { protected_tag.project } let(:user) { project.first_owner } diff --git a/spec/services/protected_tags/update_service_spec.rb b/spec/services/protected_tags/update_service_spec.rb index 4b6e726bb6e..2fb6cf84719 100644 --- a/spec/services/protected_tags/update_service_spec.rb +++ b/spec/services/protected_tags/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProtectedTags::UpdateService do +RSpec.describe ProtectedTags::UpdateService, feature_category: :compliance_management do let(:protected_tag) { create(:protected_tag) } let(:project) { protected_tag.project } let(:user) { project.first_owner } diff --git a/spec/services/push_event_payload_service_spec.rb b/spec/services/push_event_payload_service_spec.rb index de2bec21a3c..50da5ca9b24 100644 --- a/spec/services/push_event_payload_service_spec.rb +++ b/spec/services/push_event_payload_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PushEventPayloadService do +RSpec.describe PushEventPayloadService, feature_category: :source_code_management do let(:event) { create(:push_event) } describe '#execute' do diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 257e7eb972b..966782aca98 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -746,10 +746,10 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning it 'assigns to users with escaped underscores' do user = create(:user) base = user.username - user.update!(username: "#{base}_") + user.update!(username: "#{base}_new") issuable.project.add_developer(user) - cmd = "/assign @#{base}\\_" + cmd = "/assign @#{base}\\_new" _, updates, _ = service.execute(cmd, issuable) @@ -1399,34 +1399,11 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning let(:issuable) { issue } end - # /draft is a toggle (ff disabled) - it_behaves_like 'draft command' do - let(:content) { '/draft' } - let(:issuable) { merge_request } - - before do - stub_feature_flags(draft_quick_action_non_toggle: false) - end - end - - # /draft is a toggle (ff disabled) - it_behaves_like 'ready command' do - let(:content) { '/draft' } - let(:issuable) { merge_request } - - before do - stub_feature_flags(draft_quick_action_non_toggle: false) - issuable.update!(title: issuable.draft_title) - end - end - - # /draft is one way (ff enabled) it_behaves_like 'draft command' do let(:content) { '/draft' } let(:issuable) { merge_request } end - # /draft is one way (ff enabled) it_behaves_like 'draft/ready command no action' do let(:content) { '/draft' } let(:issuable) { merge_request } @@ -2150,116 +2127,8 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning end end - context 'relate command' do - let_it_be_with_refind(:group) { create(:group) } - - shared_examples 'relate command' do - it 'relates issues' do - service.execute(content, issue) - - expect(IssueLink.where(source: issue).map(&:target)).to match_array(issues_related) - end - end - - context 'user is member of group' do - before do - group.add_developer(developer) - end - - context 'relate a single issue' do - let(:other_issue) { create(:issue, project: project) } - let(:issues_related) { [other_issue] } - let(:content) { "/relate #{other_issue.to_reference}" } - - it_behaves_like 'relate command' - end - - context 'relate multiple issues at once' do - let(:second_issue) { create(:issue, project: project) } - let(:third_issue) { create(:issue, project: project) } - let(:issues_related) { [second_issue, third_issue] } - let(:content) { "/relate #{second_issue.to_reference} #{third_issue.to_reference}" } - - it_behaves_like 'relate command' - end - - context 'when quick action target is unpersisted' do - let(:issue) { build(:issue, project: project) } - let(:other_issue) { create(:issue, project: project) } - let(:issues_related) { [other_issue] } - let(:content) { "/relate #{other_issue.to_reference}" } - - it 'relates the issues after the issue is persisted' do - service.execute(content, issue) - - issue.save! - - expect(IssueLink.where(source: issue).map(&:target)).to match_array(issues_related) - end - end - - context 'empty relate command' do - let(:issues_related) { [] } - let(:content) { '/relate' } - - it_behaves_like 'relate command' - end - - context 'already having related issues' do - let(:second_issue) { create(:issue, project: project) } - let(:third_issue) { create(:issue, project: project) } - let(:issues_related) { [second_issue, third_issue] } - let(:content) { "/relate #{third_issue.to_reference(project)}" } - - before do - create(:issue_link, source: issue, target: second_issue) - end - - it_behaves_like 'relate command' - end - - context 'cross project' do - let(:another_group) { create(:group, :public) } - let(:other_project) { create(:project, group: another_group) } - - before do - another_group.add_developer(developer) - end - - context 'relate a cross project issue' do - let(:other_issue) { create(:issue, project: other_project) } - let(:issues_related) { [other_issue] } - let(:content) { "/relate #{other_issue.to_reference(project)}" } - - it_behaves_like 'relate command' - end - - context 'relate multiple cross projects issues at once' do - let(:second_issue) { create(:issue, project: other_project) } - let(:third_issue) { create(:issue, project: other_project) } - let(:issues_related) { [second_issue, third_issue] } - let(:content) { "/relate #{second_issue.to_reference(project)} #{third_issue.to_reference(project)}" } - - it_behaves_like 'relate command' - end - - context 'relate a non-existing issue' do - let(:issues_related) { [] } - let(:content) { "/relate imaginary##{non_existing_record_iid}" } - - it_behaves_like 'relate command' - end - - context 'relate a private issue' do - let(:private_project) { create(:project, :private) } - let(:other_issue) { create(:issue, project: private_project) } - let(:issues_related) { [] } - let(:content) { "/relate #{other_issue.to_reference(project)}" } - - it_behaves_like 'relate command' - end - end - end + it_behaves_like 'issues link quick action', :relate do + let(:user) { developer } end context 'invite_email command' do @@ -2507,6 +2376,55 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning expect(message).to eq("Added ~\"Bug\" label.") end end + + describe 'type command' do + let_it_be(:project) { create(:project, :private) } + let_it_be(:work_item) { create(:work_item, project: project) } + + let(:command) { '/type Task' } + + context 'when user has sufficient permissions to create new type' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(current_user, :create_task, work_item).and_return(true) + end + + it 'populates :issue_type: and :work_item_type' do + _, updates, message = service.execute(command, work_item) + + expect(message).to eq(_('Type changed successfully.')) + expect(updates).to eq({ issue_type: 'task', work_item_type: WorkItems::Type.default_by_type(:task) }) + end + + it 'returns error with an invalid type' do + _, updates, message = service.execute('/type foo', work_item) + + expect(message).to eq(_("Failed to convert this work item: Provided type is not supported.")) + expect(updates).to eq({}) + end + + it 'returns error with same type' do + _, updates, message = service.execute('/type Issue', work_item) + + expect(message).to eq(_("Failed to convert this work item: Types are the same.")) + expect(updates).to eq({}) + end + end + + context 'when user has insufficient permissions to create new type' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(current_user, :create_task, work_item).and_return(false) + end + + it 'returns error' do + _, updates, message = service.execute(command, work_item) + + expect(message).to eq(_("Failed to convert this work item: You have insufficient permissions.")) + expect(updates).to eq({}) + end + end + end end describe '#explain' do @@ -2517,8 +2435,9 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning let(:content) { '/close' } it 'includes issuable name' do - _, explanations = service.explain(content, issue) + content_result, explanations = service.explain(content, issue) + expect(content_result).to eq('') expect(explanations).to eq(['Closes this issue.']) end end @@ -2704,27 +2623,6 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning end end - describe 'draft command toggle (deprecated)' do - let(:content) { '/draft' } - - before do - stub_feature_flags(draft_quick_action_non_toggle: false) - end - - it 'includes the new status' do - _, explanations = service.explain(content, merge_request) - - expect(explanations).to match_array(['Marks this merge request as a draft.']) - end - - it 'sets the ready status on a draft' do - merge_request.update!(title: merge_request.draft_title) - _, explanations = service.explain(content, merge_request) - - expect(explanations).to match_array(["Marks this merge request as ready."]) - end - end - describe 'draft command set' do let(:content) { '/draft' } @@ -2946,6 +2844,61 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning end end end + + context 'with keep_actions' do + let(:content) { '/close' } + + it 'keeps quick actions' do + content_result, explanations = service.explain(content, issue, keep_actions: true) + + expect(content_result).to eq("\n/close") + expect(explanations).to eq(['Closes this issue.']) + end + + it 'removes the quick action' do + content_result, explanations = service.explain(content, issue, keep_actions: false) + + expect(content_result).to eq('') + expect(explanations).to eq(['Closes this issue.']) + end + end + + describe 'type command' do + let_it_be(:project) { create(:project, :private) } + let_it_be(:work_item) { create(:work_item, :task, project: project) } + + 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.") + end + + context 'when feature flag work_items_mvc_2 is disabled' do + before do + stub_feature_flags(work_items_mvc_2: false) + end + + it 'does not have the command available' do + _, explanations = service.explain(command, work_item) + + expect(explanations).to be_empty + end + end + end + + describe 'relate command' do + let_it_be(:other_issue) { create(:issue, project: project) } + let(:content) { "/relate #{other_issue.to_reference}" } + + it 'includes explain message' do + _, explanations = service.explain(content, issue) + + expect(explanations).to eq(["Marks this issue as related to #{other_issue.to_reference}."]) + end + end end describe '#available_commands' do diff --git a/spec/services/quick_actions/target_service_spec.rb b/spec/services/quick_actions/target_service_spec.rb index 1b0a5d4ae73..5f4e92cf955 100644 --- a/spec/services/quick_actions/target_service_spec.rb +++ b/spec/services/quick_actions/target_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe QuickActions::TargetService do +RSpec.describe QuickActions::TargetService, feature_category: :team_planning do let(:project) { create(:project) } let(:user) { create(:user) } let(:service) { described_class.new(project, user) } diff --git a/spec/services/releases/create_evidence_service_spec.rb b/spec/services/releases/create_evidence_service_spec.rb index 0ac15a7291d..75d0a2b9c0e 100644 --- a/spec/services/releases/create_evidence_service_spec.rb +++ b/spec/services/releases/create_evidence_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Releases::CreateEvidenceService do +RSpec.describe Releases::CreateEvidenceService, feature_category: :release_orchestration do let_it_be(:project) { create(:project) } let(:release) { create(:release, project: project) } diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb index 9768ceb12e8..ca5dd912e77 100644 --- a/spec/services/releases/create_service_spec.rb +++ b/spec/services/releases/create_service_spec.rb @@ -55,6 +55,26 @@ RSpec.describe Releases::CreateService, feature_category: :continuous_integratio end end + context 'when project is a catalog resource' do + let(:ref) { 'master' } + let!(:catalog_resource) { create(:catalog_resource, project: project) } + + context 'and it is valid' do + let_it_be(:project) { create(:project, :repository, description: 'our components') } + + it_behaves_like 'a successful release creation' + end + + context 'and it is invalid' do + it 'raises an error and does not update the release' do + result = service.execute + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Project must have a description') + end + end + end + context 'when ref is provided' do let(:ref) { 'master' } let(:tag_name) { 'foobar' } diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb index 46550ac5bef..953490ac379 100644 --- a/spec/services/releases/destroy_service_spec.rb +++ b/spec/services/releases/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Releases::DestroyService do +RSpec.describe Releases::DestroyService, feature_category: :release_orchestration do let(:project) { create(:project, :repository) } let(:mainatiner) { create(:user) } let(:repoter) { create(:user) } diff --git a/spec/services/releases/links/create_service_spec.rb b/spec/services/releases/links/create_service_spec.rb new file mode 100644 index 00000000000..9928d2162d7 --- /dev/null +++ b/spec/services/releases/links/create_service_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Releases::Links::CreateService, feature_category: :release_orchestration do + let(:service) { described_class.new(release, user, params) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + let_it_be(:release) { create(:release, project: project, author: user, tag: 'v1.1.0') } + + let(:params) { { name: name, url: url, direct_asset_path: direct_asset_path, link_type: link_type } } + let(:name) { 'link' } + let(:url) { 'https://example.com' } + let(:direct_asset_path) { '/path' } + let(:link_type) { 'other' } + + before do + project.add_developer(user) + end + + describe '#execute' do + subject(:execute) { service.execute } + + let(:link) { subject.payload[:link] } + + it 'successfully creates a release link' do + expect { execute }.to change { Releases::Link.count }.by(1) + + expect(link).to have_attributes( + name: name, + url: url, + filepath: direct_asset_path, + link_type: link_type + ) + end + + context 'when user does not have access to create release link' do + before do + project.add_guest(user) + end + + it 'returns an error' do + expect { execute }.not_to change { Releases::Link.count } + + is_expected.to be_error + expect(execute.message).to include('Access Denied') + expect(execute.reason).to eq(:forbidden) + end + end + + context 'when url is invalid' do + let(:url) { 'not_a_url' } + + it 'returns an error' do + expect { execute }.not_to change { Releases::Link.count } + + is_expected.to be_error + expect(execute.message[0]).to include('Url is blocked') + expect(execute.reason).to eq(:bad_request) + end + end + + context 'when both direct_asset_path and filepath are provided' do + let(:params) { super().merge(filepath: '/filepath') } + + it 'prefers direct_asset_path' do + is_expected.to be_success + + expect(link.filepath).to eq(direct_asset_path) + end + end + + context 'when only filepath is set' do + let(:params) { super().merge(filepath: '/filepath') } + let(:direct_asset_path) { nil } + + it 'uses filepath' do + is_expected.to be_success + + expect(link.filepath).to eq('/filepath') + end + end + end +end diff --git a/spec/services/releases/links/destroy_service_spec.rb b/spec/services/releases/links/destroy_service_spec.rb new file mode 100644 index 00000000000..a248932eada --- /dev/null +++ b/spec/services/releases/links/destroy_service_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Releases::Links::DestroyService, feature_category: :release_orchestration do + let(:service) { described_class.new(release, user, {}) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + let_it_be(:release) { create(:release, project: project, author: user, tag: 'v1.1.0') } + + let!(:release_link) do + create( + :release_link, + release: release, + name: 'awesome-app.dmg', + url: 'https://example.com/download/awesome-app.dmg' + ) + end + + before do + project.add_developer(user) + end + + describe '#execute' do + subject(:execute) { service.execute(release_link) } + + it 'successfully deletes a release link' do + expect { execute }.to change { release.links.count }.by(-1) + + is_expected.to be_success + end + + context 'when user does not have access to delete release link' do + before do + project.add_guest(user) + end + + it 'returns an error' do + expect { execute }.not_to change { release.links.count } + + is_expected.to be_error + expect(execute.message).to include('Access Denied') + expect(execute.reason).to eq(:forbidden) + end + end + + context 'when release link does not exist' do + let(:release_link) { nil } + + it 'returns an error' do + expect { execute }.not_to change { release.links.count } + + is_expected.to be_error + expect(execute.message).to eq('Link does not exist') + expect(execute.reason).to eq(:not_found) + end + end + + context 'when release link deletion failed' do + before do + allow(release_link).to receive(:destroy).and_return(false) + end + + it 'returns an error' do + expect { execute }.not_to change { release.links.count } + + is_expected.to be_error + expect(execute.reason).to eq(:bad_request) + end + end + end +end diff --git a/spec/services/releases/links/update_service_spec.rb b/spec/services/releases/links/update_service_spec.rb new file mode 100644 index 00000000000..3f48985cf60 --- /dev/null +++ b/spec/services/releases/links/update_service_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Releases::Links::UpdateService, feature_category: :release_orchestration do + let(:service) { described_class.new(release, user, params) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + let_it_be(:release) { create(:release, project: project, author: user, tag: 'v1.1.0') } + + let(:release_link) do + create( + :release_link, + release: release, + name: 'awesome-app.dmg', + url: 'https://example.com/download/awesome-app.dmg' + ) + end + + let(:params) { { name: name, url: url, direct_asset_path: direct_asset_path, link_type: link_type } } + let(:name) { 'link' } + let(:url) { 'https://example.com' } + let(:direct_asset_path) { '/path' } + let(:link_type) { 'other' } + + before do + project.add_developer(user) + end + + describe '#execute' do + subject(:execute) { service.execute(release_link) } + + let(:updated_link) { execute.payload[:link] } + + it 'successfully updates a release link' do + is_expected.to be_success + + expect(updated_link).to have_attributes( + name: name, + url: url, + filepath: direct_asset_path, + link_type: link_type + ) + end + + context 'when user does not have access to update release link' do + before do + project.add_guest(user) + end + + it 'returns an error' do + is_expected.to be_error + expect(execute.message).to include('Access Denied') + expect(execute.reason).to eq(:forbidden) + end + end + + context 'when url is invalid' do + let(:url) { 'not_a_url' } + + it 'returns an error' do + is_expected.to be_error + expect(execute.message[0]).to include('Url is blocked') + expect(execute.reason).to eq(:bad_request) + end + end + + context 'when both direct_asset_path and filepath are provided' do + let(:params) { super().merge(filepath: '/filepath') } + + it 'prefers direct_asset_path' do + is_expected.to be_success + + expect(updated_link.filepath).to eq(direct_asset_path) + end + end + + context 'when only filepath is set' do + let(:params) { super().merge(filepath: '/filepath') } + let(:direct_asset_path) { nil } + + it 'uses filepath' do + is_expected.to be_success + + expect(updated_link.filepath).to eq('/filepath') + end + end + end +end diff --git a/spec/services/repositories/changelog_service_spec.rb b/spec/services/repositories/changelog_service_spec.rb index 42b586637ad..1b5300672e3 100644 --- a/spec/services/repositories/changelog_service_spec.rb +++ b/spec/services/repositories/changelog_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Repositories::ChangelogService do +RSpec.describe Repositories::ChangelogService, feature_category: :source_code_management do describe '#execute' do let!(:project) { create(:project, :empty_repo) } let!(:creator) { project.creator } diff --git a/spec/services/repositories/destroy_service_spec.rb b/spec/services/repositories/destroy_service_spec.rb index 565a18d501a..b3bad4fd84d 100644 --- a/spec/services/repositories/destroy_service_spec.rb +++ b/spec/services/repositories/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Repositories::DestroyService do +RSpec.describe Repositories::DestroyService, feature_category: :source_code_management do let_it_be(:user) { create(:user) } let!(:project) { create(:project, :repository, namespace: user.namespace) } diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb index 8db1a6858fa..1ce68080c73 100644 --- a/spec/services/repository_archive_clean_up_service_spec.rb +++ b/spec/services/repository_archive_clean_up_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe RepositoryArchiveCleanUpService do +RSpec.describe RepositoryArchiveCleanUpService, feature_category: :source_code_management do subject(:service) { described_class.new } describe '#execute (new archive locations)' do diff --git a/spec/services/reset_project_cache_service_spec.rb b/spec/services/reset_project_cache_service_spec.rb index 165b38ee338..6ae516a5f07 100644 --- a/spec/services/reset_project_cache_service_spec.rb +++ b/spec/services/reset_project_cache_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ResetProjectCacheService do +RSpec.describe ResetProjectCacheService, feature_category: :projects do let(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb index a8c8d41ca09..59d582f038a 100644 --- a/spec/services/resource_access_tokens/create_service_spec.rb +++ b/spec/services/resource_access_tokens/create_service_spec.rb @@ -2,13 +2,16 @@ require 'spec_helper' -RSpec.describe ResourceAccessTokens::CreateService do +RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_access do subject { described_class.new(user, resource, params).execute } let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :private) } let_it_be(:group) { create(:group, :private) } let_it_be(:params) { {} } + let_it_be(:max_pat_access_token_lifetime) do + PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now.to_date.freeze + end before do stub_config_setting(host: 'example.com') @@ -99,12 +102,20 @@ RSpec.describe ResourceAccessTokens::CreateService do end end - context 'bot email' do - it 'check email domain' do - response = subject - access_token = response.payload[:access_token] + context 'bot username and email' do + include_examples 'username and email pair is generated by Gitlab::Utils::UsernameAndEmailGenerator' do + subject do + response = described_class.new(user, resource, params).execute + response.payload[:access_token].user + end - expect(access_token.user.email).to end_with("@noreply.#{Gitlab.config.gitlab.host}") + let(:username_prefix) do + "#{resource.class.name.downcase}_#{resource.id}_bot" + end + + let(:email_domain) do + "noreply.#{Gitlab.config.gitlab.host}" + end end end @@ -119,9 +130,7 @@ RSpec.describe ResourceAccessTokens::CreateService do end end - context 'when user specifies an access level' do - let_it_be(:params) { { access_level: Gitlab::Access::DEVELOPER } } - + shared_examples 'bot with access level' do it 'adds the bot user with the specified access level in the resource' do response = subject access_token = response.payload[:access_token] @@ -131,6 +140,18 @@ RSpec.describe ResourceAccessTokens::CreateService do end end + context 'when user specifies an access level' do + let_it_be(:params) { { access_level: Gitlab::Access::DEVELOPER } } + + it_behaves_like 'bot with access level' + end + + context 'with DEVELOPER access_level, in string format' do + let_it_be(:params) { { access_level: Gitlab::Access::DEVELOPER.to_s } } + + it_behaves_like 'bot with access level' + end + context 'when user is external' do before do user.update!(external: true) @@ -167,20 +188,51 @@ RSpec.describe ResourceAccessTokens::CreateService do context 'expires_at' do context 'when no expiration value is passed' do - it 'uses nil expiration value' do - response = subject - access_token = response.payload[:access_token] + context 'when default_pat_expiration feature flag is true' do + it 'defaults to PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS' do + freeze_time do + response = subject + access_token = response.payload[:access_token] + + expect(access_token.expires_at).to eq( + max_pat_access_token_lifetime.to_date + ) + end + end - expect(access_token.expires_at).to eq(nil) + 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 + + expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq( + max_pat_access_token_lifetime.to_date + ) + end + end end - context 'expiry of the project bot member' do - it 'project bot membership does not expire' do + context 'when default_pat_expiration feature flag is false' do + before do + stub_feature_flags(default_pat_expiration: false) + end + + it 'uses nil expiration value' 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(nil) + expect(access_token.expires_at).to eq(nil) + end + + context 'expiry of the project bot member' do + it 'project bot membership 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(nil) + end end end end @@ -201,7 +253,7 @@ RSpec.describe ResourceAccessTokens::CreateService do 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(params[:expires_at]) + expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(access_token.expires_at) end end end @@ -219,17 +271,31 @@ RSpec.describe ResourceAccessTokens::CreateService do let_it_be(:bot_user) { create(:user, :project_bot) } let(:unpersisted_member) { build(:project_member, source: resource, user: bot_user) } - let(:error_message) { 'Could not provision maintainer access to project access token' } + let(:error_message) { 'Could not provision maintainer access to the access token. ERROR: error message' } before do allow_next_instance_of(ResourceAccessTokens::CreateService) do |service| allow(service).to receive(:create_user).and_return(bot_user) allow(service).to receive(:create_membership).and_return(unpersisted_member) end + + allow(unpersisted_member).to receive_message_chain(:errors, :full_messages, :to_sentence) + .and_return('error message') end - it_behaves_like 'token creation fails' - it_behaves_like 'correct error message' + context 'with MAINTAINER access_level, in integer format' do + let_it_be(:params) { { access_level: Gitlab::Access::MAINTAINER } } + + it_behaves_like 'token creation fails' + it_behaves_like 'correct error message' + end + + context 'with MAINTAINER access_level, in string format' do + let_it_be(:params) { { access_level: Gitlab::Access::MAINTAINER.to_s } } + + it_behaves_like 'token creation fails' + it_behaves_like 'correct error message' + end end end diff --git a/spec/services/resource_access_tokens/revoke_service_spec.rb b/spec/services/resource_access_tokens/revoke_service_spec.rb index 28f173f1bc7..c00146961e3 100644 --- a/spec/services/resource_access_tokens/revoke_service_spec.rb +++ b/spec/services/resource_access_tokens/revoke_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ResourceAccessTokens::RevokeService do +RSpec.describe ResourceAccessTokens::RevokeService, feature_category: :system_access do subject { described_class.new(user, resource, access_token).execute } let_it_be(:user) { create(:user) } diff --git a/spec/services/resource_events/change_labels_service_spec.rb b/spec/services/resource_events/change_labels_service_spec.rb index d94b49de9d7..8393ce78df8 100644 --- a/spec/services/resource_events/change_labels_service_spec.rb +++ b/spec/services/resource_events/change_labels_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' # feature category is shared among plan(issues, epics), monitor(incidents), create(merge request) stages -RSpec.describe ResourceEvents::ChangeLabelsService, feature_category: :shared do +RSpec.describe ResourceEvents::ChangeLabelsService, feature_category: :team_planning do let_it_be(:project) { create(:project) } let_it_be(:author) { create(:user) } let_it_be(:issue) { create(:issue, project: project) } diff --git a/spec/services/resource_events/change_milestone_service_spec.rb b/spec/services/resource_events/change_milestone_service_spec.rb index 425d5b19907..077058df1d5 100644 --- a/spec/services/resource_events/change_milestone_service_spec.rb +++ b/spec/services/resource_events/change_milestone_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ResourceEvents::ChangeMilestoneService do +RSpec.describe ResourceEvents::ChangeMilestoneService, feature_category: :team_planning do let_it_be(:timebox) { create(:milestone) } let(:created_at_time) { Time.utc(2019, 12, 30) } diff --git a/spec/services/resource_events/change_state_service_spec.rb b/spec/services/resource_events/change_state_service_spec.rb index b679943073c..a63b4302635 100644 --- a/spec/services/resource_events/change_state_service_spec.rb +++ b/spec/services/resource_events/change_state_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ResourceEvents::ChangeStateService do +RSpec.describe ResourceEvents::ChangeStateService, feature_category: :team_planning do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } diff --git a/spec/services/resource_events/merge_into_notes_service_spec.rb b/spec/services/resource_events/merge_into_notes_service_spec.rb index ebfd942066f..6eb6780d704 100644 --- a/spec/services/resource_events/merge_into_notes_service_spec.rb +++ b/spec/services/resource_events/merge_into_notes_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ResourceEvents::MergeIntoNotesService do +RSpec.describe ResourceEvents::MergeIntoNotesService, feature_category: :team_planning do def create_event(params) event_params = { action: :add, label: label, issue: resource, user: user } 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 71b1d0993ee..3396abaff9e 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 @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ResourceEvents::SyntheticLabelNotesBuilderService do +RSpec.describe ResourceEvents::SyntheticLabelNotesBuilderService, feature_category: :team_planning do describe '#execute' do let_it_be(:user) { create(:user) } diff --git a/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb b/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb index f368e107c60..20537aa3685 100644 --- a/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb +++ b/spec/services/resource_events/synthetic_milestone_notes_builder_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ResourceEvents::SyntheticMilestoneNotesBuilderService do +RSpec.describe ResourceEvents::SyntheticMilestoneNotesBuilderService, feature_category: :team_planning do describe '#execute' do let_it_be(:user) { create(:user) } let_it_be(:issue) { create(:issue, author: user) } @@ -11,7 +11,8 @@ RSpec.describe ResourceEvents::SyntheticMilestoneNotesBuilderService do let_it_be(:events) do [ create(:resource_milestone_event, issue: issue, milestone: milestone, action: :add, created_at: '2020-01-01 04:00'), - create(:resource_milestone_event, issue: issue, milestone: milestone, action: :remove, created_at: '2020-01-02 08:00') + create(:resource_milestone_event, issue: issue, milestone: milestone, action: :remove, created_at: '2020-01-02 08:00'), + create(:resource_milestone_event, issue: issue, milestone: nil, action: :remove, created_at: '2020-01-02 08:00') ] end @@ -22,7 +23,8 @@ RSpec.describe ResourceEvents::SyntheticMilestoneNotesBuilderService do expect(notes.map(&:note)).to eq( [ "changed milestone to %#{milestone.iid}", - 'removed milestone' + "removed milestone %#{milestone.iid}", + "removed milestone " ]) end diff --git a/spec/services/resource_events/synthetic_state_notes_builder_service_spec.rb b/spec/services/resource_events/synthetic_state_notes_builder_service_spec.rb index 79500f3768b..9f838660f92 100644 --- a/spec/services/resource_events/synthetic_state_notes_builder_service_spec.rb +++ b/spec/services/resource_events/synthetic_state_notes_builder_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ResourceEvents::SyntheticStateNotesBuilderService do +RSpec.describe ResourceEvents::SyntheticStateNotesBuilderService, feature_category: :team_planning do describe '#execute' do let_it_be(:user) { create(:user) } diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb index e8716ef4d90..6250d32574f 100644 --- a/spec/services/search/global_service_spec.rb +++ b/spec/services/search/global_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Search::GlobalService do +RSpec.describe Search::GlobalService, feature_category: :global_search do let(:user) { create(:user) } let(:internal_user) { create(:user) } diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb index c9bfa7cb7b4..e8a4a228f8f 100644 --- a/spec/services/search/group_service_spec.rb +++ b/spec/services/search/group_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Search::GroupService do +RSpec.describe Search::GroupService, feature_category: :global_search do shared_examples_for 'group search' do context 'finding projects by name' do let(:user) { create(:user) } diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb index d204f626635..d60b60d28e4 100644 --- a/spec/services/search/snippet_service_spec.rb +++ b/spec/services/search/snippet_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Search::SnippetService do +RSpec.describe Search::SnippetService, feature_category: :global_search do let_it_be(:author) { create(:author) } let_it_be(:project) { create(:project, :public) } diff --git a/spec/services/security/ci_configuration/container_scanning_create_service_spec.rb b/spec/services/security/ci_configuration/container_scanning_create_service_spec.rb index df76750efc8..a56fbb026c1 100644 --- a/spec/services/security/ci_configuration/container_scanning_create_service_spec.rb +++ b/spec/services/security/ci_configuration/container_scanning_create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Security::CiConfiguration::ContainerScanningCreateService, :snowplow do +RSpec.describe Security::CiConfiguration::ContainerScanningCreateService, :snowplow, feature_category: :container_scanning do subject(:result) { described_class.new(project, user).execute } let(:branch_name) { 'set-container-scanning-config-1' } diff --git a/spec/services/security/ci_configuration/dependency_scanning_create_service_spec.rb b/spec/services/security/ci_configuration/dependency_scanning_create_service_spec.rb index 719a2cf24e9..7ac2249642a 100644 --- a/spec/services/security/ci_configuration/dependency_scanning_create_service_spec.rb +++ b/spec/services/security/ci_configuration/dependency_scanning_create_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Security::CiConfiguration::DependencyScanningCreateService, :snowplow, - feature_category: :dependency_scanning do + feature_category: :software_composition_analysis do subject(:result) { described_class.new(project, user).execute } let(:branch_name) { 'set-dependency-scanning-config-1' } diff --git a/spec/services/security/ci_configuration/sast_iac_create_service_spec.rb b/spec/services/security/ci_configuration/sast_iac_create_service_spec.rb index deb10732b37..7f1ad543f7c 100644 --- a/spec/services/security/ci_configuration/sast_iac_create_service_spec.rb +++ b/spec/services/security/ci_configuration/sast_iac_create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Security::CiConfiguration::SastIacCreateService, :snowplow do +RSpec.describe Security::CiConfiguration::SastIacCreateService, :snowplow, feature_category: :static_application_security_testing do subject(:result) { described_class.new(project, user).execute } let(:branch_name) { 'set-sast-iac-config-1' } diff --git a/spec/services/security/ci_configuration/sast_parser_service_spec.rb b/spec/services/security/ci_configuration/sast_parser_service_spec.rb index 9211beb76f8..051bbcd194b 100644 --- a/spec/services/security/ci_configuration/sast_parser_service_spec.rb +++ b/spec/services/security/ci_configuration/sast_parser_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Security::CiConfiguration::SastParserService do +RSpec.describe Security::CiConfiguration::SastParserService, feature_category: :static_application_security_testing do describe '#configuration' do include_context 'read ci configuration for sast enabled project' diff --git a/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb b/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb index c1df3ebdca5..6cbeb219d11 100644 --- a/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb +++ b/spec/services/security/ci_configuration/secret_detection_create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Security::CiConfiguration::SecretDetectionCreateService, :snowplow do +RSpec.describe Security::CiConfiguration::SecretDetectionCreateService, :snowplow, feature_category: :container_scanning do subject(:result) { described_class.new(project, user).execute } let(:branch_name) { 'set-secret-detection-config-1' } diff --git a/spec/services/security/merge_reports_service_spec.rb b/spec/services/security/merge_reports_service_spec.rb index 249f4da5f34..809d0b27c20 100644 --- a/spec/services/security/merge_reports_service_spec.rb +++ b/spec/services/security/merge_reports_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' # rubocop: disable RSpec/MultipleMemoizedHelpers -RSpec.describe Security::MergeReportsService, '#execute' do +RSpec.describe Security::MergeReportsService, '#execute', feature_category: :code_review_workflow do let(:scanner_1) { build(:ci_reports_security_scanner, external_id: 'scanner-1', name: 'Scanner 1') } let(:scanner_2) { build(:ci_reports_security_scanner, external_id: 'scanner-2', name: 'Scanner 2') } let(:scanner_3) { build(:ci_reports_security_scanner, external_id: 'scanner-3', name: 'Scanner 3') } diff --git a/spec/services/serverless/associate_domain_service_spec.rb b/spec/services/serverless/associate_domain_service_spec.rb deleted file mode 100644 index 2f45806589e..00000000000 --- a/spec/services/serverless/associate_domain_service_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Serverless::AssociateDomainService do - let_it_be(:sdc_pages_domain) { create(:pages_domain, :instance_serverless) } - let_it_be(:sdc_cluster) { create(:cluster, :with_installed_helm, :provided_by_gcp) } - let_it_be(:sdc_knative) { create(:clusters_applications_knative, cluster: sdc_cluster) } - let_it_be(:sdc_creator) { create(:user) } - - let(:sdc) do - create(:serverless_domain_cluster, - knative: sdc_knative, - creator: sdc_creator, - pages_domain: sdc_pages_domain) - end - - let(:knative) { sdc.knative } - let(:creator) { sdc.creator } - let(:pages_domain_id) { sdc.pages_domain_id } - - subject { described_class.new(knative, pages_domain_id: pages_domain_id, creator: creator) } - - context 'when the domain is unchanged' do - let(:creator) { create(:user) } - - it 'does not update creator' do - expect { subject.execute }.not_to change { sdc.reload.creator } - end - end - - context 'when domain is changed to nil' do - let_it_be(:creator) { create(:user) } - let_it_be(:pages_domain_id) { nil } - - it 'removes the association between knative and the domain' do - expect { subject.execute }.to change { knative.reload.pages_domain }.from(sdc.pages_domain).to(nil) - end - - it 'does not attempt to update creator' do - expect { subject.execute }.not_to raise_error - end - end - - context 'when a new domain is associated' do - let_it_be(:creator) { create(:user) } - let_it_be(:pages_domain_id) { create(:pages_domain, :instance_serverless).id } - - it 'creates an association with the domain' do - expect { subject.execute }.to change { knative.reload.pages_domain.id } - .from(sdc.pages_domain.id) - .to(pages_domain_id) - end - - it 'updates creator' do - expect { subject.execute }.to change { sdc.reload.creator }.from(sdc.creator).to(creator) - end - end - - context 'when knative is not authorized to use the pages domain' do - let_it_be(:pages_domain_id) { create(:pages_domain).id } - - before do - expect(knative).to receive(:available_domains).and_return(PagesDomain.none) - end - - it 'sets pages_domain_id to nil' do - expect { subject.execute }.to change { knative.reload.pages_domain }.from(sdc.pages_domain).to(nil) - end - end - - describe 'for new knative application' do - let_it_be(:cluster) { create(:cluster, :with_installed_helm, :provided_by_gcp) } - - context 'when knative hostname is nil' do - let(:knative) { build(:clusters_applications_knative, cluster: cluster, hostname: nil) } - - it 'sets hostname to a placeholder value' do - expect { subject.execute }.to change { knative.hostname }.to('example.com') - end - end - - context 'when knative hostname exists' do - let(:knative) { build(:clusters_applications_knative, cluster: cluster, hostname: 'hostname.com') } - - it 'does not change hostname' do - expect { subject.execute }.not_to change { knative.hostname } - 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 72134af1369..342fb2b6b7a 100644 --- a/spec/services/service_desk_settings/update_service_spec.rb +++ b/spec/services/service_desk_settings/update_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe ServiceDeskSettings::UpdateService do +RSpec.describe ServiceDeskSettings::UpdateService, feature_category: :service_desk do describe '#execute' do let_it_be(:settings) { create(:service_desk_setting, outgoing_name: 'original name') } let_it_be(:user) { create(:user) } diff --git a/spec/services/service_ping/submit_service_ping_service_spec.rb b/spec/services/service_ping/submit_service_ping_service_spec.rb index b02f1e84d25..2248febda5c 100644 --- a/spec/services/service_ping/submit_service_ping_service_spec.rb +++ b/spec/services/service_ping/submit_service_ping_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ServicePing::SubmitService do +RSpec.describe ServicePing::SubmitService, feature_category: :service_ping do include StubRequests include UsageDataHelpers diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb index 58dd2fd4c5e..6171ca1a8a6 100644 --- a/spec/services/service_response_spec.rb +++ b/spec/services/service_response_spec.rb @@ -7,7 +7,7 @@ require 're2' require_relative '../../app/services/service_response' require_relative '../../lib/gitlab/error_tracking' -RSpec.describe ServiceResponse do +RSpec.describe ServiceResponse, feature_category: :shared do describe '.success' do it 'creates a successful response without a message' do expect(described_class.success).to be_success diff --git a/spec/services/snippets/bulk_destroy_service_spec.rb b/spec/services/snippets/bulk_destroy_service_spec.rb index 4142aa349e4..208386aee48 100644 --- a/spec/services/snippets/bulk_destroy_service_spec.rb +++ b/spec/services/snippets/bulk_destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Snippets::BulkDestroyService do +RSpec.describe Snippets::BulkDestroyService, feature_category: :source_code_management do let_it_be(:project) { create(:project) } let(:user) { create(:user) } diff --git a/spec/services/snippets/count_service_spec.rb b/spec/services/snippets/count_service_spec.rb index 5ce637d0bac..4ad9b07d518 100644 --- a/spec/services/snippets/count_service_spec.rb +++ b/spec/services/snippets/count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Snippets::CountService do +RSpec.describe Snippets::CountService, feature_category: :source_code_management do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public) } diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb index 0eb73c8edd2..725f1b165a2 100644 --- a/spec/services/snippets/create_service_spec.rb +++ b/spec/services/snippets/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Snippets::CreateService do +RSpec.describe Snippets::CreateService, feature_category: :source_code_management do describe '#execute' do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:user, :admin) } diff --git a/spec/services/snippets/destroy_service_spec.rb b/spec/services/snippets/destroy_service_spec.rb index 23765243dd6..ace9847185e 100644 --- a/spec/services/snippets/destroy_service_spec.rb +++ b/spec/services/snippets/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Snippets::DestroyService do +RSpec.describe Snippets::DestroyService, feature_category: :source_code_management do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:other_user) { create(:user) } @@ -144,7 +144,7 @@ RSpec.describe Snippets::DestroyService do end end - context 'when the repository does not exists' do + context 'when the repository does not exist' do let(:snippet) { create(:personal_snippet, author: user) } it 'does not schedule anything and return success' do diff --git a/spec/services/snippets/repository_validation_service_spec.rb b/spec/services/snippets/repository_validation_service_spec.rb index 8166ce144e1..c9cd9f21481 100644 --- a/spec/services/snippets/repository_validation_service_spec.rb +++ b/spec/services/snippets/repository_validation_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Snippets::RepositoryValidationService do +RSpec.describe Snippets::RepositoryValidationService, feature_category: :source_code_management do describe '#execute' do let_it_be(:user) { create(:user) } let_it_be(:snippet) { create(:personal_snippet, :empty_repo, author: user) } diff --git a/spec/services/snippets/schedule_bulk_repository_shard_moves_service_spec.rb b/spec/services/snippets/schedule_bulk_repository_shard_moves_service_spec.rb index 9286d73ed4a..e88969ccf2d 100644 --- a/spec/services/snippets/schedule_bulk_repository_shard_moves_service_spec.rb +++ b/spec/services/snippets/schedule_bulk_repository_shard_moves_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Snippets::ScheduleBulkRepositoryShardMovesService do +RSpec.describe Snippets::ScheduleBulkRepositoryShardMovesService, feature_category: :source_code_management do it_behaves_like 'moves repository shard in bulk' do let_it_be_with_reload(:container) { create(:snippet, :repository) } diff --git a/spec/services/snippets/update_repository_storage_service_spec.rb b/spec/services/snippets/update_repository_storage_service_spec.rb index 9874189f73a..c417fbfd8b1 100644 --- a/spec/services/snippets/update_repository_storage_service_spec.rb +++ b/spec/services/snippets/update_repository_storage_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Snippets::UpdateRepositoryStorageService do +RSpec.describe Snippets::UpdateRepositoryStorageService, feature_category: :source_code_management do subject { described_class.new(repository_storage_move) } describe "#execute" do diff --git a/spec/services/snippets/update_service_spec.rb b/spec/services/snippets/update_service_spec.rb index 67cc258b4b6..99bb70a3077 100644 --- a/spec/services/snippets/update_service_spec.rb +++ b/spec/services/snippets/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Snippets::UpdateService do +RSpec.describe Snippets::UpdateService, feature_category: :source_code_management do describe '#execute', :aggregate_failures do let_it_be(:user) { create(:user) } let_it_be(:admin) { create :user, admin: true } diff --git a/spec/services/snippets/update_statistics_service_spec.rb b/spec/services/snippets/update_statistics_service_spec.rb index 27ae054676a..2d1872a09c4 100644 --- a/spec/services/snippets/update_statistics_service_spec.rb +++ b/spec/services/snippets/update_statistics_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Snippets::UpdateStatisticsService do +RSpec.describe Snippets::UpdateStatisticsService, feature_category: :source_code_management do describe '#execute' do subject { described_class.new(snippet).execute } diff --git a/spec/services/spam/akismet_mark_as_spam_service_spec.rb b/spec/services/spam/akismet_mark_as_spam_service_spec.rb index 12666e23e47..f07fa8d262b 100644 --- a/spec/services/spam/akismet_mark_as_spam_service_spec.rb +++ b/spec/services/spam/akismet_mark_as_spam_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Spam::AkismetMarkAsSpamService do +RSpec.describe Spam::AkismetMarkAsSpamService, feature_category: :instance_resiliency do let(:user_agent_detail) { build(:user_agent_detail) } let(:spammable) { build(:issue, user_agent_detail: user_agent_detail) } let(:fake_akismet_service) { double(:akismet_service, submit_spam: true) } diff --git a/spec/services/spam/akismet_service_spec.rb b/spec/services/spam/akismet_service_spec.rb index d9f62258a53..4d6a1650327 100644 --- a/spec/services/spam/akismet_service_spec.rb +++ b/spec/services/spam/akismet_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Spam::AkismetService do +RSpec.describe Spam::AkismetService, feature_category: :instance_resiliency do let(:fake_akismet_client) { double(:akismet_client) } let(:ip) { '1.2.3.4' } let(:user_agent) { 'some user_agent' } diff --git a/spec/services/spam/ham_service_spec.rb b/spec/services/spam/ham_service_spec.rb index 0101a8e7704..00906bc4b3d 100644 --- a/spec/services/spam/ham_service_spec.rb +++ b/spec/services/spam/ham_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Spam::HamService do +RSpec.describe Spam::HamService, feature_category: :instance_resiliency do let_it_be(:user) { create(:user) } let!(:spam_log) { create(:spam_log, user: user, submitted_as_ham: false) } diff --git a/spec/services/spam/spam_action_service_spec.rb b/spec/services/spam/spam_action_service_spec.rb index 4dfec9735ba..e2cc2ea7ce3 100644 --- a/spec/services/spam/spam_action_service_spec.rb +++ b/spec/services/spam/spam_action_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Spam::SpamActionService do +RSpec.describe Spam::SpamActionService, feature_category: :instance_resiliency do include_context 'includes Spam constants' let(:issue) { create(:issue, project: project, author: author) } @@ -53,6 +53,16 @@ RSpec.describe Spam::SpamActionService do end end + shared_examples 'allows user' do + it 'does not perform spam check' do + expect(Spam::SpamVerdictService).not_to receive(:new) + + response = subject + + expect(response.message).to match(/user was allowlisted/) + end + end + shared_examples 'creates a spam log' do |target_type| it do expect { subject } @@ -73,7 +83,6 @@ RSpec.describe Spam::SpamActionService do shared_examples 'execute spam action service' do |target_type| let(:fake_captcha_verification_service) { double(:captcha_verification_service) } let(:fake_verdict_service) { double(:spam_verdict_service) } - let(:allowlisted) { false } let(:verdict_service_opts) do { @@ -101,7 +110,6 @@ RSpec.describe Spam::SpamActionService do subject do described_service = described_class.new(spammable: target, spam_params: spam_params, extra_features: extra_features, user: user, action: :create) - allow(described_service).to receive(:allowlisted?).and_return(allowlisted) described_service.execute end @@ -158,16 +166,20 @@ RSpec.describe Spam::SpamActionService do target.description = 'Lovely Spam! Wonderful Spam!' end - context 'when allowlisted' do - let(:allowlisted) { true } - - it 'does not perform spam check' do - expect(Spam::SpamVerdictService).not_to receive(:new) + context 'when user is a gitlab bot' do + before do + allow(user).to receive(:gitlab_bot?).and_return(true) + end - response = subject + it_behaves_like 'allows user' + end - expect(response.message).to match(/user was allowlisted/) + context 'when user is a gitlab service user' do + before do + allow(user).to receive(:gitlab_service_user?).and_return(true) end + + it_behaves_like 'allows user' end context 'when disallowed by the spam verdict service' do diff --git a/spec/services/spam/spam_params_spec.rb b/spec/services/spam/spam_params_spec.rb index 7e74641c0fa..39c3b303529 100644 --- a/spec/services/spam/spam_params_spec.rb +++ b/spec/services/spam/spam_params_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Spam::SpamParams do +RSpec.describe Spam::SpamParams, feature_category: :instance_resiliency do shared_examples 'constructs from a request' do it 'constructs from a request' do expected = ::Spam::SpamParams.new( diff --git a/spec/services/spam/spam_verdict_service_spec.rb b/spec/services/spam/spam_verdict_service_spec.rb index dde93aa6b93..00e320ed56c 100644 --- a/spec/services/spam/spam_verdict_service_spec.rb +++ b/spec/services/spam/spam_verdict_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Spam::SpamVerdictService do +RSpec.describe Spam::SpamVerdictService, feature_category: :instance_resiliency do include_context 'includes Spam constants' let(:fake_ip) { '1.2.3.4' } @@ -14,6 +14,22 @@ RSpec.describe Spam::SpamVerdictService do 'HTTP_REFERER' => fake_referer } end + let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::ALLOW } + let(:verdict_score) { 0.01 } + let(:verdict_evaluated) { true } + + let(:response) do + response = ::Spamcheck::SpamVerdict.new + response.verdict = verdict_value + response.score = verdict_score + response.evaluated = verdict_evaluated + response + end + + let(:spam_client_result) do + Gitlab::Spamcheck::Result.new(response) + end + let(:check_for_spam) { true } let_it_be(:user) { create(:user) } let_it_be(:issue) { create(:issue, author: user) } @@ -23,43 +39,38 @@ RSpec.describe Spam::SpamVerdictService do described_class.new(user: user, target: target, options: {}) end - let(:attribs) do - extra_attributes = { "monitorMode" => "false" } - extra_attributes - end - shared_examples 'execute spam verdict service' do - subject { service.execute } + subject(:execute) { service.execute } before do - allow(service).to receive(:akismet_verdict).and_return(nil) - allow(service).to receive(:spamcheck_verdict).and_return([nil, attribs]) + allow(service).to receive(:get_akismet_verdict).and_return(nil) + allow(service).to receive(:get_spamcheck_verdict).and_return(nil) end context 'if all services return nil' do it 'renders ALLOW verdict' do - expect(subject).to eq ALLOW + is_expected.to eq ALLOW end end context 'if only one service returns a verdict' do context 'and it is supported' do before do - allow(service).to receive(:akismet_verdict).and_return(DISALLOW) + allow(service).to receive(:get_akismet_verdict).and_return(DISALLOW) end it 'renders that verdict' do - expect(subject).to eq DISALLOW + is_expected.to eq DISALLOW end end context 'and it is unexpected' do before do - allow(service).to receive(:akismet_verdict).and_return("unexpected") + allow(service).to receive(:get_akismet_verdict).and_return("unexpected") end it 'allows' do - expect(subject).to eq ALLOW + is_expected.to eq ALLOW end end end @@ -67,50 +78,34 @@ RSpec.describe Spam::SpamVerdictService do context 'if more than one service returns a verdict' do context 'and they are supported' do before do - allow(service).to receive(:akismet_verdict).and_return(DISALLOW) - allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs]) + allow(service).to receive(:get_akismet_verdict).and_return(DISALLOW) + allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER) end it 'renders the more restrictive verdict' do - expect(subject).to eq BLOCK_USER + is_expected.to eq BLOCK_USER end end context 'and one is supported' do before do - allow(service).to receive(:akismet_verdict).and_return('nonsense') - allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs]) + allow(service).to receive(:get_akismet_verdict).and_return('nonsense') + allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER) end it 'renders the more restrictive verdict' do - expect(subject).to eq BLOCK_USER + is_expected.to eq BLOCK_USER end end context 'and none are supported' do before do - allow(service).to receive(:akismet_verdict).and_return('nonsense') - allow(service).to receive(:spamcheck_verdict).and_return(['rubbish', attribs]) - end - - it 'renders the more restrictive verdict' do - expect(subject).to eq ALLOW - end - end - - context 'and attribs - monitorMode is true' do - let(:attribs) do - extra_attributes = { "monitorMode" => "true" } - extra_attributes - end - - before do - allow(service).to receive(:akismet_verdict).and_return(DISALLOW) - allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs]) + allow(service).to receive(:get_akismet_verdict).and_return('nonsense') + allow(service).to receive(:get_spamcheck_verdict).and_return('rubbish') end it 'renders the more restrictive verdict' do - expect(subject).to eq(DISALLOW) + is_expected.to eq ALLOW end end end @@ -122,21 +117,21 @@ RSpec.describe Spam::SpamVerdictService do context 'and a service returns a verdict that should be overridden' do before do - allow(service).to receive(:spamcheck_verdict).and_return([BLOCK_USER, attribs]) + allow(service).to receive(:get_spamcheck_verdict).and_return(BLOCK_USER) end it 'overrides and renders the override verdict' do - expect(subject).to eq OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM + 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(:spamcheck_verdict).and_return([ALLOW, attribs]) + allow(service).to receive(:get_spamcheck_verdict).and_return(ALLOW) end it 'does not override and renders the original verdict' do - expect(subject).to eq ALLOW + is_expected.to eq ALLOW end end end @@ -146,24 +141,23 @@ RSpec.describe Spam::SpamVerdictService do using RSpec::Parameterized::TableSyntax - where(:verdict, :error, :label) do - Spam::SpamConstants::ALLOW | false | 'ALLOW' - Spam::SpamConstants::ALLOW | true | 'ERROR' - Spam::SpamConstants::CONDITIONAL_ALLOW | false | 'CONDITIONAL_ALLOW' - Spam::SpamConstants::BLOCK_USER | false | 'BLOCK' - Spam::SpamConstants::DISALLOW | false | 'DISALLOW' - Spam::SpamConstants::NOOP | false | 'NOOP' + where(:verdict, :label) do + Spam::SpamConstants::ALLOW | 'ALLOW' + Spam::SpamConstants::CONDITIONAL_ALLOW | 'CONDITIONAL_ALLOW' + Spam::SpamConstants::BLOCK_USER | 'BLOCK' + Spam::SpamConstants::DISALLOW | 'DISALLOW' + Spam::SpamConstants::NOOP | 'NOOP' end with_them do before do allow(Gitlab::Metrics).to receive(:histogram).with(:gitlab_spamcheck_request_duration_seconds, anything).and_return(histogram) - allow(service).to receive(:spamcheck_verdict).and_return([verdict, attribs, error]) + allow(service).to receive(:get_spamcheck_verdict).and_return(verdict) end it 'records duration with labels' do expect(histogram).to receive(:observe).with(a_hash_including(result: label), anything) - subject + execute end end end @@ -171,7 +165,8 @@ RSpec.describe Spam::SpamVerdictService do shared_examples 'akismet verdict' do let(:target) { issue } - subject { service.send(:akismet_verdict) } + + subject(:get_akismet_verdict) { service.send(:get_akismet_verdict) } context 'if Akismet is enabled' do before do @@ -190,7 +185,7 @@ RSpec.describe Spam::SpamVerdictService do end it 'returns conditionally allow verdict' do - expect(subject).to eq CONDITIONAL_ALLOW + is_expected.to eq CONDITIONAL_ALLOW end end @@ -200,7 +195,7 @@ RSpec.describe Spam::SpamVerdictService do end it 'renders disallow verdict' do - expect(subject).to eq DISALLOW + is_expected.to eq DISALLOW end end end @@ -209,7 +204,7 @@ RSpec.describe Spam::SpamVerdictService do let(:akismet_result) { false } it 'renders allow verdict' do - expect(subject).to eq ALLOW + is_expected.to eq ALLOW end end end @@ -220,13 +215,13 @@ RSpec.describe Spam::SpamVerdictService do end it 'renders allow verdict' do - expect(subject).to eq ALLOW + is_expected.to eq ALLOW end end end shared_examples 'spamcheck verdict' do - subject { service.send(:spamcheck_verdict) } + subject(:get_spamcheck_verdict) { service.send(:get_spamcheck_verdict) } context 'if a Spam Check endpoint enabled and set to a URL' do let(:spam_check_body) { {} } @@ -242,45 +237,50 @@ RSpec.describe Spam::SpamVerdictService do end context 'if the endpoint is accessible' do - let(:error) { '' } - let(:verdict) { nil } - - let(:attribs) do - extra_attributes = { "monitorMode" => "false" } - extra_attributes - end - before do allow(service).to receive(:spamcheck_client).and_return(spam_client) - allow(spam_client).to receive(:spam?).and_return([verdict, attribs, error]) + allow(spam_client).to receive(:spam?).and_return(spam_client_result) end context 'if the result is a NOOP verdict' do - let(:verdict) { NOOP } + let(:verdict_evaluated) { false } + let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::NOOP } it 'returns the verdict' do - expect(subject).to eq([NOOP, attribs]) + is_expected.to eq(NOOP) + expect(user.spam_score).to eq(0.0) end end - context 'if attribs - monitorMode is true' do - let(:attribs) do - extra_attributes = { "monitorMode" => "true" } - extra_attributes + context 'the result is a valid verdict' do + let(:verdict_score) { 0.05 } + let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::ALLOW } + + 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) + end end - let(:verdict) { ALLOW } + context 'the result was not evaluated' do + let(:verdict_evaluated) { false } - it 'returns the verdict' do - expect(subject).to eq([ALLOW, attribs]) + 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) + end end - end - context 'the result is a valid verdict' do - let(:verdict) { ALLOW } + context 'user spam score feature is disabled' do + before do + stub_feature_flags(user_spam_scores: false) + end - it 'returns the verdict' do - expect(subject).to eq([ALLOW, attribs]) + 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) + end end end @@ -291,20 +291,17 @@ RSpec.describe Spam::SpamVerdictService do using RSpec::Parameterized::TableSyntax - # rubocop: disable Lint/BinaryOperatorWithIdenticalOperands - where(:verdict_value, :expected) do - ::Spam::SpamConstants::ALLOW | ::Spam::SpamConstants::ALLOW - ::Spam::SpamConstants::CONDITIONAL_ALLOW | ::Spam::SpamConstants::CONDITIONAL_ALLOW - ::Spam::SpamConstants::DISALLOW | ::Spam::SpamConstants::DISALLOW - ::Spam::SpamConstants::BLOCK_USER | ::Spam::SpamConstants::BLOCK_USER + where(:verdict_value, :expected, :verdict_score) do + ::Spamcheck::SpamVerdict::Verdict::ALLOW | ::Spam::SpamConstants::ALLOW | 0.1 + ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW | ::Spam::SpamConstants::CONDITIONAL_ALLOW | 0.5 + ::Spamcheck::SpamVerdict::Verdict::DISALLOW | ::Spam::SpamConstants::DISALLOW | 0.8 + ::Spamcheck::SpamVerdict::Verdict::BLOCK | ::Spam::SpamConstants::BLOCK_USER | 0.9 end - # rubocop: enable Lint/BinaryOperatorWithIdenticalOperands with_them do - let(:verdict) { verdict_value } - - it "returns expected spam constant" do - expect(subject).to eq([expected, attribs]) + 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) end end end @@ -314,54 +311,23 @@ RSpec.describe Spam::SpamVerdictService do allow(Gitlab::Recaptcha).to receive(:enabled?).and_return(false) end - [::Spam::SpamConstants::ALLOW, - ::Spam::SpamConstants::CONDITIONAL_ALLOW, - ::Spam::SpamConstants::DISALLOW, - ::Spam::SpamConstants::BLOCK_USER].each do |verdict_value| - let(:verdict) { verdict_value } - let(:expected) { [verdict_value, attribs] } - - it "returns expected spam constant" do - expect(subject).to eq(expected) - end - end - end - - context 'the verdict is an unexpected value' do - let(:verdict) { :this_is_fine } - - it 'returns the string' do - expect(subject).to eq([verdict, attribs]) - end - end - - context 'the verdict is an empty string' do - let(:verdict) { '' } - - it 'returns nil' do - expect(subject).to eq([verdict, attribs]) - end - end - - context 'the verdict is nil' do - let(:verdict) { nil } + using RSpec::Parameterized::TableSyntax - it 'returns nil' do - expect(subject).to eq([nil, attribs]) + where(:verdict_value, :expected) do + ::Spamcheck::SpamVerdict::Verdict::ALLOW | ::Spam::SpamConstants::ALLOW + ::Spamcheck::SpamVerdict::Verdict::CONDITIONAL_ALLOW | ::Spam::SpamConstants::CONDITIONAL_ALLOW + ::Spamcheck::SpamVerdict::Verdict::DISALLOW | ::Spam::SpamConstants::DISALLOW + ::Spamcheck::SpamVerdict::Verdict::BLOCK | ::Spam::SpamConstants::BLOCK_USER end - end - context 'there is an error' do - let(:error) { "Sorry Dave, I can't do that" } - - it 'returns nil' do - expect(subject).to eq([nil, attribs]) + with_them do + it "returns expected spam constant" do + is_expected.to eq(expected) + end end end context 'the requested is aborted' do - let(:attribs) { nil } - before do allow(spam_client).to receive(:spam?).and_raise(GRPC::Aborted) end @@ -370,22 +336,11 @@ RSpec.describe Spam::SpamVerdictService do expect(Gitlab::ErrorTracking).to receive(:log_exception).with( an_instance_of(GRPC::Aborted), error: ::Spam::SpamConstants::ERROR_TYPE ) - expect(subject).to eq([ALLOW, attribs, true]) - end - end - - context 'the confused API endpoint returns both an error and a verdict' do - let(:verdict) { 'disallow' } - let(:error) { 'oh noes!' } - - it 'renders the verdict' do - expect(subject).to eq [DISALLOW, attribs] + is_expected.to be_nil end end context 'if the endpoint times out' do - let(:attribs) { nil } - before do allow(spam_client).to receive(:spam?).and_raise(GRPC::DeadlineExceeded) end @@ -394,7 +349,7 @@ RSpec.describe Spam::SpamVerdictService do expect(Gitlab::ErrorTracking).to receive(:log_exception).with( an_instance_of(GRPC::DeadlineExceeded), error: ::Spam::SpamConstants::ERROR_TYPE ) - expect(subject).to eq([ALLOW, attribs, true]) + is_expected.to be_nil end end end @@ -406,7 +361,7 @@ RSpec.describe Spam::SpamVerdictService do end it 'returns nil' do - expect(subject).to be_nil + is_expected.to be_nil end end @@ -416,7 +371,7 @@ RSpec.describe Spam::SpamVerdictService do end it 'returns nil' do - expect(subject).to be_nil + is_expected.to be_nil end end end @@ -435,7 +390,7 @@ RSpec.describe Spam::SpamVerdictService do end end - describe '#akismet_verdict' do + describe '#get_akismet_verdict' do describe 'issue' do let(:target) { issue } @@ -449,7 +404,7 @@ RSpec.describe Spam::SpamVerdictService do end end - describe '#spamcheck_verdict' do + describe '#get_spamcheck_verdict' do describe 'issue' do let(:target) { issue } diff --git a/spec/services/submodules/update_service_spec.rb b/spec/services/submodules/update_service_spec.rb index 1a53da7b9fe..aeaf8ec1c7b 100644 --- a/spec/services/submodules/update_service_spec.rb +++ b/spec/services/submodules/update_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Submodules::UpdateService do +RSpec.describe Submodules::UpdateService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user, :commit_email) } diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index 41ccd8523fa..6e2c623035e 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Suggestions::ApplyService do +RSpec.describe Suggestions::ApplyService, feature_category: :code_suggestions do include ProjectForksHelper def build_position(**optional_args) diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb index a4e62431128..a8bc3cba697 100644 --- a/spec/services/suggestions/create_service_spec.rb +++ b/spec/services/suggestions/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Suggestions::CreateService do +RSpec.describe Suggestions::CreateService, feature_category: :code_suggestions do let(:project_with_repo) { create(:project, :repository) } let(:merge_request) do create(:merge_request, source_project: project_with_repo, diff --git a/spec/services/suggestions/outdate_service_spec.rb b/spec/services/suggestions/outdate_service_spec.rb index e8891f88548..7bd70866bf7 100644 --- a/spec/services/suggestions/outdate_service_spec.rb +++ b/spec/services/suggestions/outdate_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Suggestions::OutdateService do +RSpec.describe Suggestions::OutdateService, feature_category: :code_suggestions do describe '#execute' do let(:merge_request) { create(:merge_request) } let(:project) { merge_request.target_project } diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 5d60b6e0487..883a7d3a2ce 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SystemHooksService do +RSpec.describe SystemHooksService, feature_category: :webhooks do describe '#execute_hooks_for' do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 38b6943b12a..1eb11c80264 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -692,10 +692,10 @@ RSpec.describe SystemNoteService, feature_category: :shared do it 'calls IssuableService' do expect_next_instance_of(::SystemNotes::IssuablesService) do |service| - expect(service).to receive(:change_issue_type) + expect(service).to receive(:change_issue_type).with('issue') end - described_class.change_issue_type(incident, author) + described_class.change_issue_type(incident, author, 'issue') end end diff --git a/spec/services/system_notes/alert_management_service_spec.rb b/spec/services/system_notes/alert_management_service_spec.rb index 039975c1bf6..4d40a6a6cfd 100644 --- a/spec/services/system_notes/alert_management_service_spec.rb +++ b/spec/services/system_notes/alert_management_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::SystemNotes::AlertManagementService do +RSpec.describe ::SystemNotes::AlertManagementService, feature_category: :projects do let_it_be(:author) { create(:user) } let_it_be(:project) { create(:project, :repository) } let_it_be(:noteable) { create(:alert_management_alert, :with_incident, :acknowledged, project: project) } diff --git a/spec/services/system_notes/base_service_spec.rb b/spec/services/system_notes/base_service_spec.rb index efb165f8e4c..6ea4751b613 100644 --- a/spec/services/system_notes/base_service_spec.rb +++ b/spec/services/system_notes/base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SystemNotes::BaseService do +RSpec.describe SystemNotes::BaseService, feature_category: :projects do let(:noteable) { double } let(:project) { double } let(:author) { double } diff --git a/spec/services/system_notes/commit_service_spec.rb b/spec/services/system_notes/commit_service_spec.rb index 0399603980d..8dfb83f63fe 100644 --- a/spec/services/system_notes/commit_service_spec.rb +++ b/spec/services/system_notes/commit_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SystemNotes::CommitService do +RSpec.describe SystemNotes::CommitService, feature_category: :code_review_workflow do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :repository, group: group) } let_it_be(:author) { create(:user) } @@ -13,7 +13,7 @@ RSpec.describe SystemNotes::CommitService do subject { commit_service.add_commits(new_commits, old_commits, oldrev) } let(:noteable) { create(:merge_request, source_project: project, target_project: project) } - let(:new_commits) { noteable.commits } + let(:new_commits) { create_commits(10) } let(:old_commits) { [] } let(:oldrev) { nil } @@ -43,6 +43,48 @@ RSpec.describe SystemNotes::CommitService do expect(decoded_note_content).to include("<li>#{commit.short_id} - #{commit.title}</li>") end end + + context 'with HTML content' do + let(:new_commits) { [double(title: '<pre>This is a test</pre>', short_id: '12345678')] } + + it 'escapes HTML titles' do + expect(note_lines[1]).to eq("<ul><li>12345678 - <pre>This is a test</pre></li></ul>") + end + end + + context 'with one commit exceeding the NEW_COMMIT_DISPLAY_LIMIT' do + let(:new_commits) { create_commits(11) } + let(:earlier_commit_summary_line) { note_lines[1] } + + it 'includes the truncated new commits summary' do + expect(earlier_commit_summary_line).to start_with("<ul><li>#{new_commits[0].short_id} - 1 earlier commit") + end + + context 'with oldrev' do + let(:oldrev) { '12345678abcd' } + + it 'includes the truncated new commits summary with the oldrev' do + expect(earlier_commit_summary_line).to start_with("<ul><li>#{new_commits[0].short_id} - 1 earlier commit") + end + end + end + + context 'with multiple commits exceeding the NEW_COMMIT_DISPLAY_LIMIT' do + let(:new_commits) { create_commits(13) } + let(:earlier_commit_summary_line) { note_lines[1] } + + it 'includes the truncated new commits summary' do + expect(earlier_commit_summary_line).to start_with("<ul><li>#{new_commits[0].short_id}..#{new_commits[2].short_id} - 3 earlier commits") + end + + context 'with oldrev' do + let(:oldrev) { '12345678abcd' } + + it 'includes the truncated new commits summary with the oldrev' do + expect(earlier_commit_summary_line).to start_with("<ul><li>12345678...#{new_commits[2].short_id} - 3 earlier commits") + end + end + end end describe 'summary line for existing commits' do @@ -54,6 +96,15 @@ RSpec.describe SystemNotes::CommitService do it 'includes the existing commit' do expect(summary_line).to start_with("<ul><li>#{old_commits.first.short_id} - 1 commit from branch <code>feature</code>") end + + context 'with new commits exceeding the display limit' do + let(:summary_line) { note_lines[1] } + let(:new_commits) { create_commits(13) } + + it 'includes the existing commit as well as the truncated new commit summary' do + expect(summary_line).to start_with("<ul><li>#{old_commits.first.short_id} - 1 commit from branch <code>feature</code></li><li>#{old_commits.last.short_id}...#{new_commits[2].short_id} - 3 earlier commits") + end + end end context 'with multiple existing commits' do @@ -66,6 +117,15 @@ RSpec.describe SystemNotes::CommitService do expect(summary_line) .to start_with("<ul><li>#{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id} - 26 commits from branch <code>feature</code>") end + + context 'with new commits exceeding the display limit' do + let(:new_commits) { create_commits(13) } + + it 'includes the existing commit as well as the truncated new commit summary' do + expect(summary_line) + .to start_with("<ul><li>#{Commit.truncate_sha(oldrev)}...#{old_commits.last.short_id} - 26 commits from branch <code>feature</code></li><li>#{old_commits.last.short_id}...#{new_commits[2].short_id} - 3 earlier commits") + end + end end context 'without oldrev' do @@ -73,6 +133,15 @@ RSpec.describe SystemNotes::CommitService do expect(summary_line) .to start_with("<ul><li>#{old_commits[0].short_id}..#{old_commits[-1].short_id} - 26 commits from branch <code>feature</code>") end + + context 'with new commits exceeding the display limit' do + let(:new_commits) { create_commits(13) } + + it 'includes the existing commit as well as the truncated new commit summary' do + expect(summary_line) + .to start_with("<ul><li>#{old_commits.first.short_id}..#{old_commits.last.short_id} - 26 commits from branch <code>feature</code></li><li>#{old_commits.last.short_id}...#{new_commits[2].short_id} - 3 earlier commits") + end + end end context 'on a fork' do @@ -106,12 +175,9 @@ RSpec.describe SystemNotes::CommitService do end end - describe '#new_commit_summary' do - it 'escapes HTML titles' do - commit = double(title: '<pre>This is a test</pre>', short_id: '12345678') - escaped = '<pre>This is a test</pre>' - - expect(described_class.new.new_commit_summary([commit])).to all(match(/- #{escaped}/)) + def create_commits(count) + Array.new(count) do |i| + double(title: "Test commit #{i}", short_id: "abcd00#{i}") end end end diff --git a/spec/services/system_notes/design_management_service_spec.rb b/spec/services/system_notes/design_management_service_spec.rb index 19e1f338eb8..92568890c6f 100644 --- a/spec/services/system_notes/design_management_service_spec.rb +++ b/spec/services/system_notes/design_management_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SystemNotes::DesignManagementService do +RSpec.describe SystemNotes::DesignManagementService, feature_category: :design_management do let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } diff --git a/spec/services/system_notes/incident_service_spec.rb b/spec/services/system_notes/incident_service_spec.rb index 5de352ad8fa..0e9828c0a81 100644 --- a/spec/services/system_notes/incident_service_spec.rb +++ b/spec/services/system_notes/incident_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::SystemNotes::IncidentService do +RSpec.describe ::SystemNotes::IncidentService, feature_category: :incident_management do let_it_be(:author) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:noteable) { create(:incident, project: project) } diff --git a/spec/services/system_notes/incidents_service_spec.rb b/spec/services/system_notes/incidents_service_spec.rb index 6439f9fae93..5452d51dfc0 100644 --- a/spec/services/system_notes/incidents_service_spec.rb +++ b/spec/services/system_notes/incidents_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SystemNotes::IncidentsService do +RSpec.describe SystemNotes::IncidentsService, feature_category: :incident_management do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:author) { create(:user) } diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb index 3263e410d3c..af660a9b72e 100644 --- a/spec/services/system_notes/issuables_service_spec.rb +++ b/spec/services/system_notes/issuables_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::SystemNotes::IssuablesService do +RSpec.describe ::SystemNotes::IssuablesService, feature_category: :team_planning do include ProjectForksHelper let_it_be(:group) { create(:group) } @@ -861,15 +861,29 @@ RSpec.describe ::SystemNotes::IssuablesService do end describe '#change_issue_type' do - let(:noteable) { create(:incident, project: project) } + context 'with issue' do + let_it_be_with_reload(:noteable) { create(:issue, project: project) } - subject { service.change_issue_type } + subject { service.change_issue_type('incident') } - it_behaves_like 'a system note' do - let(:action) { 'issue_type' } + it_behaves_like 'a system note' do + let(:action) { 'issue_type' } + end + + it { expect(subject.note).to eq "changed type from incident to issue" } end - it { expect(subject.note).to eq "changed issue type to incident" } + context 'with work item' do + let_it_be_with_reload(:noteable) { create(:work_item, project: project) } + + subject { service.change_issue_type('task') } + + it_behaves_like 'a system note' do + let(:action) { 'issue_type' } + end + + it { expect(subject.note).to eq "changed type from task to issue" } + end end describe '#hierarchy_changed' do diff --git a/spec/services/system_notes/merge_requests_service_spec.rb b/spec/services/system_notes/merge_requests_service_spec.rb index 3e66ccef106..7ddcd799a55 100644 --- a/spec/services/system_notes/merge_requests_service_spec.rb +++ b/spec/services/system_notes/merge_requests_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::SystemNotes::MergeRequestsService do +RSpec.describe ::SystemNotes::MergeRequestsService, feature_category: :code_review_workflow do include Gitlab::Routing let_it_be(:group) { create(:group) } diff --git a/spec/services/system_notes/time_tracking_service_spec.rb b/spec/services/system_notes/time_tracking_service_spec.rb index c856caa3f3e..71228050085 100644 --- a/spec/services/system_notes/time_tracking_service_spec.rb +++ b/spec/services/system_notes/time_tracking_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::SystemNotes::TimeTrackingService do +RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_planning do let_it_be(:author) { create(:user) } let_it_be(:project) { create(:project, :repository) } @@ -37,7 +37,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService do end it 'sets the correct note message' do - expect(note.note).to eq('removed start date and removed due date') + expect(note.note).to eq("removed start date #{start_date.to_s(:long)} and removed due date #{due_date.to_s(:long)}") end end @@ -52,7 +52,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService 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 and changed due date to #{due_date.to_s(:long)}") + expect(note.note).to eq("removed start date #{start_date.to_s(:long)} and changed due date to #{due_date.to_s(:long)}") end end end @@ -80,7 +80,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService 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") + expect(note.note).to eq("changed start date to #{start_date.to_s(:long)} and removed due date #{due_date.to_s(:long)}") end end end diff --git a/spec/services/system_notes/zoom_service_spec.rb b/spec/services/system_notes/zoom_service_spec.rb index 986324c9664..b46b4113e12 100644 --- a/spec/services/system_notes/zoom_service_spec.rb +++ b/spec/services/system_notes/zoom_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::SystemNotes::ZoomService do +RSpec.describe ::SystemNotes::ZoomService, feature_category: :integrations do let_it_be(:project) { create(:project, :repository) } let_it_be(:author) { create(:user) } diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb index bbf6fe62959..51b8bace626 100644 --- a/spec/services/tags/create_service_spec.rb +++ b/spec/services/tags/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Tags::CreateService do +RSpec.describe Tags::CreateService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb index 6160f337552..343a87785ad 100644 --- a/spec/services/tags/destroy_service_spec.rb +++ b/spec/services/tags/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Tags::DestroyService do +RSpec.describe Tags::DestroyService, feature_category: :source_code_management do let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb index f889f298213..5d55c1ca8de 100644 --- a/spec/services/task_list_toggle_service_spec.rb +++ b/spec/services/task_list_toggle_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe TaskListToggleService do +RSpec.describe TaskListToggleService, feature_category: :team_planning do let(:markdown) do <<-EOT.strip_heredoc * [ ] Task 1 @@ -18,6 +18,8 @@ RSpec.describe TaskListToggleService do with an embedded paragraph + [ ] No-break space (U+00A0) + + 2) [ ] Another item EOT end @@ -53,6 +55,11 @@ RSpec.describe TaskListToggleService do <input type="checkbox" class="task-list-item-checkbox" disabled=""> No-break space (U+00A0) </li> </ul> + <ol start="2" data-sourcepos="15:1-15:19" class="task-list" dir="auto"> + <li data-sourcepos="15:1-15:19" class="task-list-item"> + <input type="checkbox" class="task-list-item-checkbox" disabled> Another item + </li> + </ol> EOT end @@ -92,10 +99,20 @@ RSpec.describe TaskListToggleService do line_source: '+ [ ] No-break space (U+00A0)', line_number: 13) expect(toggler.execute).to be_truthy - expect(toggler.updated_markdown.lines[12]).to eq "+ [x] No-break space (U+00A0)" + expect(toggler.updated_markdown.lines[12]).to eq "+ [x] No-break space (U+00A0)\n" expect(toggler.updated_markdown_html).to include('disabled checked> No-break space (U+00A0)') end + it 'checks Another item' do + toggler = described_class.new(markdown, markdown_html, + toggle_as_checked: true, + line_source: '2) [ ] Another item', line_number: 15) + + expect(toggler.execute).to be_truthy + expect(toggler.updated_markdown.lines[14]).to eq "2) [x] Another item" + expect(toggler.updated_markdown_html).to include('disabled checked> Another item') + end + it 'returns false if line_source does not match the text' do toggler = described_class.new(markdown, markdown_html, toggle_as_checked: false, diff --git a/spec/services/tasks_to_be_done/base_service_spec.rb b/spec/services/tasks_to_be_done/base_service_spec.rb index cfeff36cc0d..3ca9d140197 100644 --- a/spec/services/tasks_to_be_done/base_service_spec.rb +++ b/spec/services/tasks_to_be_done/base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe TasksToBeDone::BaseService do +RSpec.describe TasksToBeDone::BaseService, feature_category: :team_planning do let_it_be(:project) { create(:project) } let_it_be(:current_user) { create(:user) } let_it_be(:assignee_one) { create(:user) } @@ -33,9 +33,9 @@ RSpec.describe TasksToBeDone::BaseService do add_labels: label.title } - expect(Issues::BuildService) + expect(Issues::CreateService) .to receive(:new) - .with(container: project, current_user: current_user, params: params) + .with(container: project, current_user: current_user, params: params, spam_params: nil) .and_call_original expect { service.execute }.to change(Issue, :count).by(1) diff --git a/spec/services/terraform/remote_state_handler_spec.rb b/spec/services/terraform/remote_state_handler_spec.rb index 369309e4d5a..4590a9ad0e9 100644 --- a/spec/services/terraform/remote_state_handler_spec.rb +++ b/spec/services/terraform/remote_state_handler_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Terraform::RemoteStateHandler do +RSpec.describe Terraform::RemoteStateHandler, feature_category: :infrastructure_as_code do let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user, developer_projects: [project]) } let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) } @@ -85,6 +85,7 @@ RSpec.describe Terraform::RemoteStateHandler do end expect(record.reload.name).to eq 'new-name' + expect(record.reload.project).to eq project end it 'raises exception if lock has not been acquired before' do diff --git a/spec/services/terraform/states/destroy_service_spec.rb b/spec/services/terraform/states/destroy_service_spec.rb index 5acf32cd73c..3515a758827 100644 --- a/spec/services/terraform/states/destroy_service_spec.rb +++ b/spec/services/terraform/states/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Terraform::States::DestroyService do +RSpec.describe Terraform::States::DestroyService, feature_category: :infrastructure_as_code do let_it_be(:state) { create(:terraform_state, :with_version, :deletion_in_progress) } let(:file) { instance_double(Terraform::StateUploader, relative_path: 'path') } diff --git a/spec/services/terraform/states/trigger_destroy_service_spec.rb b/spec/services/terraform/states/trigger_destroy_service_spec.rb index 459f4c3bdb9..0b37d962353 100644 --- a/spec/services/terraform/states/trigger_destroy_service_spec.rb +++ b/spec/services/terraform/states/trigger_destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Terraform::States::TriggerDestroyService do +RSpec.describe Terraform::States::TriggerDestroyService, feature_category: :infrastructure_as_code do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user, maintainer_projects: [project]) } diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb index 13f863dbbdb..31f97edbd08 100644 --- a/spec/services/test_hooks/project_service_spec.rb +++ b/spec/services/test_hooks/project_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe TestHooks::ProjectService do +RSpec.describe TestHooks::ProjectService, feature_category: :code_testing do include AfterNextHelpers let(:current_user) { create(:user) } diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb index e94ea4669c6..4c5009fea54 100644 --- a/spec/services/test_hooks/system_service_spec.rb +++ b/spec/services/test_hooks/system_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe TestHooks::SystemService do +RSpec.describe TestHooks::SystemService, feature_category: :code_testing do include AfterNextHelpers describe '#execute' do diff --git a/spec/services/timelogs/delete_service_spec.rb b/spec/services/timelogs/delete_service_spec.rb index ee1133af6b3..c0543bafcec 100644 --- a/spec/services/timelogs/delete_service_spec.rb +++ b/spec/services/timelogs/delete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Timelogs::DeleteService do +RSpec.describe Timelogs::DeleteService, feature_category: :team_planning do let_it_be(:author) { create(:user) } let_it_be(:project) { create(:project, :public) } let_it_be(:issue) { create(:issue, project: project) } diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index f73eae70d3c..1ec6a3250fc 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe TodoService do +RSpec.describe TodoService, feature_category: :team_planning do include AfterNextHelpers let_it_be(:project) { create(:project, :repository) } @@ -211,7 +211,6 @@ RSpec.describe TodoService do end it_behaves_like 'Snowplow event tracking with RedisHLL context' do - let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:namespace) { project.namespace } let(:category) { described_class.to_s } let(:action) { 'incident_management_incident_todo' } @@ -395,6 +394,39 @@ RSpec.describe TodoService do end end + describe '#resolve_todos_with_attributes_for_target' do + it 'marks related pending todos to the target for all the users as done' do + first_todo = create(:todo, :assigned, user: member, project: project, target: issue, author: author) + second_todo = create(:todo, :review_requested, user: john_doe, project: project, target: issue, author: author) + another_todo = create(:todo, :assigned, user: john_doe, project: project, target: project, author: author) + + service.resolve_todos_with_attributes_for_target(issue, {}) + + expect(first_todo.reload).to be_done + expect(second_todo.reload).to be_done + expect(another_todo.reload).to be_pending + end + + it 'marks related only filtered pending todos to the target for all the users as done' do + first_todo = create(:todo, :assigned, user: member, project: project, target: issue, author: author) + second_todo = create(:todo, :review_requested, user: john_doe, project: project, target: issue, author: author) + another_todo = create(:todo, :assigned, user: john_doe, project: project, target: project, author: author) + + service.resolve_todos_with_attributes_for_target(issue, { action: Todo::ASSIGNED }) + + expect(first_todo.reload).to be_done + expect(second_todo.reload).to be_pending + expect(another_todo.reload).to be_pending + end + + it 'fetches the pending todos with users preloaded' do + expect(PendingTodosFinder).to receive(:new) + .with(a_hash_including(preload_user_association: true)).and_call_original + + service.resolve_todos_with_attributes_for_target(issue, { action: Todo::ASSIGNED }) + end + end + describe '#new_note' do let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) } @@ -1225,20 +1257,64 @@ RSpec.describe TodoService do end describe '#resolve_access_request_todos' do - let_it_be(:source) { create(:group, :public) } - let_it_be(:requester) { create(:group_member, :access_request, group: source, user: assignee) } + let_it_be(:group) { create(:group, :public) } + let_it_be(:group_requester) { create(:group_member, :access_request, group: group, user: assignee) } + let_it_be(:project_requester) { create(:project_member, :access_request, project: project, user: non_member) } + let_it_be(:another_pending_todo) { create(:todo, state: :pending, user: john_doe) } + # access request by another user + let_it_be(:another_group_todo) do + create(:todo, state: :pending, target: group, action: Todo::MEMBER_ACCESS_REQUESTED) + end - it 'marks the todos for request handler as done' do - request_handler_todo = create(:todo, - user: member, - state: :pending, - action: Todo::MEMBER_ACCESS_REQUESTED, - author: requester.user, - target: source) + let_it_be(:another_project_todo) do + create(:todo, state: :pending, target: project, action: Todo::MEMBER_ACCESS_REQUESTED) + end - service.resolve_access_request_todos(member, requester) + it 'marks the todos for group access request handlers as done' do + access_request_todos = [member, john_doe].map do |group_user| + create(:todo, + user: group_user, + state: :pending, + action: Todo::MEMBER_ACCESS_REQUESTED, + author: group_requester.user, + target: group + ) + end - expect(request_handler_todo.reload).to be_done + expect do + service.resolve_access_request_todos(group_requester) + end.to change { + Todo.pending.where(target: group).for_author(group_requester.user) + .for_action(Todo::MEMBER_ACCESS_REQUESTED).count + }.from(2).to(0) + + expect(access_request_todos.each(&:reload)).to all be_done + expect(another_pending_todo.reload).not_to be_done + expect(another_group_todo.reload).not_to be_done + end + + it 'marks the todos for project access request handlers as done' do + # The project has 1 owner already. Adding another owner here + project.add_member(john_doe, Gitlab::Access::OWNER) + + access_request_todo = create(:todo, + user: john_doe, + state: :pending, + action: Todo::MEMBER_ACCESS_REQUESTED, + author: project_requester.user, + target: project + ) + + expect do + service.resolve_access_request_todos(project_requester) + end.to change { + Todo.pending.where(target: project).for_author(project_requester.user) + .for_action(Todo::MEMBER_ACCESS_REQUESTED).count + }.from(2).to(0) # The original owner todo was created with the pending access request + + expect(access_request_todo.reload).to be_done + expect(another_pending_todo.reload).to be_pending + expect(another_project_todo.reload).to be_pending end end diff --git a/spec/services/todos/allowed_target_filter_service_spec.rb b/spec/services/todos/allowed_target_filter_service_spec.rb index 1d2b1b044db..3929e3788d0 100644 --- a/spec/services/todos/allowed_target_filter_service_spec.rb +++ b/spec/services/todos/allowed_target_filter_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Todos::AllowedTargetFilterService do +RSpec.describe Todos::AllowedTargetFilterService, feature_category: :team_planning do include DesignManagementTestHelpers let_it_be(:authorized_group) { create(:group, :private) } diff --git a/spec/services/todos/destroy/confidential_issue_service_spec.rb b/spec/services/todos/destroy/confidential_issue_service_spec.rb index e3dcc2bae95..9de71faf8bf 100644 --- a/spec/services/todos/destroy/confidential_issue_service_spec.rb +++ b/spec/services/todos/destroy/confidential_issue_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Todos::Destroy::ConfidentialIssueService do +RSpec.describe Todos::Destroy::ConfidentialIssueService, feature_category: :team_planning do let(:project) { create(:project, :public) } let(:user) { create(:user) } let(:author) { create(:user) } diff --git a/spec/services/todos/destroy/design_service_spec.rb b/spec/services/todos/destroy/design_service_spec.rb index 92b25d94dc6..628398e7062 100644 --- a/spec/services/todos/destroy/design_service_spec.rb +++ b/spec/services/todos/destroy/design_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Todos::Destroy::DesignService do +RSpec.describe Todos::Destroy::DesignService, feature_category: :design_management do let_it_be(:user) { create(:user) } let_it_be(:user_2) { create(:user) } let_it_be(:design) { create(:design) } diff --git a/spec/services/todos/destroy/destroyed_issuable_service_spec.rb b/spec/services/todos/destroy/destroyed_issuable_service_spec.rb index 6d6abe06d1c..63ff189ede5 100644 --- a/spec/services/todos/destroy/destroyed_issuable_service_spec.rb +++ b/spec/services/todos/destroy/destroyed_issuable_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Todos::Destroy::DestroyedIssuableService do +RSpec.describe Todos::Destroy::DestroyedIssuableService, feature_category: :team_planning do describe '#execute' do let_it_be(:user) { create(:user) } diff --git a/spec/services/todos/destroy/project_private_service_spec.rb b/spec/services/todos/destroy/project_private_service_spec.rb index 1d1c010535d..cc15f6eab8b 100644 --- a/spec/services/todos/destroy/project_private_service_spec.rb +++ b/spec/services/todos/destroy/project_private_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Todos::Destroy::ProjectPrivateService do +RSpec.describe Todos::Destroy::ProjectPrivateService, feature_category: :team_planning do let(:group) { create(:group, :public) } let(:project) { create(:project, :public, group: group) } let(:user) { create(:user) } diff --git a/spec/services/todos/destroy/unauthorized_features_service_spec.rb b/spec/services/todos/destroy/unauthorized_features_service_spec.rb index 5f6c9b0cdf0..c02c0dfd5c8 100644 --- a/spec/services/todos/destroy/unauthorized_features_service_spec.rb +++ b/spec/services/todos/destroy/unauthorized_features_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Todos::Destroy::UnauthorizedFeaturesService do +RSpec.describe Todos::Destroy::UnauthorizedFeaturesService, feature_category: :team_planning do let_it_be(:project, reload: true) { create(:project, :public, :repository) } let_it_be(:issue) { create(:issue, project: project) } let_it_be(:mr) { create(:merge_request, source_project: project) } diff --git a/spec/services/topics/merge_service_spec.rb b/spec/services/topics/merge_service_spec.rb index 98247250a61..705b222b39e 100644 --- a/spec/services/topics/merge_service_spec.rb +++ b/spec/services/topics/merge_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Topics::MergeService do +RSpec.describe Topics::MergeService, feature_category: :shared do let_it_be(:source_topic) { create(:topic, name: 'source_topic') } let_it_be(:target_topic) { create(:topic, name: 'target_topic') } let_it_be(:project_1) { create(:project, :public, topic_list: source_topic.name) } @@ -47,7 +47,7 @@ RSpec.describe Topics::MergeService do where(:source_topic_parameter, :target_topic_parameter, :expected_message) do nil | ref(:target_topic) | 'The source topic is not a topic.' ref(:source_topic) | nil | 'The target topic is not a topic.' - ref(:target_topic) | ref(:target_topic) | 'The source topic and the target topic are identical.' # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + ref(:target_topic) | ref(:target_topic) | 'The source topic and the target topic are identical.' end with_them do diff --git a/spec/services/two_factor/destroy_service_spec.rb b/spec/services/two_factor/destroy_service_spec.rb index 30c189520fd..0811ce336c8 100644 --- a/spec/services/two_factor/destroy_service_spec.rb +++ b/spec/services/two_factor/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe TwoFactor::DestroyService do +RSpec.describe TwoFactor::DestroyService, feature_category: :system_access do let_it_be(:current_user) { create(:user) } subject { described_class.new(current_user, user: user).execute } diff --git a/spec/services/update_container_registry_info_service_spec.rb b/spec/services/update_container_registry_info_service_spec.rb index 64071e79508..416b08bd04b 100644 --- a/spec/services/update_container_registry_info_service_spec.rb +++ b/spec/services/update_container_registry_info_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe UpdateContainerRegistryInfoService do +RSpec.describe UpdateContainerRegistryInfoService, feature_category: :container_registry do let_it_be(:application_settings) { Gitlab::CurrentSettings } let_it_be(:api_url) { 'http://registry.gitlab' } diff --git a/spec/services/update_merge_request_metrics_service_spec.rb b/spec/services/update_merge_request_metrics_service_spec.rb index a07fcee91e4..f30836fbaf5 100644 --- a/spec/services/update_merge_request_metrics_service_spec.rb +++ b/spec/services/update_merge_request_metrics_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe MergeRequestMetricsService do +RSpec.describe MergeRequestMetricsService, feature_category: :code_review_workflow do let(:metrics) { create(:merge_request).metrics } describe '#merge' do diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb index 48aa65451f3..518d12d5b41 100644 --- a/spec/services/upload_service_spec.rb +++ b/spec/services/upload_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe UploadService do +RSpec.describe UploadService, feature_category: :shared do describe 'File service' do before do @user = create(:user) diff --git a/spec/services/uploads/destroy_service_spec.rb b/spec/services/uploads/destroy_service_spec.rb index bb58da231b6..76ac2ec245e 100644 --- a/spec/services/uploads/destroy_service_spec.rb +++ b/spec/services/uploads/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Uploads::DestroyService do +RSpec.describe Uploads::DestroyService, feature_category: :shared do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be_with_reload(:upload) { create(:upload, :issuable_upload, model: project) } diff --git a/spec/services/user_preferences/update_service_spec.rb b/spec/services/user_preferences/update_service_spec.rb index 59089a4a7af..601a023c30d 100644 --- a/spec/services/user_preferences/update_service_spec.rb +++ b/spec/services/user_preferences/update_service_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' -RSpec.describe UserPreferences::UpdateService do +RSpec.describe UserPreferences::UpdateService, feature_category: :user_profile do let(:user) { create(:user) } - let(:params) { { view_diffs_file_by_file: false } } + let(:params) { { view_diffs_file_by_file: false, pass_user_identities_to_ci_jwt: true } } describe '#execute' do subject(:service) { described_class.new(user, params) } @@ -15,6 +15,8 @@ RSpec.describe UserPreferences::UpdateService do expect(result.status).to eq(:success) expect(result.payload[:preferences].view_diffs_file_by_file).to eq(params[:view_diffs_file_by_file]) + expect(result.payload[:preferences].pass_user_identities_to_ci_jwt + ).to eq(params[:pass_user_identities_to_ci_jwt]) end end diff --git a/spec/services/user_project_access_changed_service_spec.rb b/spec/services/user_project_access_changed_service_spec.rb index 356675d55f2..563af8e7e9e 100644 --- a/spec/services/user_project_access_changed_service_spec.rb +++ b/spec/services/user_project_access_changed_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe UserProjectAccessChangedService, feature_category: :authentication_and_authorization do +RSpec.describe UserProjectAccessChangedService, feature_category: :system_access do describe '#execute' do it 'permits high-priority operation' do expect(AuthorizedProjectsWorker).to receive(:bulk_perform_async) diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb index 6c0d93f568a..e2141f9bf6f 100644 --- a/spec/services/users/activity_service_spec.rb +++ b/spec/services/users/activity_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::ActivityService do +RSpec.describe Users::ActivityService, feature_category: :user_profile do include ExclusiveLeaseHelpers let(:user) { create(:user, last_activity_on: last_activity_on) } @@ -57,7 +57,6 @@ RSpec.describe Users::ActivityService do it_behaves_like 'Snowplow event tracking with RedisHLL context' do subject(:record_activity) { described_class.new(author: user, namespace: namespace, project: project).execute } - let(:feature_flag_name) { :route_hll_to_snowplow_phase3 } let(:category) { described_class.name } let(:action) { 'perform_action' } let(:label) { 'redis_hll_counters.manage.unique_active_users_monthly' } diff --git a/spec/services/users/approve_service_spec.rb b/spec/services/users/approve_service_spec.rb index 34eb5b18ff6..09379857c38 100644 --- a/spec/services/users/approve_service_spec.rb +++ b/spec/services/users/approve_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::ApproveService do +RSpec.describe Users::ApproveService, feature_category: :user_management do let_it_be(:current_user) { create(:admin) } let(:user) { create(:user, :blocked_pending_approval) } @@ -75,6 +75,24 @@ RSpec.describe Users::ApproveService do expect { subject }.to have_enqueued_mail(DeviseMailer, :user_admin_approval) end + context 'when the user was created via sign up' do + it 'does not send a password reset email' do + expect { subject }.not_to have_enqueued_mail(Notify, :new_user_email) + end + end + + context 'when the user was created by an admin' do + let(:user) { create(:user, :blocked_pending_approval, created_by_id: current_user.id) } + + it 'sends a password reset email' do + allow(user).to receive(:generate_reset_token).and_return(:reset_token) + + expect(Notify).to receive(:new_user_email).with(user.id, :reset_token).and_call_original + + expect { subject }.to have_enqueued_mail(Notify, :new_user_email) + end + end + context 'email confirmation status' do context 'user is unconfirmed' do let(:user) { create(:user, :blocked_pending_approval, :unconfirmed) } diff --git a/spec/services/users/authorized_build_service_spec.rb b/spec/services/users/authorized_build_service_spec.rb index 57a122cbf35..7eed6833cba 100644 --- a/spec/services/users/authorized_build_service_spec.rb +++ b/spec/services/users/authorized_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::AuthorizedBuildService do +RSpec.describe Users::AuthorizedBuildService, feature_category: :user_management do describe '#execute' do let_it_be(:current_user) { create(:user) } diff --git a/spec/services/users/ban_service_spec.rb b/spec/services/users/ban_service_spec.rb index 3f9c7ebf067..5be5de82e91 100644 --- a/spec/services/users/ban_service_spec.rb +++ b/spec/services/users/ban_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::BanService do +RSpec.describe Users::BanService, feature_category: :user_management do let(:user) { create(:user) } let_it_be(:current_user) { create(:admin) } diff --git a/spec/services/users/banned_user_base_service_spec.rb b/spec/services/users/banned_user_base_service_spec.rb index 29a549f0f49..65b24e08d80 100644 --- a/spec/services/users/banned_user_base_service_spec.rb +++ b/spec/services/users/banned_user_base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::BannedUserBaseService do +RSpec.describe Users::BannedUserBaseService, feature_category: :user_management do let(:admin) { create(:admin) } let(:base_service) { described_class.new(admin) } diff --git a/spec/services/users/batch_status_cleaner_service_spec.rb b/spec/services/users/batch_status_cleaner_service_spec.rb index 46a004542d8..8feec761fd0 100644 --- a/spec/services/users/batch_status_cleaner_service_spec.rb +++ b/spec/services/users/batch_status_cleaner_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::BatchStatusCleanerService do +RSpec.describe Users::BatchStatusCleanerService, feature_category: :user_management do let_it_be(:user_status_1) { create(:user_status, emoji: 'coffee', message: 'msg1', clear_status_at: 1.year.ago) } let_it_be(:user_status_2) { create(:user_status, emoji: 'coffee', message: 'msg1', clear_status_at: 1.year.from_now) } let_it_be(:user_status_3) { create(:user_status, emoji: 'coffee', message: 'msg1', clear_status_at: 2.years.ago) } diff --git a/spec/services/users/block_service_spec.rb b/spec/services/users/block_service_spec.rb index 7ff9a887f38..63aa375c8af 100644 --- a/spec/services/users/block_service_spec.rb +++ b/spec/services/users/block_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::BlockService do +RSpec.describe Users::BlockService, feature_category: :user_management do let_it_be(:current_user) { create(:admin) } subject(:service) { described_class.new(current_user) } diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb index 98fe6d9b5ba..f3236d40412 100644 --- a/spec/services/users/build_service_spec.rb +++ b/spec/services/users/build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::BuildService do +RSpec.describe Users::BuildService, feature_category: :user_management do using RSpec::Parameterized::TableSyntax describe '#execute' do diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb index f3c9701c556..eac4faa2042 100644 --- a/spec/services/users/create_service_spec.rb +++ b/spec/services/users/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::CreateService do +RSpec.describe Users::CreateService, feature_category: :user_management do describe '#execute' do let(:password) { User.random_password } let(:admin_user) { create(:admin) } diff --git a/spec/services/users/deactivate_service_spec.rb b/spec/services/users/deactivate_service_spec.rb new file mode 100644 index 00000000000..0bb6e51a3b1 --- /dev/null +++ b/spec/services/users/deactivate_service_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Users::DeactivateService, feature_category: :user_management do + let_it_be(:current_user) { build(:admin) } + let_it_be(:user) { build(:user) } + + subject(:service) { described_class.new(current_user) } + + describe '#execute' do + subject(:operation) { service.execute(user) } + + context 'when successful', :enable_admin_mode do + let(:user) { create(:user) } + + it 'returns success status' do + expect(operation[:status]).to eq(:success) + end + + it "changes the user's state" do + expect { operation }.to change { user.state }.to('deactivated') + end + + it 'creates a log entry' do + expect(Gitlab::AppLogger).to receive(:info).with(message: "User deactivated", user: user.username, + email: user.email, deactivated_by: current_user.username, ip_address: current_user.current_sign_in_ip.to_s) + + operation + end + end + + context 'when the user is already deactivated', :enable_admin_mode do + let(:user) { create(:user, :deactivated) } + + it 'returns error result' do + aggregate_failures 'error result' do + expect(operation[:status]).to eq(:success) + expect(operation[:message]).to eq('User has already been deactivated') + end + end + + it "does not change the user's state" do + expect { operation }.not_to change { user.state } + end + end + + context 'when internal user', :enable_admin_mode do + let(:user) { create(:user, :bot) } + + it 'returns an error message' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq('Internal users cannot be deactivated') + expect(operation.reason).to eq :forbidden + end + end + + context 'when user is blocked', :enable_admin_mode do + let(:user) { create(:user, :blocked) } + + it 'returns an error message' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq('Error occurred. A blocked user cannot be deactivated') + expect(operation.reason).to eq :forbidden + end + end + + context 'when user is not an admin' do + it 'returns permissions error message' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq("You are not authorized to perform this action") + expect(operation.reason).to eq :forbidden + end + end + + context 'when skip_authorization is true' do + let(:non_admin_user) { create(:user) } + let(:user_to_deactivate) { create(:user) } + let(:skip_authorization_service) { described_class.new(non_admin_user, skip_authorization: true) } + + it 'deactivates the user even if the current user is not an admin' do + expect(skip_authorization_service.execute(user_to_deactivate)[:status]).to eq(:success) + end + end + end +end diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 18ad946b289..5cd11efe942 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::DestroyService do +RSpec.describe Users::DestroyService, feature_category: :user_management do let!(:user) { create(:user) } let!(:admin) { create(:admin) } let!(:namespace) { user.namespace } diff --git a/spec/services/users/dismiss_callout_service_spec.rb b/spec/services/users/dismiss_callout_service_spec.rb index 6ba9f180444..776388ef5f1 100644 --- a/spec/services/users/dismiss_callout_service_spec.rb +++ b/spec/services/users/dismiss_callout_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::DismissCalloutService do +RSpec.describe Users::DismissCalloutService, feature_category: :user_management do describe '#execute' do let_it_be(:user) { create(:user) } diff --git a/spec/services/users/dismiss_group_callout_service_spec.rb b/spec/services/users/dismiss_group_callout_service_spec.rb index d74602a7606..a653fa7ee00 100644 --- a/spec/services/users/dismiss_group_callout_service_spec.rb +++ b/spec/services/users/dismiss_group_callout_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::DismissGroupCalloutService do +RSpec.describe Users::DismissGroupCalloutService, feature_category: :user_management do describe '#execute' do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } diff --git a/spec/services/users/dismiss_project_callout_service_spec.rb b/spec/services/users/dismiss_project_callout_service_spec.rb index 73e50a4c37d..7bcb11e4dbc 100644 --- a/spec/services/users/dismiss_project_callout_service_spec.rb +++ b/spec/services/users/dismiss_project_callout_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::DismissProjectCalloutService do +RSpec.describe Users::DismissProjectCalloutService, feature_category: :user_management do describe '#execute' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/services/users/email_verification/generate_token_service_spec.rb b/spec/services/users/email_verification/generate_token_service_spec.rb index e7aa1bf8306..8b2b57cbf62 100644 --- a/spec/services/users/email_verification/generate_token_service_spec.rb +++ b/spec/services/users/email_verification/generate_token_service_spec.rb @@ -2,12 +2,13 @@ require 'spec_helper' -RSpec.describe Users::EmailVerification::GenerateTokenService do +RSpec.describe Users::EmailVerification::GenerateTokenService, feature_category: :system_access do using RSpec::Parameterized::TableSyntax - let(:service) { described_class.new(attr: attr) } + let(:user) { build_stubbed(:user) } + let(:service) { described_class.new(attr: attr, user: user) } let(:token) { 'token' } - let(:digest) { Devise.token_generator.digest(User, attr, token) } + let(:digest) { service.send(:digest) } describe '#execute' do context 'with a valid attribute' do @@ -33,5 +34,21 @@ RSpec.describe Users::EmailVerification::GenerateTokenService do expect { service.execute }.to raise_error(ArgumentError, 'Invalid attribute') end end + + context 'when similar tokens are generated' do + let(:attr) { :confirmation_token } + + before do + allow_next_instance_of(described_class) do |service| + allow(service).to receive(:generate_token).and_return(token) + end + end + + it 'generates a unique digest' do + second_service = described_class.new(attr: attr, user: build_stubbed(:user)) + + expect(service.execute[1]).not_to eq(second_service.execute[1]) + end + end end end diff --git a/spec/services/users/email_verification/validate_token_service_spec.rb b/spec/services/users/email_verification/validate_token_service_spec.rb index 44af4a4d36f..c8924bc20b7 100644 --- a/spec/services/users/email_verification/validate_token_service_spec.rb +++ b/spec/services/users/email_verification/validate_token_service_spec.rb @@ -2,15 +2,16 @@ require 'spec_helper' -RSpec.describe Users::EmailVerification::ValidateTokenService, :clean_gitlab_redis_rate_limiting do +RSpec.describe Users::EmailVerification::ValidateTokenService, :clean_gitlab_redis_rate_limiting, feature_category: :system_access do using RSpec::Parameterized::TableSyntax let(:service) { described_class.new(attr: attr, user: user, token: token) } + let(:email) { build_stubbed(:user).email } let(:token) { 'token' } - let(:encrypted_token) { Devise.token_generator.digest(User, attr, token) } + let(:encrypted_token) { Devise.token_generator.digest(User, email, token) } let(:generated_at_attr) { attr == :unlock_token ? :locked_at : :confirmation_sent_at } let(:token_generated_at) { 1.minute.ago } - let(:user) { build(:user, attr => encrypted_token, generated_at_attr => token_generated_at) } + let(:user) { build(:user, email: email, attr => encrypted_token, generated_at_attr => token_generated_at) } describe '#execute' do context 'with a valid attribute' do diff --git a/spec/services/users/in_product_marketing_email_records_spec.rb b/spec/services/users/in_product_marketing_email_records_spec.rb index 0b9400dcd12..059f0890b53 100644 --- a/spec/services/users/in_product_marketing_email_records_spec.rb +++ b/spec/services/users/in_product_marketing_email_records_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::InProductMarketingEmailRecords do +RSpec.describe Users::InProductMarketingEmailRecords, feature_category: :onboarding do let_it_be(:user) { create :user } subject(:records) { described_class.new } diff --git a/spec/services/users/keys_count_service_spec.rb b/spec/services/users/keys_count_service_spec.rb index 607d2946b2c..258fe351e4b 100644 --- a/spec/services/users/keys_count_service_spec.rb +++ b/spec/services/users/keys_count_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::KeysCountService, :use_clean_rails_memory_store_caching do +RSpec.describe Users::KeysCountService, :use_clean_rails_memory_store_caching, feature_category: :system_access do let(:user) { create(:user) } subject { described_class.new(user) } diff --git a/spec/services/users/last_push_event_service_spec.rb b/spec/services/users/last_push_event_service_spec.rb index 5b755db407f..fe61f12fe1a 100644 --- a/spec/services/users/last_push_event_service_spec.rb +++ b/spec/services/users/last_push_event_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::LastPushEventService do +RSpec.describe Users::LastPushEventService, feature_category: :source_code_management do let(:user) { build(:user, id: 1) } let(:project) { build(:project, id: 2) } let(:event) { build(:push_event, id: 3, author: user, project: project) } diff --git a/spec/services/users/migrate_records_to_ghost_user_in_batches_service_spec.rb b/spec/services/users/migrate_records_to_ghost_user_in_batches_service_spec.rb index 107ff82016c..0b9f92a868e 100644 --- a/spec/services/users/migrate_records_to_ghost_user_in_batches_service_spec.rb +++ b/spec/services/users/migrate_records_to_ghost_user_in_batches_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::MigrateRecordsToGhostUserInBatchesService do +RSpec.describe Users::MigrateRecordsToGhostUserInBatchesService, feature_category: :user_management do let(:service) { described_class.new } let_it_be(:ghost_user_migration) { create(:ghost_user_migration) } diff --git a/spec/services/users/migrate_records_to_ghost_user_service_spec.rb b/spec/services/users/migrate_records_to_ghost_user_service_spec.rb index 827d6f652a4..cfa0ddff04d 100644 --- a/spec/services/users/migrate_records_to_ghost_user_service_spec.rb +++ b/spec/services/users/migrate_records_to_ghost_user_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::MigrateRecordsToGhostUserService do +RSpec.describe Users::MigrateRecordsToGhostUserService, feature_category: :user_management do include BatchDestroyDependentAssociationsHelper let!(:user) { create(:user) } diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb index e33886d2add..55b27954a74 100644 --- a/spec/services/users/refresh_authorized_projects_service_spec.rb +++ b/spec/services/users/refresh_authorized_projects_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::RefreshAuthorizedProjectsService do +RSpec.describe Users::RefreshAuthorizedProjectsService, feature_category: :user_management do include ExclusiveLeaseHelpers # We're using let! here so that any expectations for the service class are not diff --git a/spec/services/users/registrations_build_service_spec.rb b/spec/services/users/registrations_build_service_spec.rb index fa53a4cc604..736db855fe0 100644 --- a/spec/services/users/registrations_build_service_spec.rb +++ b/spec/services/users/registrations_build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::RegistrationsBuildService do +RSpec.describe Users::RegistrationsBuildService, feature_category: :system_access do describe '#execute' do let(:base_params) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) } let(:skip_param) { {} } diff --git a/spec/services/users/reject_service_spec.rb b/spec/services/users/reject_service_spec.rb index 37d003c5dac..f72666d8a63 100644 --- a/spec/services/users/reject_service_spec.rb +++ b/spec/services/users/reject_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::RejectService do +RSpec.describe Users::RejectService, feature_category: :user_management do let_it_be(:current_user) { create(:admin) } let(:user) { create(:user, :blocked_pending_approval) } diff --git a/spec/services/users/repair_ldap_blocked_service_spec.rb b/spec/services/users/repair_ldap_blocked_service_spec.rb index 54540d68af2..424c14ccdbc 100644 --- a/spec/services/users/repair_ldap_blocked_service_spec.rb +++ b/spec/services/users/repair_ldap_blocked_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::RepairLdapBlockedService do +RSpec.describe Users::RepairLdapBlockedService, feature_category: :system_access do let(:user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') } let(:identity) { user.ldap_identity } diff --git a/spec/services/users/respond_to_terms_service_spec.rb b/spec/services/users/respond_to_terms_service_spec.rb index 1997dcd0e04..dc33f98535a 100644 --- a/spec/services/users/respond_to_terms_service_spec.rb +++ b/spec/services/users/respond_to_terms_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::RespondToTermsService do +RSpec.describe Users::RespondToTermsService, feature_category: :user_profile do let(:user) { create(:user) } let(:term) { create(:term) } diff --git a/spec/services/users/saved_replies/create_service_spec.rb b/spec/services/users/saved_replies/create_service_spec.rb index e01b6248308..ee42a53a220 100644 --- a/spec/services/users/saved_replies/create_service_spec.rb +++ b/spec/services/users/saved_replies/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::SavedReplies::CreateService do +RSpec.describe Users::SavedReplies::CreateService, feature_category: :team_planning do describe '#execute' do let_it_be(:current_user) { create(:user) } let_it_be(:saved_reply) { create(:saved_reply, user: current_user) } diff --git a/spec/services/users/saved_replies/destroy_service_spec.rb b/spec/services/users/saved_replies/destroy_service_spec.rb index cb97fac7b7c..41c2013e3df 100644 --- a/spec/services/users/saved_replies/destroy_service_spec.rb +++ b/spec/services/users/saved_replies/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::SavedReplies::DestroyService do +RSpec.describe Users::SavedReplies::DestroyService, feature_category: :team_planning do describe '#execute' do let!(:saved_reply) { create(:saved_reply) } diff --git a/spec/services/users/saved_replies/update_service_spec.rb b/spec/services/users/saved_replies/update_service_spec.rb index bdb54d7c8f7..c18b7395040 100644 --- a/spec/services/users/saved_replies/update_service_spec.rb +++ b/spec/services/users/saved_replies/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::SavedReplies::UpdateService do +RSpec.describe Users::SavedReplies::UpdateService, feature_category: :team_planning do describe '#execute' do let_it_be(:current_user) { create(:user) } let_it_be(:saved_reply) { create(:saved_reply, user: current_user) } diff --git a/spec/services/users/set_status_service_spec.rb b/spec/services/users/set_status_service_spec.rb index 76e86506d94..b75c558785f 100644 --- a/spec/services/users/set_status_service_spec.rb +++ b/spec/services/users/set_status_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::SetStatusService do +RSpec.describe Users::SetStatusService, feature_category: :user_management do let(:current_user) { create(:user) } subject(:service) { described_class.new(current_user, params) } diff --git a/spec/services/users/signup_service_spec.rb b/spec/services/users/signup_service_spec.rb index ef532e01d0b..29663411346 100644 --- a/spec/services/users/signup_service_spec.rb +++ b/spec/services/users/signup_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::SignupService do +RSpec.describe Users::SignupService, feature_category: :system_access do let(:user) { create(:user, setup_for_company: true) } describe '#execute' do @@ -48,11 +48,7 @@ RSpec.describe Users::SignupService do expect(user.reload.setup_for_company).to be(false) end - context 'when on .com' do - before do - allow(Gitlab).to receive(:com?).and_return(true) - end - + context 'when on SaaS', :saas do it 'returns an error result when setup_for_company is missing' do result = update_user(user, setup_for_company: '') diff --git a/spec/services/users/unban_service_spec.rb b/spec/services/users/unban_service_spec.rb index 3dcb8450e7b..20fe40b370f 100644 --- a/spec/services/users/unban_service_spec.rb +++ b/spec/services/users/unban_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::UnbanService do +RSpec.describe Users::UnbanService, feature_category: :user_management do let(:user) { create(:user) } let_it_be(:current_user) { create(:admin) } diff --git a/spec/services/users/unblock_service_spec.rb b/spec/services/users/unblock_service_spec.rb index 25ee99427ab..95a077d6100 100644 --- a/spec/services/users/unblock_service_spec.rb +++ b/spec/services/users/unblock_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::UnblockService do +RSpec.describe Users::UnblockService, feature_category: :user_management do let_it_be(:current_user) { create(:admin) } subject(:service) { described_class.new(current_user) } diff --git a/spec/services/users/update_canonical_email_service_spec.rb b/spec/services/users/update_canonical_email_service_spec.rb index 1dead13d338..d3c414f6db4 100644 --- a/spec/services/users/update_canonical_email_service_spec.rb +++ b/spec/services/users/update_canonical_email_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::UpdateCanonicalEmailService do +RSpec.describe Users::UpdateCanonicalEmailService, feature_category: :user_profile do let(:other_email) { "differentaddress@includeddomain.com" } before do @@ -92,23 +92,25 @@ RSpec.describe Users::UpdateCanonicalEmailService do context 'when the user email is not processable' do [nil, 'nonsense'].each do |invalid_address| - before do - user.email = invalid_address - end + context "with #{invalid_address}" do + before do + user.email = invalid_address + end - specify do - subject.execute + specify do + subject.execute - expect(user.user_canonical_email).to be_nil - end + expect(user.user_canonical_email).to be_nil + end - it 'preserves any existing record' do - user.email = nil - user.user_canonical_email = build(:user_canonical_email, canonical_email: other_email) + it 'preserves any existing record' do + user.email = nil + user.user_canonical_email = build(:user_canonical_email, canonical_email: other_email) - subject.execute + subject.execute - expect(user.user_canonical_email.canonical_email).to eq other_email + expect(user.user_canonical_email.canonical_email).to eq other_email + end end end end diff --git a/spec/services/users/update_highest_member_role_service_spec.rb b/spec/services/users/update_highest_member_role_service_spec.rb index 89ddd635bb6..06f4d787d72 100644 --- a/spec/services/users/update_highest_member_role_service_spec.rb +++ b/spec/services/users/update_highest_member_role_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::UpdateHighestMemberRoleService do +RSpec.describe Users::UpdateHighestMemberRoleService, feature_category: :user_management do let(:user) { create(:user) } let(:execute_service) { described_class.new(user).execute } diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index f4ea757f81a..9ff3d9208fa 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -2,18 +2,17 @@ require 'spec_helper' -RSpec.describe Users::UpdateService do +RSpec.describe Users::UpdateService, feature_category: :user_profile do let(:password) { User.random_password } let(:user) { create(:user, password: password, password_confirmation: password) } describe '#execute' do it 'updates time preferences' do - result = update_user(user, timezone: 'Europe/Warsaw', time_display_relative: true, time_format_in_24h: false) + result = update_user(user, timezone: 'Europe/Warsaw', time_display_relative: true) expect(result).to eq(status: :success) expect(user.reload.timezone).to eq('Europe/Warsaw') expect(user.time_display_relative).to eq(true) - expect(user.time_format_in_24h).to eq(false) end it 'returns an error result when record cannot be updated' do @@ -185,6 +184,49 @@ RSpec.describe Users::UpdateService do end.not_to raise_error end + describe 'updates the enabled_following' do + let(:user) { create(:user) } + + before do + 3.times do + user.follow(create(:user)) + create(:user).follow(user) + end + user.reload + end + + it 'removes followers and followees' do + expect do + update_user(user, enabled_following: false) + end.to change { user.followed_users.count }.from(3).to(0) + .and change { user.following_users.count }.from(3).to(0) + expect(user.enabled_following).to eq(false) + end + + it 'does not remove followers/followees if feature flag is off' do + stub_feature_flags(disable_follow_users: false) + + expect do + update_user(user, enabled_following: false) + end.to not_change { user.followed_users.count } + .and not_change { user.following_users.count } + end + + context 'when there is more followers/followees then batch limit' do + before do + stub_env('BATCH_SIZE', 1) + end + + it 'removes followers and followees' do + expect do + update_user(user, enabled_following: false) + end.to change { user.followed_users.count }.from(3).to(0) + .and change { user.following_users.count }.from(3).to(0) + expect(user.enabled_following).to eq(false) + end + end + end + def update_user(user, opts) described_class.new(user, opts.merge(user: user)).execute end diff --git a/spec/services/users/update_todo_count_cache_service_spec.rb b/spec/services/users/update_todo_count_cache_service_spec.rb index 3d96af928df..eec637cf5b4 100644 --- a/spec/services/users/update_todo_count_cache_service_spec.rb +++ b/spec/services/users/update_todo_count_cache_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::UpdateTodoCountCacheService do +RSpec.describe Users::UpdateTodoCountCacheService, feature_category: :team_planning do describe '#execute' do let_it_be(:user1) { create(:user) } let_it_be(:user2) { create(:user) } diff --git a/spec/services/users/upsert_credit_card_validation_service_spec.rb b/spec/services/users/upsert_credit_card_validation_service_spec.rb index ac7e619612f..ebd2502398d 100644 --- a/spec/services/users/upsert_credit_card_validation_service_spec.rb +++ b/spec/services/users/upsert_credit_card_validation_service_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe Users::UpsertCreditCardValidationService do - let_it_be(:user) { create(:user, requires_credit_card_verification: true) } +RSpec.describe Users::UpsertCreditCardValidationService, feature_category: :user_profile do + let_it_be(:user) { create(:user) } let(:user_id) { user.id } let(:credit_card_validated_time) { Time.utc(2020, 1, 1) } @@ -21,7 +21,7 @@ RSpec.describe Users::UpsertCreditCardValidationService do end describe '#execute' do - subject(:service) { described_class.new(params, user) } + subject(:service) { described_class.new(params) } context 'successfully set credit card validation record for the user' do context 'when user does not have credit card validation record' do @@ -42,10 +42,6 @@ RSpec.describe Users::UpsertCreditCardValidationService do expiration_date: Date.new(expiration_year, 1, 31) ) end - - it 'sets the requires_credit_card_verification attribute on the user to false' do - expect { service.execute }.to change { user.reload.requires_credit_card_verification }.to(false) - end end context 'when user has credit card validation record' do diff --git a/spec/services/users/validate_manual_otp_service_spec.rb b/spec/services/users/validate_manual_otp_service_spec.rb index d71735814f2..9a6083bc41c 100644 --- a/spec/services/users/validate_manual_otp_service_spec.rb +++ b/spec/services/users/validate_manual_otp_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::ValidateManualOtpService do +RSpec.describe Users::ValidateManualOtpService, feature_category: :user_profile do let_it_be(:user) { create(:user) } let(:otp_code) { 42 } @@ -32,6 +32,20 @@ RSpec.describe Users::ValidateManualOtpService do validate end + + it 'handles unexpected error' do + error_message = "boom!" + + expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::FortiAuthenticator::ManualOtp) do |strategy| + expect(strategy).to receive(:validate).with(otp_code).once.and_raise(StandardError, error_message) + end + expect(Gitlab::ErrorTracking).to receive(:log_exception) + + result = validate + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq(error_message) + end end context 'FortiTokenCloud' do @@ -49,16 +63,23 @@ RSpec.describe Users::ValidateManualOtpService do end end - context 'unexpected error' do + context 'DuoAuth' do before do - stub_feature_flags(forti_authenticator: user) - allow(::Gitlab.config.forti_authenticator).to receive(:enabled).and_return(true) + allow(::Gitlab.config.duo_auth).to receive(:enabled).and_return(true) end - it 'returns error' do + it 'calls DuoAuth strategy' do + expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::DuoAuth::ManualOtp) do |strategy| + expect(strategy).to receive(:validate).with(otp_code).once + end + + validate + end + + it "handles unexpected error" do error_message = "boom!" - expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::FortiAuthenticator::ManualOtp) do |strategy| + expect_next_instance_of(::Gitlab::Auth::Otp::Strategies::DuoAuth::ManualOtp) do |strategy| expect(strategy).to receive(:validate).with(otp_code).once.and_raise(StandardError, error_message) end expect(Gitlab::ErrorTracking).to receive(:log_exception) diff --git a/spec/services/users/validate_push_otp_service_spec.rb b/spec/services/users/validate_push_otp_service_spec.rb index 960b6bcd3bb..4ef374cbb7f 100644 --- a/spec/services/users/validate_push_otp_service_spec.rb +++ b/spec/services/users/validate_push_otp_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Users::ValidatePushOtpService do +RSpec.describe Users::ValidatePushOtpService, feature_category: :user_profile do let_it_be(:user) { create(:user) } subject(:validate) { described_class.new(user).execute } diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb index 42f7ebc85f9..d66d584d3d0 100644 --- a/spec/services/verify_pages_domain_service_spec.rb +++ b/spec/services/verify_pages_domain_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe VerifyPagesDomainService do +RSpec.describe VerifyPagesDomainService, feature_category: :pages do using RSpec::Parameterized::TableSyntax include EmailHelpers diff --git a/spec/services/web_hooks/destroy_service_spec.rb b/spec/services/web_hooks/destroy_service_spec.rb index ca8cb8a1b75..642c25ab312 100644 --- a/spec/services/web_hooks/destroy_service_spec.rb +++ b/spec/services/web_hooks/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WebHooks::DestroyService do +RSpec.describe WebHooks::DestroyService, feature_category: :webhooks do let_it_be(:user) { create(:user) } subject { described_class.new(user) } diff --git a/spec/services/web_hooks/log_destroy_service_spec.rb b/spec/services/web_hooks/log_destroy_service_spec.rb index 7634726e5a4..b0444b659ba 100644 --- a/spec/services/web_hooks/log_destroy_service_spec.rb +++ b/spec/services/web_hooks/log_destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WebHooks::LogDestroyService do +RSpec.describe WebHooks::LogDestroyService, feature_category: :webhooks do subject(:service) { described_class.new(hook.id) } describe '#execute' do diff --git a/spec/services/web_hooks/log_execution_service_spec.rb b/spec/services/web_hooks/log_execution_service_spec.rb index 8a845f60ad2..f56c07386fa 100644 --- a/spec/services/web_hooks/log_execution_service_spec.rb +++ b/spec/services/web_hooks/log_execution_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WebHooks::LogExecutionService do +RSpec.describe WebHooks::LogExecutionService, feature_category: :webhooks do include ExclusiveLeaseHelpers using RSpec::Parameterized::TableSyntax diff --git a/spec/services/webauthn/authenticate_service_spec.rb b/spec/services/webauthn/authenticate_service_spec.rb index b40f9465b63..ca940dff0eb 100644 --- a/spec/services/webauthn/authenticate_service_spec.rb +++ b/spec/services/webauthn/authenticate_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require 'webauthn/fake_client' -RSpec.describe Webauthn::AuthenticateService do +RSpec.describe Webauthn::AuthenticateService, feature_category: :system_access do let(:client) { WebAuthn::FakeClient.new(origin) } let(:user) { create(:user) } let(:challenge) { Base64.strict_encode64(SecureRandom.random_bytes(32)) } diff --git a/spec/services/webauthn/register_service_spec.rb b/spec/services/webauthn/register_service_spec.rb index bb9fa2080d2..2286d261e94 100644 --- a/spec/services/webauthn/register_service_spec.rb +++ b/spec/services/webauthn/register_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require 'webauthn/fake_client' -RSpec.describe Webauthn::RegisterService do +RSpec.describe Webauthn::RegisterService, feature_category: :system_access do let(:client) { WebAuthn::FakeClient.new(origin) } let(:user) { create(:user) } let(:challenge) { Base64.strict_encode64(SecureRandom.random_bytes(32)) } diff --git a/spec/services/wiki_pages/base_service_spec.rb b/spec/services/wiki_pages/base_service_spec.rb index 6ccc796014c..f434dc689ef 100644 --- a/spec/services/wiki_pages/base_service_spec.rb +++ b/spec/services/wiki_pages/base_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WikiPages::BaseService do +RSpec.describe WikiPages::BaseService, feature_category: :wiki do let(:project) { double('project') } let(:user) { double('user') } diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb index fd3776f4207..ca2d38ad70d 100644 --- a/spec/services/wiki_pages/create_service_spec.rb +++ b/spec/services/wiki_pages/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WikiPages::CreateService do +RSpec.describe WikiPages::CreateService, feature_category: :wiki do it_behaves_like 'WikiPages::CreateService#execute', :project describe '#execute' do diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb index 9384ea1cd43..ff29fc59b3e 100644 --- a/spec/services/wiki_pages/destroy_service_spec.rb +++ b/spec/services/wiki_pages/destroy_service_spec.rb @@ -2,6 +2,6 @@ require 'spec_helper' -RSpec.describe WikiPages::DestroyService do +RSpec.describe WikiPages::DestroyService, feature_category: :wiki do it_behaves_like 'WikiPages::DestroyService#execute', :project end diff --git a/spec/services/wiki_pages/event_create_service_spec.rb b/spec/services/wiki_pages/event_create_service_spec.rb index 8476f872e98..cbc2bd82a98 100644 --- a/spec/services/wiki_pages/event_create_service_spec.rb +++ b/spec/services/wiki_pages/event_create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WikiPages::EventCreateService do +RSpec.describe WikiPages::EventCreateService, feature_category: :wiki do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb index 62881817e32..79b2b55907b 100644 --- a/spec/services/wiki_pages/update_service_spec.rb +++ b/spec/services/wiki_pages/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WikiPages::UpdateService do +RSpec.describe WikiPages::UpdateService, feature_category: :wiki do it_behaves_like 'WikiPages::UpdateService#execute', :project describe '#execute' do diff --git a/spec/services/wikis/create_attachment_service_spec.rb b/spec/services/wikis/create_attachment_service_spec.rb index 22e34e1f373..fccdbd3040b 100644 --- a/spec/services/wikis/create_attachment_service_spec.rb +++ b/spec/services/wikis/create_attachment_service_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Wikis::CreateAttachmentService do +RSpec.describe Wikis::CreateAttachmentService, feature_category: :wiki do let(:container) { create(:project, :wiki_repo) } let(:user) { create(:user) } let(:file_name) { 'filename.txt' } diff --git a/spec/services/work_items/build_service_spec.rb b/spec/services/work_items/build_service_spec.rb index 405b4414fc2..3ecf78e0659 100644 --- a/spec/services/work_items/build_service_spec.rb +++ b/spec/services/work_items/build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::BuildService do +RSpec.describe WorkItems::BuildService, feature_category: :team_planning do let_it_be(:project) { create(:project, :repository) } let_it_be(:guest) { create(:user) } diff --git a/spec/services/work_items/create_from_task_service_spec.rb b/spec/services/work_items/create_from_task_service_spec.rb index 7c5430f038c..b2f81f1dc54 100644 --- a/spec/services/work_items/create_from_task_service_spec.rb +++ b/spec/services/work_items/create_from_task_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::CreateFromTaskService do +RSpec.describe WorkItems::CreateFromTaskService, feature_category: :team_planning do let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user) } let_it_be(:list_work_item, refind: true) { create(:work_item, project: project, description: "- [ ] Item to be converted\n second line\n third line") } diff --git a/spec/services/work_items/create_service_spec.rb b/spec/services/work_items/create_service_spec.rb index 1b134c308f2..46e598c3f11 100644 --- a/spec/services/work_items/create_service_spec.rb +++ b/spec/services/work_items/create_service_spec.rb @@ -2,200 +2,255 @@ require 'spec_helper' -RSpec.describe WorkItems::CreateService do +RSpec.describe WorkItems::CreateService, feature_category: :team_planning do include AfterNextHelpers - let_it_be_with_reload(:project) { create(:project) } - let_it_be(:parent) { create(:work_item, project: project) } - let_it_be(:guest) { create(:user) } - let_it_be(:reporter) { create(:user) } - let_it_be(:user_with_no_access) { create(:user) } - - let(:widget_params) { {} } - let(:spam_params) { double } - let(:current_user) { guest } - let(:opts) do - { - title: 'Awesome work_item', - description: 'please fix' - } - end - - before_all do - project.add_guest(guest) - project.add_reporter(reporter) - end + RSpec.shared_examples 'creates work item in container' do |container_type| + let_it_be_with_reload(:project) { create(:project) } + let_it_be_with_reload(:group) { create(:group) } - describe '#execute' do - let(:service) do - described_class.new( - container: project, - current_user: current_user, - params: opts, - spam_params: spam_params, - widget_params: widget_params - ) + let_it_be(:container) do + case container_type + when :project then project + when :project_namespace then project.project_namespace + when :group then group + end end - subject(:service_result) { service.execute } + let_it_be(:container_args) do + case container_type + when :project, :project_namespace then { project: project } + when :group then { namespace: group } + end + end - before do - stub_spam_services + let_it_be(:parent) { create(:work_item, **container_args) } + let_it_be(:guest) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:user_with_no_access) { create(:user) } + + let(:widget_params) { {} } + let(:spam_params) { double } + let(:current_user) { guest } + let(:opts) do + { + title: 'Awesome work_item', + description: 'please fix' + } end - context 'when user is not allowed to create a work item in the project' do - let(:current_user) { user_with_no_access } + before_all do + memberships_container = container.is_a?(Namespaces::ProjectNamespace) ? container.reload.project : container + memberships_container.add_guest(guest) + memberships_container.add_reporter(reporter) + end - it { is_expected.to be_error } + describe '#execute' do + shared_examples 'fails creating work item and returns errors' do + it 'does not create new work item if parent can not be set' do + expect { service_result }.not_to change(WorkItem, :count) - it 'returns an access error' do - expect(service_result.errors).to contain_exactly('Operation not allowed') + expect(service_result[:status]).to be(:error) + expect(service_result[:message]).to match(error_message) + end end - end - context 'when params are valid' do - it 'created instance is a WorkItem' do - expect(Issuable::CommonSystemNotesService).to receive_message_chain(:new, :execute) + let(:service) do + described_class.new( + container: container, + current_user: current_user, + params: opts, + spam_params: spam_params, + widget_params: widget_params + ) + end - work_item = service_result[:work_item] + subject(:service_result) { service.execute } - expect(work_item).to be_persisted - expect(work_item).to be_a(::WorkItem) - expect(work_item.title).to eq('Awesome work_item') - expect(work_item.description).to eq('please fix') - expect(work_item.work_item_type.base_type).to eq('issue') + before do + stub_spam_services end - it 'calls NewIssueWorker with correct arguments' do - expect(NewIssueWorker).to receive(:perform_async).with(Integer, current_user.id, 'WorkItem') + context 'when user is not allowed to create a work item in the container' do + let(:current_user) { user_with_no_access } - service_result + it { is_expected.to be_error } + + it 'returns an access error' do + expect(service_result.errors).to contain_exactly('Operation not allowed') + end end - end - context 'when params are invalid' do - let(:opts) { { title: '' } } + context 'when applying quick actions' do + let(:work_item) { service_result[:work_item] } + let(:opts) do + { + title: 'My work item', + work_item_type: work_item_type, + description: '/shrug' + } + end - it { is_expected.to be_error } + context 'when work item type is not the default Issue' do + let(:work_item_type) { create(:work_item_type, :task, namespace: group) } - it 'returns validation errors' do - expect(service_result.errors).to contain_exactly("Title can't be blank") - end + it 'saves the work item without applying the quick action' do + expect(service_result).to be_success + expect(work_item).to be_persisted + expect(work_item.description).to eq('/shrug') + end + end - it 'does not execute after-create transaction widgets' do - expect(service).to receive(:create).and_call_original - expect(service).not_to receive(:execute_widgets) - .with(callback: :after_create_in_transaction, widget_params: widget_params) + context 'when work item type is the default Issue' do + let(:work_item_type) { WorkItems::Type.default_by_type(:issue) } - service_result + it 'saves the work item and applies the quick action' do + expect(service_result).to be_success + expect(work_item).to be_persisted + expect(work_item.description).to eq(' ¯\_(ツ)_/¯') + end + end end - end - context 'checking spam' do - it 'executes SpamActionService' do - expect_next_instance_of( - Spam::SpamActionService, - { - spammable: kind_of(WorkItem), - spam_params: spam_params, - user: an_instance_of(User), - action: :create - } - ) do |instance| - expect(instance).to receive(:execute) + context 'when params are valid' do + it 'created instance is a WorkItem' do + expect(Issuable::CommonSystemNotesService).to receive_message_chain(:new, :execute) + + work_item = service_result[:work_item] + + expect(work_item).to be_persisted + expect(work_item).to be_a(::WorkItem) + expect(work_item.title).to eq('Awesome work_item') + expect(work_item.description).to eq('please fix') + expect(work_item.work_item_type.base_type).to eq('issue') end - service_result - end - end + it 'calls NewIssueWorker with correct arguments' do + expect(NewIssueWorker).to receive(:perform_async).with(Integer, current_user.id, 'WorkItem') - it_behaves_like 'work item widgetable service' do - let(:widget_params) do - { - hierarchy_widget: { parent: parent } - } + service_result + end end - let(:service) do - described_class.new( - container: project, - current_user: current_user, - params: opts, - spam_params: spam_params, - widget_params: widget_params - ) + context 'when params are invalid' do + let(:opts) { { title: '' } } + + it { is_expected.to be_error } + + it 'returns validation errors' do + expect(service_result.errors).to contain_exactly("Title can't be blank") + end + + it 'does not execute after-create transaction widgets' do + expect(service).to receive(:create).and_call_original + expect(service).not_to receive(:execute_widgets) + .with(callback: :after_create_in_transaction, widget_params: widget_params) + + service_result + end end - let(:service_execute) { service.execute } + context 'checking spam' do + it 'executes SpamActionService' do + expect_next_instance_of( + Spam::SpamActionService, + { + spammable: kind_of(WorkItem), + spam_params: spam_params, + user: an_instance_of(User), + action: :create + } + ) do |instance| + expect(instance).to receive(:execute) + end + + service_result + end + end - let(:supported_widgets) do - [ + it_behaves_like 'work item widgetable service' do + let(:widget_params) do { - klass: WorkItems::Widgets::HierarchyService::CreateService, - callback: :after_create_in_transaction, - params: { parent: parent } + hierarchy_widget: { parent: parent } } - ] - end - end + end - describe 'hierarchy widget' do - let(:widget_params) { { hierarchy_widget: { parent: parent } } } + let(:service) do + described_class.new( + container: container, + current_user: current_user, + params: opts, + spam_params: spam_params, + widget_params: widget_params + ) + end - shared_examples 'fails creating work item and returns errors' do - it 'does not create new work item if parent can not be set' do - expect { service_result }.not_to change(WorkItem, :count) + let(:service_execute) { service.execute } - expect(service_result[:status]).to be(:error) - expect(service_result[:message]).to match(error_message) + let(:supported_widgets) do + [ + { + klass: WorkItems::Widgets::HierarchyService::CreateService, + callback: :after_create_in_transaction, + params: { parent: parent } + } + ] end end - context 'when user can admin parent link' do - let(:current_user) { reporter } + describe 'hierarchy widget' do + let(:widget_params) { { hierarchy_widget: { parent: parent } } } - context 'when parent is valid work item' do - let(:opts) do - { - title: 'Awesome work_item', - description: 'please fix', - work_item_type: WorkItems::Type.default_by_type(:task) - } - end + context 'when user can admin parent link' do + let(:current_user) { reporter } - it 'creates new work item and sets parent reference' do - expect { service_result }.to change( - WorkItem, :count).by(1).and(change( - WorkItems::ParentLink, :count).by(1)) + context 'when parent is valid work item' do + let(:opts) do + { + title: 'Awesome work_item', + description: 'please fix', + work_item_type: WorkItems::Type.default_by_type(:task) + } + end - expect(service_result[:status]).to be(:success) + it 'creates new work item and sets parent reference' do + expect { service_result }.to change(WorkItem, :count).by(1).and( + change(WorkItems::ParentLink, :count).by(1) + ) + + expect(service_result[:status]).to be(:success) + end end - end - context 'when parent type is invalid' do - let_it_be(:parent) { create(:work_item, :task, project: project) } + context 'when parent type is invalid' do + let_it_be(:parent) { create(:work_item, :task, **container_args) } - it_behaves_like 'fails creating work item and returns errors' do - let(:error_message) { 'is not allowed to add this type of parent' } + it_behaves_like 'fails creating work item and returns errors' do + let(:error_message) { 'is not allowed to add this type of parent' } + end end end - end - context 'when user cannot admin parent link' do - let(:current_user) { guest } + context 'when user cannot admin parent link' do + let(:current_user) { guest } - let(:opts) do - { - title: 'Awesome work_item', - description: 'please fix', - work_item_type: WorkItems::Type.default_by_type(:task) - } - end + let(:opts) do + { + title: 'Awesome work_item', + description: 'please fix', + work_item_type: WorkItems::Type.default_by_type(:task) + } + end - it_behaves_like 'fails creating work item and returns errors' do - let(:error_message) { 'No matching work item found. Make sure that you are adding a valid work item ID.' } + it_behaves_like 'fails creating work item and returns errors' do + let(:error_message) { 'No matching work item found. Make sure that you are adding a valid work item ID.' } + end end end end end + + it_behaves_like 'creates work item in container', :project + it_behaves_like 'creates work item in container', :project_namespace + it_behaves_like 'creates work item in container', :group end diff --git a/spec/services/work_items/delete_service_spec.rb b/spec/services/work_items/delete_service_spec.rb index 69ae881a12f..ac72815a57e 100644 --- a/spec/services/work_items/delete_service_spec.rb +++ b/spec/services/work_items/delete_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::DeleteService do +RSpec.describe WorkItems::DeleteService, feature_category: :team_planning do let_it_be(:project) { create(:project, :repository) } let_it_be(:guest) { create(:user) } let_it_be(:work_item, refind: true) { create(:work_item, project: project, author: guest) } diff --git a/spec/services/work_items/delete_task_service_spec.rb b/spec/services/work_items/delete_task_service_spec.rb index 07a0d8d6c1a..dc01da65771 100644 --- a/spec/services/work_items/delete_task_service_spec.rb +++ b/spec/services/work_items/delete_task_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::DeleteTaskService do +RSpec.describe WorkItems::DeleteTaskService, feature_category: :team_planning do let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } let_it_be_with_refind(:task) { create(:work_item, project: project, author: developer) } diff --git a/spec/services/work_items/export_csv_service_spec.rb b/spec/services/work_items/export_csv_service_spec.rb index 0718d3b686a..948ff89245e 100644 --- a/spec/services/work_items/export_csv_service_spec.rb +++ b/spec/services/work_items/export_csv_service_spec.rb @@ -6,7 +6,7 @@ RSpec.describe WorkItems::ExportCsvService, :with_license, feature_category: :te let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :public, group: group) } - let_it_be(:work_item_1) { create(:work_item, project: project) } + let_it_be(:work_item_1) { create(:work_item, description: 'test', project: project) } let_it_be(:work_item_2) { create(:work_item, :incident, project: project) } subject { described_class.new(WorkItem.all, project) } @@ -30,9 +30,8 @@ RSpec.describe WorkItems::ExportCsvService, :with_license, feature_category: :te end describe '#email' do - # TODO - will be implemented as part of https://gitlab.com/gitlab-org/gitlab/-/issues/379082 - xit 'emails csv' do - expect { subject.email(user) }.o change { ActionMailer::Base.deliveries.count }.from(0).to(1) + it 'emails csv' do + expect { subject.email(user) }.to change { ActionMailer::Base.deliveries.count }.from(0).to(1) end end @@ -65,6 +64,11 @@ RSpec.describe WorkItems::ExportCsvService, :with_license, feature_category: :te expect(csv[0]['Created At (UTC)']).to eq(work_item_1.created_at.to_s(:csv)) end + specify 'description' do + expect(csv[0]['Description']).to be_present + expect(csv[0]['Description']).to eq(work_item_1.description) + end + it 'preloads fields to avoid N+1 queries' do control = ActiveRecord::QueryRecorder.new { subject.csv_data } @@ -74,4 +78,20 @@ RSpec.describe WorkItems::ExportCsvService, :with_license, feature_category: :te end it_behaves_like 'a service that returns invalid fields from selection' + + # TODO - once we have a UI for this feature + # we can turn these into feature specs. + # more info at: https://gitlab.com/gitlab-org/gitlab/-/issues/396943 + context 'when importing an exported file' do + context 'for work item of type issue' do + it_behaves_like 'a exported file that can be imported' do + let_it_be(:user) { create(:user) } + let_it_be(:origin_project) { create(:project) } + let_it_be(:target_project) { create(:project) } + let_it_be(:work_item) { create(:work_item, project: origin_project) } + + let(:expected_matching_fields) { %w[title work_item_type] } + end + end + end end diff --git a/spec/services/work_items/import_csv_service_spec.rb b/spec/services/work_items/import_csv_service_spec.rb new file mode 100644 index 00000000000..3c710640f4a --- /dev/null +++ b/spec/services/work_items/import_csv_service_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::ImportCsvService, feature_category: :team_planning do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:author) { create(:user, username: 'csv_author') } + let(:file) { fixture_file_upload('spec/fixtures/work_items_valid_types.csv') } + let(:service) do + uploader = FileUploader.new(project) + uploader.store!(file) + + described_class.new(user, project, uploader) + end + + let_it_be(:issue_type) { ::WorkItems::Type.default_issue_type } + + let(:work_items) { ::WorkItems::WorkItemsFinder.new(user, project: project).execute } + let(:email_method) { :import_work_items_csv_email } + + subject { service.execute } + + describe '#execute', :aggregate_failures do + context 'when user has permission' do + before do + project.add_reporter(user) + end + + it_behaves_like 'importer with email notification' + + context 'when file format is valid' do + context 'when work item types are available' do + it 'creates the expected number of work items' do + expect { subject }.to change { work_items.count }.by 2 + end + + it 'sets work item attributes' do + result = subject + + expect(work_items.reload).to contain_exactly( + have_attributes( + title: 'Valid issue', + work_item_type_id: issue_type.id + ), + have_attributes( + title: 'Valid issue with alternate case', + work_item_type_id: issue_type.id + ) + ) + + expect(result[:success]).to eq(2) + expect(result[:error_lines]).to eq([]) + expect(result[:type_errors]).to be_nil + expect(result[:parse_error]).to eq(false) + end + end + + context 'when csv contains work item types that are missing or not available' do + let(:file) { fixture_file_upload('spec/fixtures/work_items_invalid_types.csv') } + + it 'creates no work items' do + expect { subject }.not_to change { work_items.count } + end + + it 'returns the correct result' do + result = subject + + expect(result[:success]).to eq(0) + expect(result[:error_lines]).to be_empty # there are problematic lines detailed below + expect(result[:parse_error]).to eq(false) + expect(result[:type_errors]).to match({ + blank: [4], + disallowed: {}, # tested in the EE version + missing: { + "isssue" => [2], + "issue!!" => [3] + } + }) + end + end + end + + context 'when file is missing necessary headers' do + let(:file) { fixture_file_upload('spec/fixtures/work_items_missing_header.csv') } + + it 'creates no records' do + result = subject + + expect(result[:success]).to eq(0) + expect(result[:error_lines]).to eq([1]) + expect(result[:type_errors]).to be_nil + expect(result[:parse_error]).to eq(true) + end + + it 'creates no work items' do + expect { subject }.not_to change { work_items.count } + end + end + + context 'when import_export_work_items_csv feature flag is off' do + before do + stub_feature_flags(import_export_work_items_csv: false) + end + + it 'raises an error' do + expect { subject }.to raise_error(/This feature is currently behind a feature flag and it is not available./) + end + end + end + + context 'when user does not have permission' do + before do + project.add_guest(user) + end + + it 'raises an error' do + expect { subject }.to raise_error(/You do not have permission to import work items in this project/) + end + end + end +end diff --git a/spec/services/work_items/parent_links/base_service_spec.rb b/spec/services/work_items/parent_links/base_service_spec.rb new file mode 100644 index 00000000000..dbdbc774d3c --- /dev/null +++ b/spec/services/work_items/parent_links/base_service_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module WorkItems + class ParentLinksService < WorkItems::ParentLinks::BaseService; end +end + +RSpec.describe WorkItems::ParentLinks::BaseService, feature_category: :portfolio_management do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:work_item) { create(:work_item, :objective, project: project) } + let_it_be(:target_work_item) { create(:work_item, :objective, project: project) } + + let(:params) { { target_issuable: target_work_item } } + let(:described_class_descendant) { WorkItems::ParentLinksService } + + before do + project.add_reporter(user) + end + + describe '#execute' do + subject { described_class_descendant.new(work_item, user, params).execute } + + context 'when user has sufficient permissions' do + it 'raises NotImplementedError' do + expect { subject }.to raise_error(NotImplementedError) + end + end + end +end diff --git a/spec/services/work_items/parent_links/create_service_spec.rb b/spec/services/work_items/parent_links/create_service_spec.rb index 5884847eac3..41ae6398614 100644 --- a/spec/services/work_items/parent_links/create_service_spec.rb +++ b/spec/services/work_items/parent_links/create_service_spec.rb @@ -9,8 +9,8 @@ RSpec.describe WorkItems::ParentLinks::CreateService, feature_category: :portfol let_it_be(:project) { create(:project) } let_it_be(:work_item) { create(:work_item, project: project) } let_it_be(:task) { create(:work_item, :task, project: project) } - let_it_be(:task1) { create(:work_item, :task, project: project) } - let_it_be(:task2) { create(:work_item, :task, project: project) } + let_it_be_with_reload(:task1) { create(:work_item, :task, project: project) } + let_it_be_with_reload(:task2) { create(:work_item, :task, project: project) } let_it_be(:guest_task) { create(:work_item, :task) } let_it_be(:invalid_task) { build_stubbed(:work_item, :task, id: non_existing_record_id) } let_it_be(:another_project) { (create :project) } @@ -68,6 +68,40 @@ RSpec.describe WorkItems::ParentLinks::CreateService, feature_category: :portfol end end + context 'when adjacent is already in place' do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:parent_item) { create(:work_item, :objective, project: project) } + let_it_be_with_reload(:current_item) { create(:work_item, :objective, project: project) } + + let_it_be_with_reload(:adjacent) do + create(:work_item, :objective, project: project) + end + + let_it_be_with_reload(:link_to_adjacent) do + create(:parent_link, work_item_parent: parent_item, work_item: adjacent) + end + + subject { described_class.new(parent_item, user, { target_issuable: current_item }).execute } + + where(:adjacent_position, :expected_order) do + -100 | lazy { [adjacent, current_item] } + 0 | lazy { [adjacent, current_item] } + 100 | lazy { [adjacent, current_item] } + end + + with_them do + before do + link_to_adjacent.update!(relative_position: adjacent_position) + end + + it 'sets relative positions' do + expect { subject }.to change(parent_link_class, :count).by(1) + expect(parent_item.work_item_children_by_relative_position).to eq(expected_order) + end + end + end + context 'when there are tasks to relate' do let(:params) { { issuable_references: [task1, task2] } } @@ -84,26 +118,74 @@ RSpec.describe WorkItems::ParentLinks::CreateService, feature_category: :portfol expect(subject[:created_references].map(&:work_item_id)).to match_array([task1.id, task2.id]) end - it 'creates notes', :aggregate_failures do - subject + it 'creates notes and records the events', :aggregate_failures do + expect { subject }.to change(WorkItems::ResourceLinkEvent, :count).by(2) work_item_notes = work_item.notes.last(2) + resource_link_events = WorkItems::ResourceLinkEvent.last(2) expect(work_item_notes.first.note).to eq("added #{task1.to_reference} as child task") expect(work_item_notes.last.note).to eq("added #{task2.to_reference} as child task") expect(task1.notes.last.note).to eq("added #{work_item.to_reference} as parent issue") expect(task2.notes.last.note).to eq("added #{work_item.to_reference} as parent issue") + expect(resource_link_events.first).to have_attributes( + user_id: user.id, + issue_id: work_item.id, + child_work_item_id: task1.id, + action: "add", + system_note_metadata_id: task1.notes.last.system_note_metadata.id + ) + expect(resource_link_events.last).to have_attributes( + user_id: user.id, + issue_id: work_item.id, + child_work_item_id: task2.id, + action: "add", + system_note_metadata_id: task2.notes.last.system_note_metadata.id + ) + end + + context 'when note creation fails for some reason' do + let(:params) { { issuable_references: [task1] } } + + [Note.new, nil].each do |relate_child_note| + it 'still records the link event', :aggregate_failures do + allow_next_instance_of(WorkItems::ParentLinks::CreateService) do |instance| + allow(instance).to receive(:create_notes).and_return(relate_child_note) + end + + expect { subject } + .to change(WorkItems::ResourceLinkEvent, :count).by(1) + .and not_change(Note, :count) + + expect(WorkItems::ResourceLinkEvent.last).to have_attributes( + user_id: user.id, + issue_id: work_item.id, + child_work_item_id: task1.id, + action: "add", + system_note_metadata_id: nil + ) + end + end end context 'when task is already assigned' do let(:params) { { issuable_references: [task, task2] } } it 'creates links only for non related tasks', :aggregate_failures do - expect { subject }.to change(parent_link_class, :count).by(1) + expect { subject } + .to change(parent_link_class, :count).by(1) + .and change(WorkItems::ResourceLinkEvent, :count).by(1) expect(subject[:created_references].map(&:work_item_id)).to match_array([task2.id]) expect(work_item.notes.last.note).to eq("added #{task2.to_reference} as child task") expect(task2.notes.last.note).to eq("added #{work_item.to_reference} as parent issue") expect(task.notes).to be_empty + expect(WorkItems::ResourceLinkEvent.last).to have_attributes( + user_id: user.id, + issue_id: work_item.id, + child_work_item_id: task2.id, + action: "add", + system_note_metadata_id: task2.notes.last.system_note_metadata.id + ) end end @@ -160,7 +242,7 @@ RSpec.describe WorkItems::ParentLinks::CreateService, feature_category: :portfol end context 'when params include invalid ids' do - let(:params) { { issuable_references: [task1, invalid_task] } } + let(:params) { { issuable_references: [task1, guest_task] } } it 'creates links only for valid IDs' do expect { subject }.to change(parent_link_class, :count).by(1) diff --git a/spec/services/work_items/parent_links/destroy_service_spec.rb b/spec/services/work_items/parent_links/destroy_service_spec.rb index 654a03ef6f7..7e2e3949b73 100644 --- a/spec/services/work_items/parent_links/destroy_service_spec.rb +++ b/spec/services/work_items/parent_links/destroy_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::ParentLinks::DestroyService do +RSpec.describe WorkItems::ParentLinks::DestroyService, feature_category: :team_planning do describe '#execute' do let_it_be(:reporter) { create(:user) } let_it_be(:guest) { create(:user) } @@ -24,23 +24,53 @@ RSpec.describe WorkItems::ParentLinks::DestroyService do let(:user) { reporter } it 'removes relation and creates notes', :aggregate_failures do - expect { subject }.to change(parent_link_class, :count).by(-1) + expect { subject } + .to change(parent_link_class, :count).by(-1) + .and change(WorkItems::ResourceLinkEvent, :count).by(1) expect(work_item.notes.last.note).to eq("removed child task #{task.to_reference}") expect(task.notes.last.note).to eq("removed parent issue #{work_item.to_reference}") + expect(WorkItems::ResourceLinkEvent.last).to have_attributes( + user_id: user.id, + issue_id: work_item.id, + child_work_item_id: task.id, + action: "remove", + system_note_metadata_id: task.notes.last.system_note_metadata.id + ) end it 'returns success message' do is_expected.to eq(message: 'Relation was removed', status: :success) end + + context 'when note creation fails for some reason' do + [Note.new, nil].each do |unrelate_child_note| + it 'still records the link event', :aggregate_failures do + allow(SystemNoteService).to receive(:unrelate_work_item).and_return(unrelate_child_note) + + expect { subject } + .to change(WorkItems::ResourceLinkEvent, :count).by(1) + .and not_change(Note, :count) + + expect(WorkItems::ResourceLinkEvent.last).to have_attributes( + user_id: user.id, + issue_id: work_item.id, + child_work_item_id: task.id, + action: "remove", + system_note_metadata_id: nil + ) + end + end + end end context 'when user has insufficient permissions' do let(:user) { guest } it 'does not remove relation', :aggregate_failures do - expect { subject }.not_to change(parent_link_class, :count).from(1) - + expect { subject } + .to not_change(parent_link_class, :count).from(1) + .and not_change(WorkItems::ResourceLinkEvent, :count) expect(SystemNoteService).not_to receive(:unrelate_work_item) end diff --git a/spec/services/work_items/parent_links/reorder_service_spec.rb b/spec/services/work_items/parent_links/reorder_service_spec.rb new file mode 100644 index 00000000000..0448429d2bb --- /dev/null +++ b/spec/services/work_items/parent_links/reorder_service_spec.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::ParentLinks::ReorderService, feature_category: :portfolio_management do + describe '#execute' do + let_it_be(:reporter) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be_with_reload(:parent) { create(:work_item, :objective, project: project) } + let_it_be_with_reload(:work_item) { create(:work_item, :objective, project: project) } + let_it_be_with_reload(:top_adjacent) { create(:work_item, :objective, project: project) } + let_it_be_with_reload(:last_adjacent) { create(:work_item, :objective, project: project) } + + let(:parent_link_class) { WorkItems::ParentLink } + let(:user) { reporter } + let(:params) { { target_issuable: work_item } } + let(:relative_range) { [top_adjacent, last_adjacent].map(&:parent_link).map(&:relative_position) } + + subject { described_class.new(parent, user, params).execute } + + before do + project.add_reporter(reporter) + project.add_guest(guest) + + create(:parent_link, work_item: top_adjacent, work_item_parent: parent) + create(:parent_link, work_item: last_adjacent, work_item_parent: parent) + end + + shared_examples 'raises a service error' do |message, status = 409| + it { is_expected.to eq(service_error(message, http_status: status)) } + end + + shared_examples 'returns not found error' do + it 'returns error' do + error = "No matching work item found. Make sure that you are adding a valid work item ID." + + is_expected.to eq(service_error(error)) + end + + it 'creates no relationship' do + expect { subject }.not_to change { parent_link_class.count } + end + end + + shared_examples 'returns conflict error' do + it_behaves_like 'raises a service error', 'Work item(s) already assigned' + + it 'creates no relationship' do + expect { subject }.to not_change { parent_link_class.count } + end + end + + shared_examples 'processes ordered hierarchy' do + it 'returns success status and processed links', :aggregate_failures do + expect(subject.keys).to match_array([:status, :created_references]) + expect(subject[:status]).to eq(:success) + expect(subject[:created_references].map(&:work_item_id)).to match_array([work_item.id]) + end + + it 'orders hierarchy' do + subject + + expect(last_adjacent.parent_link.relative_position).to be_between(*relative_range) + end + end + + context 'when user has insufficient permissions' do + let(:user) { guest } + + it_behaves_like 'returns not found error' + + context 'when user is a guest assigned to the work item' do + before do + work_item.assignees = [guest] + end + + it_behaves_like 'returns not found error' + end + end + + context 'when child and parent are already linked' do + before do + create(:parent_link, work_item: work_item, work_item_parent: parent) + end + + it_behaves_like 'returns conflict error' + + context 'when adjacents are already in place and the user has sufficient permissions' do + let(:base_param) { { target_issuable: work_item } } + + shared_examples 'updates hierarchy order without notes' do + it_behaves_like 'processes ordered hierarchy' + + it 'keeps relationships', :aggregate_failures do + expect { subject }.to not_change { parent_link_class.count } + + expect(parent_link_class.where(work_item: work_item).last.work_item_parent).to eq(parent) + end + + it 'does not create notes', :aggregate_failures do + expect { subject }.to not_change { work_item.notes.count }.and(not_change { work_item.notes.count }) + end + end + + context 'when moving before adjacent work item' do + let(:params) { base_param.merge({ adjacent_work_item: last_adjacent, relative_position: 'BEFORE' }) } + + it_behaves_like 'updates hierarchy order without notes' + end + + context 'when moving after adjacent work item' do + let(:params) { base_param.merge({ adjacent_work_item: top_adjacent, relative_position: 'AFTER' }) } + + it_behaves_like 'updates hierarchy order without notes' + end + end + end + + context 'when new parent is assigned' do + shared_examples 'updates hierarchy order and creates notes' do + it_behaves_like 'processes ordered hierarchy' + + it 'creates notes', :aggregate_failures do + subject + + expect(parent.notes.last.note).to eq("added #{work_item.to_reference} as child objective") + expect(work_item.notes.last.note).to eq("added #{parent.to_reference} as parent objective") + end + end + + context 'when adjacents are already in place and the user has sufficient permissions' do + let(:base_param) { { target_issuable: work_item } } + + context 'when moving before adjacent work item' do + let(:params) { base_param.merge({ adjacent_work_item: last_adjacent, relative_position: 'BEFORE' }) } + + it_behaves_like 'updates hierarchy order and creates notes' + end + + context 'when moving after adjacent work item' do + let(:params) { base_param.merge({ adjacent_work_item: top_adjacent, relative_position: 'AFTER' }) } + + it_behaves_like 'updates hierarchy order and creates notes' + end + + context 'when previous parent was in place' do + before do + create(:parent_link, work_item: work_item, + work_item_parent: create(:work_item, :objective, project: project)) + end + + context 'when moving before adjacent work item' do + let(:params) { base_param.merge({ adjacent_work_item: last_adjacent, relative_position: 'BEFORE' }) } + + it_behaves_like 'updates hierarchy order and creates notes' + end + + context 'when moving after adjacent work item' do + let(:params) { base_param.merge({ adjacent_work_item: top_adjacent, relative_position: 'AFTER' }) } + + it_behaves_like 'updates hierarchy order and creates notes' + end + end + end + end + end + + def service_error(message, http_status: 404) + { + message: message, + status: :error, + http_status: http_status + } + end +end diff --git a/spec/services/work_items/prepare_import_csv_service_spec.rb b/spec/services/work_items/prepare_import_csv_service_spec.rb new file mode 100644 index 00000000000..6a657120690 --- /dev/null +++ b/spec/services/work_items/prepare_import_csv_service_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::PrepareImportCsvService, feature_category: :team_planning do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + let(:file) { double } + let(:upload_service) { double } + let(:uploader) { double } + let(:upload) { double } + + let(:subject) do + described_class.new(project, user, file: file).execute + end + + context 'when file is uploaded correctly' do + let(:upload_id) { 99 } + + before do + mock_upload + end + + it 'returns a success message' do + result = subject + + expect(result[:status]).to eq(:success) + expect(result[:message]).to eq( + "Your work items are being imported. Once finished, you'll receive a confirmation email.") + end + + it 'enqueues the ImportWorkItemsCsvWorker' do + expect(WorkItems::ImportWorkItemsCsvWorker).to receive(:perform_async).with(user.id, project.id, upload_id) + + subject + end + end + + context 'when file upload fails' do + before do + mock_upload(false) + end + + it 'returns an error message' do + result = subject + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('File upload error.') + end + end +end diff --git a/spec/services/work_items/task_list_reference_removal_service_spec.rb b/spec/services/work_items/task_list_reference_removal_service_spec.rb index 91b7814ae92..4e87ce66c21 100644 --- a/spec/services/work_items/task_list_reference_removal_service_spec.rb +++ b/spec/services/work_items/task_list_reference_removal_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::TaskListReferenceRemovalService do +RSpec.describe WorkItems::TaskListReferenceRemovalService, feature_category: :team_planning do let_it_be(:developer) { create(:user) } let_it_be(:project) { create(:project, :repository).tap { |project| project.add_developer(developer) } } let_it_be(:task) { create(:work_item, project: project, title: 'Task title') } diff --git a/spec/services/work_items/task_list_reference_replacement_service_spec.rb b/spec/services/work_items/task_list_reference_replacement_service_spec.rb index 965c5f1d554..8f696109fa1 100644 --- a/spec/services/work_items/task_list_reference_replacement_service_spec.rb +++ b/spec/services/work_items/task_list_reference_replacement_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::TaskListReferenceReplacementService do +RSpec.describe WorkItems::TaskListReferenceReplacementService, feature_category: :team_planning do let_it_be(:developer) { create(:user) } let_it_be(:project) { create(:project, :repository).tap { |project| project.add_developer(developer) } } let_it_be(:single_line_work_item, refind: true) { create(:work_item, project: project, description: '- [ ] single line', lock_version: 3) } diff --git a/spec/services/work_items/update_service_spec.rb b/spec/services/work_items/update_service_spec.rb index 435995c6570..2cf52ee853a 100644 --- a/spec/services/work_items/update_service_spec.rb +++ b/spec/services/work_items/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::UpdateService do +RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do let_it_be(:developer) { create(:user) } let_it_be(:guest) { create(:user) } let_it_be(:project) { create(:project) } @@ -44,6 +44,33 @@ RSpec.describe WorkItems::UpdateService do end end + context 'when applying quick actions' do + let(:opts) { { description: "/shrug" } } + + 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) + end + + it 'does not apply the quick action' do + expect do + update_work_item + end.to change(work_item, :description).to('/shrug') + end + end + + context 'when work item type is the default Issue' do + let(:issue) { create(:work_item, :issue, description: '') } + + it 'applies the quick action' do + expect do + update_work_item + end.to change(work_item, :description).to(' ¯\_(ツ)_/¯') + end + end + end + context 'when title is changed' do let(:opts) { { title: 'changed' } } diff --git a/spec/services/work_items/widgets/assignees_service/update_service_spec.rb b/spec/services/work_items/widgets/assignees_service/update_service_spec.rb index 0ab2c85f078..66e30e2f882 100644 --- a/spec/services/work_items/widgets/assignees_service/update_service_spec.rb +++ b/spec/services/work_items/widgets/assignees_service/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::Widgets::AssigneesService::UpdateService, :freeze_time do +RSpec.describe WorkItems::Widgets::AssigneesService::UpdateService, :freeze_time, feature_category: :portfolio_management do let_it_be(:reporter) { create(:user) } let_it_be(:project) { create(:project, :private) } let_it_be(:new_assignee) { create(:user) } @@ -21,10 +21,9 @@ RSpec.describe WorkItems::Widgets::AssigneesService::UpdateService, :freeze_time end describe '#before_update_in_transaction' do - subject do - described_class.new(widget: widget, current_user: current_user) - .before_update_in_transaction(params: params) - end + let(:service) { described_class.new(widget: widget, current_user: current_user) } + + subject { service.before_update_in_transaction(params: params) } it 'updates the assignees and sets updated_at to the current time' do subject @@ -112,5 +111,20 @@ RSpec.describe WorkItems::Widgets::AssigneesService::UpdateService, :freeze_time expect(work_item.updated_at).to be_like_time(1.day.ago) end end + + context 'when widget does not exist in new type' do + let(:params) { {} } + + before do + allow(service).to receive(:new_type_excludes_widget?).and_return(true) + work_item.assignee_ids = [new_assignee.id] + end + + it "resets the work item's assignees" do + subject + + expect(work_item.assignee_ids).to be_empty + end + end end end diff --git a/spec/services/work_items/widgets/award_emoji_service/update_service_spec.rb b/spec/services/work_items/widgets/award_emoji_service/update_service_spec.rb new file mode 100644 index 00000000000..186e4d56cc4 --- /dev/null +++ b/spec/services/work_items/widgets/award_emoji_service/update_service_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::Widgets::AwardEmojiService::UpdateService, feature_category: :team_planning do + let_it_be(:reporter) { create(:user) } + let_it_be(:unauthorized_user) { create(:user) } + let_it_be(:project) { create(:project, :private) } + let_it_be(:work_item) { create(:work_item, project: project) } + + let(:current_user) { reporter } + let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::AwardEmoji) } } + + before_all do + project.add_reporter(reporter) + end + + describe '#before_update_in_transaction' do + subject do + described_class.new(widget: widget, current_user: current_user) + .before_update_in_transaction(params: params) + end + + shared_examples 'raises a WidgetError' do + it { expect { subject }.to raise_error(described_class::WidgetError, message) } + end + + context 'when awarding an emoji' do + let(:params) { { action: :add, name: 'star' } } + + context 'when user has no access' do + let(:current_user) { unauthorized_user } + + it 'does not award the emoji' do + expect { subject }.not_to change { AwardEmoji.count } + end + end + + context 'when user has access' do + it 'awards the emoji to the work item' do + expect { subject }.to change { AwardEmoji.count }.by(1) + + emoji = AwardEmoji.last + + expect(emoji.name).to eq('star') + expect(emoji.awardable_id).to eq(work_item.id) + expect(emoji.user).to eq(current_user) + end + + context 'when the name is incorrect' do + let(:params) { { action: :add, name: 'foo' } } + + it_behaves_like 'raises a WidgetError' do + let(:message) { 'Name is not a valid emoji name' } + end + end + + context 'when the action is incorrect' do + let(:params) { { action: :foo, name: 'star' } } + + it_behaves_like 'raises a WidgetError' do + let(:message) { 'foo is not a valid action.' } + end + end + end + end + + context 'when removing emoji' do + let(:params) { { action: :remove, name: 'thumbsup' } } + + context 'when user has no access' do + let(:current_user) { unauthorized_user } + + it 'does not remove the emoji' do + expect { subject }.not_to change { AwardEmoji.count } + end + end + + context 'when user has access' do + it 'removes existing emoji' do + create(:award_emoji, :upvote, awardable: work_item, user: current_user) + + expect { subject }.to change { AwardEmoji.count }.by(-1) + end + + context 'when work item does not have the emoji' do + let(:params) { { action: :remove, name: 'star' } } + + it_behaves_like 'raises a WidgetError' do + let(:message) { 'User has not awarded emoji of type star on the awardable' } + end + end + end + end + end +end 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 new file mode 100644 index 00000000000..85b7e7a70df --- /dev/null +++ b/spec/services/work_items/widgets/current_user_todos_service/update_service_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::Widgets::CurrentUserTodosService::UpdateService, feature_category: :team_planning do + let_it_be(:reporter) { create(:user) } + let_it_be(:project) { create(:project, :private) } + let_it_be(:current_user) { reporter } + let_it_be(:work_item) { create(:work_item, project: project) } + + let_it_be(:pending_todo1) do + create(:todo, state: :pending, target: work_item, target_type: work_item.class.name, user: current_user) + end + + let_it_be(:pending_todo2) do + create(:todo, state: :pending, target: work_item, target_type: work_item.class.name, user: current_user) + end + + let_it_be(:done_todo) do + create(:todo, state: :done, target: work_item, target_type: work_item.class.name, user: current_user) + end + + let_it_be(:other_work_item_todo) { create(:todo, state: :pending, target: create(:work_item), user: current_user) } + let_it_be(:other_user_todo) do + create(:todo, state: :pending, target: work_item, target_type: work_item.class.name, user: create(:user)) + end + + let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::CurrentUserTodos) } } + + before_all do + project.add_reporter(reporter) + end + + describe '#before_update_in_transaction' do + subject do + described_class.new(widget: widget, current_user: current_user) + .before_update_in_transaction(params: params) + end + + context 'when adding a todo' do + let(:params) { { action: "add" } } + + context 'when user has no access' do + let(:current_user) { create(:user) } + + it 'does add a todo' do + expect { subject }.not_to change { Todo.count } + end + end + + context 'when user has access' do + let(:params) { { action: "add" } } + + it 'creates a new todo for the user and the work item' do + expect { subject }.to change { current_user.todos.count }.by(1) + + todo = current_user.todos.last + + expect(todo.target).to eq(work_item) + expect(todo).to be_pending + end + end + end + + context 'when marking as done' do + let(:params) { { action: "mark_as_done" } } + + context 'when user has no access' do + let(:current_user) { create(:user) } + + it 'does not change todo status' do + subject + + expect(pending_todo1.reload).to be_pending + expect(pending_todo2.reload).to be_pending + expect(other_work_item_todo.reload).to be_pending + expect(other_user_todo.reload).to be_pending + end + end + + context 'when resolving all todos of the work item', :aggregate_failures do + it 'resolves todos of the user for the work item' do + subject + + expect(pending_todo1.reload).to be_done + expect(pending_todo2.reload).to be_done + expect(other_work_item_todo.reload).to be_pending + expect(other_user_todo.reload).to be_pending + end + end + + context 'when resolving a specific todo', :aggregate_failures do + let(:params) { { action: "mark_as_done", todo_id: pending_todo1.id } } + + it 'resolves todos of the user for the work item' do + subject + + expect(pending_todo1.reload).to be_done + expect(pending_todo2.reload).to be_pending + expect(other_work_item_todo.reload).to be_pending + expect(other_user_todo.reload).to be_pending + end + end + end + end +end diff --git a/spec/services/work_items/widgets/description_service/update_service_spec.rb b/spec/services/work_items/widgets/description_service/update_service_spec.rb index 4275950e720..7da5b24a3b7 100644 --- a/spec/services/work_items/widgets/description_service/update_service_spec.rb +++ b/spec/services/work_items/widgets/description_service/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::Widgets::DescriptionService::UpdateService do +RSpec.describe WorkItems::Widgets::DescriptionService::UpdateService, feature_category: :portfolio_management do let_it_be(:random_user) { create(:user) } let_it_be(:author) { create(:user) } let_it_be(:guest) { create(:user) } @@ -20,7 +20,9 @@ RSpec.describe WorkItems::Widgets::DescriptionService::UpdateService do let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Description) } } describe '#update' do - subject { described_class.new(widget: widget, current_user: current_user).before_update_callback(params: params) } + let(:service) { described_class.new(widget: widget, current_user: current_user) } + + subject(:before_update_callback) { service.before_update_callback(params: params) } shared_examples 'sets work item description' do it 'correctly sets work item description value' do @@ -78,6 +80,23 @@ RSpec.describe WorkItems::Widgets::DescriptionService::UpdateService do it_behaves_like 'does not set work item description' end + + context 'when widget does not exist in new type' do + let(:current_user) { author } + let(:params) { {} } + + before do + allow(service).to receive(:new_type_excludes_widget?).and_return(true) + work_item.update!(description: 'test') + end + + it "resets the work item's description" do + expect { before_update_callback } + .to change { work_item.description } + .from('test') + .to(nil) + end + end end context 'when user does not have permission to update description' do diff --git a/spec/services/work_items/widgets/hierarchy_service/create_service_spec.rb b/spec/services/work_items/widgets/hierarchy_service/create_service_spec.rb new file mode 100644 index 00000000000..8d834c9a4f8 --- /dev/null +++ b/spec/services/work_items/widgets/hierarchy_service/create_service_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::Widgets::HierarchyService::CreateService, feature_category: :portfolio_management do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:parent_item) { create(:work_item, project: project) } + + let(:widget) { parent_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Hierarchy) } } + + shared_examples 'raises a WidgetError' do + it { expect { subject }.to raise_error(described_class::WidgetError, message) } + end + + before(:all) do + project.add_developer(user) + end + + describe '#create' do + subject { described_class.new(widget: widget, current_user: user).after_create_in_transaction(params: params) } + + context 'when invalid params are present' do + let(:params) { { other_parent: 'parent_work_item' } } + + it_behaves_like 'raises a WidgetError' do + let(:message) { 'One or more arguments are invalid: other_parent.' } + end + end + end +end diff --git a/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb b/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb index 6285b43311d..229ba81d676 100644 --- a/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb +++ b/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb @@ -14,7 +14,13 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService, feature_cate let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Hierarchy) } } let(:not_found_error) { 'No matching work item found. Make sure that you are adding a valid work item ID.' } - shared_examples 'raises a WidgetError' do + shared_examples 'raises a WidgetError' do |message| + it { expect { subject }.to raise_error(described_class::WidgetError, message) } + end + + shared_examples 'raises a WidgetError with message' do + let(:message) { not_found_error } + it { expect { subject }.to raise_error(described_class::WidgetError, message) } end @@ -24,16 +30,30 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService, feature_cate context 'when parent and children params are present' do let(:params) { { parent: parent_work_item, children: [child_work_item] } } - it_behaves_like 'raises a WidgetError' do - let(:message) { 'A Work Item can be a parent or a child, but not both.' } - end + it_behaves_like 'raises a WidgetError', 'A Work Item can be a parent or a child, but not both.' end context 'when invalid params are present' do let(:params) { { other_parent: parent_work_item } } - it_behaves_like 'raises a WidgetError' do - let(:message) { 'One or more arguments are invalid: other_parent.' } + it_behaves_like 'raises a WidgetError', 'One or more arguments are invalid: other_parent.' + end + + context 'when relative position params are incomplete' do + context 'when only adjacent_work_item is present' do + let(:params) do + { parent: parent_work_item, adjacent_work_item: child_work_item } + end + + it_behaves_like 'raises a WidgetError', described_class::INVALID_RELATIVE_POSITION_ERROR + end + + context 'when only relative_position is present' do + let(:params) do + { parent: parent_work_item, relative_position: 'AFTER' } + end + + it_behaves_like 'raises a WidgetError', described_class::INVALID_RELATIVE_POSITION_ERROR end end @@ -45,7 +65,7 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService, feature_cate context 'when user has insufficient permissions to link work items' do let(:params) { { children: [child_work_item4] } } - it_behaves_like 'raises a WidgetError' do + it_behaves_like 'raises a WidgetError with message' do let(:message) { not_found_error } end end @@ -55,7 +75,7 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService, feature_cate project.add_developer(user) end - context 'with valid params' do + context 'with valid children params' do let(:params) { { children: [child_work_item2, child_work_item3] } } it 'correctly sets work item parent' do @@ -64,14 +84,30 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService, feature_cate expect(work_item.reload.work_item_children) .to contain_exactly(child_work_item, child_work_item2, child_work_item3) end + + context 'when relative_position and adjacent_work_item are given' do + context 'with BEFORE value' do + let(:params) do + { children: [child_work_item3], relative_position: 'BEFORE', adjacent_work_item: child_work_item } + end + + it_behaves_like 'raises a WidgetError', described_class::CHILDREN_REORDERING_ERROR + end + + context 'with AFTER value' do + let(:params) do + { children: [child_work_item2], relative_position: 'AFTER', adjacent_work_item: child_work_item } + end + + it_behaves_like 'raises a WidgetError', described_class::CHILDREN_REORDERING_ERROR + end + end end context 'when child is already assigned' do let(:params) { { children: [child_work_item] } } - it_behaves_like 'raises a WidgetError' do - let(:message) { 'Work item(s) already assigned' } - end + it_behaves_like 'raises a WidgetError', 'Work item(s) already assigned' end context 'when child type is invalid' do @@ -79,10 +115,8 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService, feature_cate let(:params) { { children: [child_issue] } } - it_behaves_like 'raises a WidgetError' do - let(:message) do - "#{child_issue.to_reference} cannot be added: is not allowed to add this type of parent" - end + it_behaves_like 'raises a WidgetError with message' do + let(:message) { "#{child_issue.to_reference} cannot be added: is not allowed to add this type of parent" } end end end @@ -94,7 +128,7 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService, feature_cate let(:params) { { parent: parent_work_item } } context 'when user has insufficient permissions to link work items' do - it_behaves_like 'raises a WidgetError' do + it_behaves_like 'raises a WidgetError with message' do let(:message) { not_found_error } end end @@ -121,7 +155,7 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService, feature_cate end.to change(work_item, :work_item_parent).from(parent_work_item).to(nil) end - it 'returns success status if parent not present', :aggregate_failure do + it 'returns success status if parent not present', :aggregate_failures do work_item.update!(work_item_parent: nil) expect(subject[:status]).to eq(:success) @@ -134,10 +168,34 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService, feature_cate let(:params) { { parent: parent_task } } - it_behaves_like 'raises a WidgetError' do - let(:message) do - "#{work_item.to_reference} cannot be added: is not allowed to add this type of parent" + it_behaves_like 'raises a WidgetError with message' do + let(:message) { "#{work_item.to_reference} cannot be added: is not allowed to add this type of parent" } + end + end + + context 'with positioning arguments' do + let_it_be_with_reload(:adjacent) { create(:work_item, :task, project: project) } + + let_it_be_with_reload(:adjacent_link) do + create(:parent_link, work_item: adjacent, work_item_parent: parent_work_item) + end + + let(:params) { { parent: parent_work_item, adjacent_work_item: adjacent, relative_position: 'AFTER' } } + + it 'correctly sets new parent and position' do + expect(subject[:status]).to eq(:success) + expect(work_item.work_item_parent).to eq(parent_work_item) + expect(work_item.parent_link.relative_position).to be > adjacent_link.relative_position + end + + context 'when other hierarchy adjacent is provided' do + let_it_be(:other_hierarchy_adjacent) { create(:parent_link).work_item } + + let(:params) do + { parent: parent_work_item, adjacent_work_item: other_hierarchy_adjacent, relative_position: 'AFTER' } end + + it_behaves_like 'raises a WidgetError', described_class::UNRELATED_ADJACENT_HIERARCHY_ERROR end end end diff --git a/spec/services/work_items/widgets/labels_service/update_service_spec.rb b/spec/services/work_items/widgets/labels_service/update_service_spec.rb new file mode 100644 index 00000000000..17daec2b1ea --- /dev/null +++ b/spec/services/work_items/widgets/labels_service/update_service_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::Widgets::LabelsService::UpdateService, feature_category: :team_planning do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:label1) { create(:label, project: project) } + let_it_be(:label2) { create(:label, project: project) } + let_it_be(:label3) { create(:label, project: project) } + let_it_be(:current_user) { create(:user) } + + let(:work_item) { create(:work_item, project: project, labels: [label1, label2]) } + let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Labels) } } + let(:service) { described_class.new(widget: widget, current_user: current_user) } + + describe '#prepare_update_params' do + context 'when params are set' do + let(:params) { { add_label_ids: [label1.id], remove_label_ids: [label2.id] } } + + it "sets params correctly" do + expect(service.prepare_update_params(params: params)).to include( + { + add_label_ids: match_array([label1.id]), + remove_label_ids: match_array([label2.id]) + } + ) + end + end + + context 'when widget does not exist in new type' do + let(:params) { {} } + + before do + allow(service).to receive(:new_type_excludes_widget?).and_return(true) + end + + it "sets correct params to remove work item labels" do + expect(service.prepare_update_params(params: params)).to include( + { + remove_label_ids: match_array([label1.id, label2.id]), + add_label_ids: [] + } + ) + end + end + end +end diff --git a/spec/services/work_items/widgets/milestone_service/create_service_spec.rb b/spec/services/work_items/widgets/milestone_service/create_service_spec.rb deleted file mode 100644 index 3f90784b703..00000000000 --- a/spec/services/work_items/widgets/milestone_service/create_service_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe WorkItems::Widgets::MilestoneService::CreateService do - let_it_be(:group) { create(:group) } - let_it_be(:project) { create(:project, :private, group: group) } - let_it_be(:project_milestone) { create(:milestone, project: project) } - let_it_be(:group_milestone) { create(:milestone, group: group) } - let_it_be(:guest) { create(:user) } - - let(:current_user) { guest } - let(:work_item) { build(:work_item, project: project, updated_at: 1.day.ago) } - let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Milestone) } } - let(:service) { described_class.new(widget: widget, current_user: current_user) } - - before do - project.add_guest(guest) - end - - describe '#before_create_callback' do - it_behaves_like "setting work item's milestone" do - subject(:execute_callback) do - service.before_create_callback(params: params) - end - end - end -end diff --git a/spec/services/work_items/widgets/milestone_service/update_service_spec.rb b/spec/services/work_items/widgets/milestone_service/update_service_spec.rb deleted file mode 100644 index f3a7fc156b9..00000000000 --- a/spec/services/work_items/widgets/milestone_service/update_service_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe WorkItems::Widgets::MilestoneService::UpdateService do - let_it_be(:group) { create(:group) } - let_it_be(:project) { create(:project, :private, group: group) } - let_it_be(:project_milestone) { create(:milestone, project: project) } - let_it_be(:group_milestone) { create(:milestone, group: group) } - let_it_be(:reporter) { create(:user) } - let_it_be(:guest) { create(:user) } - - let(:work_item) { create(:work_item, project: project, updated_at: 1.day.ago) } - let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Milestone) } } - let(:service) { described_class.new(widget: widget, current_user: current_user) } - - before do - project.add_reporter(reporter) - project.add_guest(guest) - end - - describe '#before_update_callback' do - context 'when current user is not allowed to set work item metadata' do - let(:current_user) { guest } - let(:params) { { milestone_id: group_milestone.id } } - - it "does not set the work item's milestone" do - expect { service.before_update_callback(params: params) } - .to not_change(work_item, :milestone) - end - end - - context "when current user is allowed to set work item metadata" do - let(:current_user) { reporter } - - it_behaves_like "setting work item's milestone" do - subject(:execute_callback) do - service.before_update_callback(params: params) - end - end - - context 'when unsetting a milestone' do - let(:params) { { milestone_id: nil } } - - before do - work_item.update!(milestone: project_milestone) - end - - it "sets the work item's milestone" do - expect { service.before_update_callback(params: params) } - .to change(work_item, :milestone) - .from(project_milestone) - .to(nil) - end - end - end - end -end diff --git a/spec/services/work_items/widgets/notifications_service/update_service_spec.rb b/spec/services/work_items/widgets/notifications_service/update_service_spec.rb new file mode 100644 index 00000000000..9615020fe49 --- /dev/null +++ b/spec/services/work_items/widgets/notifications_service/update_service_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::Widgets::NotificationsService::UpdateService, feature_category: :team_planning do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :private, group: group) } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:author) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be_with_reload(:work_item) { create(:work_item, project: project, author: author) } + let_it_be(:current_user) { guest } + + let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::Notifications) } } + let(:service) { described_class.new(widget: widget, current_user: current_user) } + + describe '#before_update_in_transaction' do + let(:expected) { params[:subscribed] } + + subject(:update_notifications) { service.before_update_in_transaction(params: params) } + + shared_examples 'failing to update subscription' do + context 'when user is subscribed with a subscription record' do + let_it_be(:subscription) { create_subscription(:subscribed) } + + it "does not update the work item's subscription" do + expect do + update_notifications + subscription.reload + end.to not_change { subscription.subscribed } + .and(not_change { work_item.subscribed?(current_user, project) }) + end + end + + context 'when user is subscribed by being a participant' do + let_it_be(:current_user) { author } + + it 'does not create subscription record or change subscription state' do + expect { update_notifications } + .to not_change { Subscription.count } + .and(not_change { work_item.subscribed?(current_user, project) }) + end + end + end + + shared_examples 'updating notifications subscription successfully' do + it 'updates existing subscription record' do + expect do + update_notifications + subscription.reload + end.to change { subscription.subscribed }.to(expected) + .and(change { work_item.subscribed?(current_user, project) }.to(expected)) + end + end + + context 'when update fails' do + context 'when user lack update_subscription permissions' do + let_it_be(:params) { { subscribed: false } } + + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(current_user, :update_subscription, work_item) + .and_return(false) + end + + it_behaves_like 'failing to update subscription' + end + + context 'when notifications params are not present' do + let_it_be(:params) { {} } + + it_behaves_like 'failing to update subscription' + end + end + + context 'when update is successful' do + context 'when subscribing' do + let_it_be(:subscription) { create_subscription(:unsubscribed) } + let(:params) { { subscribed: true } } + + it_behaves_like 'updating notifications subscription successfully' + end + + context 'when unsubscribing' do + let(:params) { { subscribed: false } } + + context 'when user is subscribed with a subscription record' do + let_it_be(:subscription) { create_subscription(:subscribed) } + + it_behaves_like 'updating notifications subscription successfully' + end + + context 'when user is subscribed by being a participant' do + let_it_be(:current_user) { author } + + it 'creates a subscription with expected value' do + expect { update_notifications } + .to change { Subscription.count }.by(1) + .and(change { work_item.subscribed?(current_user, project) }.to(expected)) + + expect(Subscription.last.subscribed).to eq(expected) + end + end + end + end + end + + def create_subscription(state) + create( + :subscription, + project: project, + user: current_user, + subscribable: work_item, + subscribed: (state == :subscribed) + ) + end +end diff --git a/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb b/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb index d328c541fc7..0196e7c2b02 100644 --- a/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb +++ b/spec/services/work_items/widgets/start_and_due_date_service/update_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WorkItems::Widgets::StartAndDueDateService::UpdateService do +RSpec.describe WorkItems::Widgets::StartAndDueDateService::UpdateService, feature_category: :portfolio_management do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be_with_reload(:work_item) { create(:work_item, project: project) } @@ -12,10 +12,9 @@ RSpec.describe WorkItems::Widgets::StartAndDueDateService::UpdateService do describe '#before_update_callback' do let(:start_date) { Date.today } let(:due_date) { 1.week.from_now.to_date } + let(:service) { described_class.new(widget: widget, current_user: user) } - subject(:update_params) do - described_class.new(widget: widget, current_user: user).before_update_callback(params: params) - end + subject(:update_params) { service.before_update_callback(params: params) } context 'when start and due date params are present' do let(:params) { { start_date: Date.today, due_date: 1.week.from_now.to_date } } @@ -58,5 +57,22 @@ RSpec.describe WorkItems::Widgets::StartAndDueDateService::UpdateService do end end end + + context 'when widget does not exist in new type' do + let(:params) { {} } + + before do + allow(service).to receive(:new_type_excludes_widget?).and_return(true) + work_item.update!(start_date: start_date, due_date: due_date) + end + + it 'sets both dates to null' do + expect do + update_params + end.to change(work_item, :start_date).from(start_date).to(nil).and( + change(work_item, :due_date).from(due_date).to(nil) + ) + end + end end end diff --git a/spec/services/x509_certificate_revoke_service_spec.rb b/spec/services/x509_certificate_revoke_service_spec.rb index ff5d2dc058b..460381afd79 100644 --- a/spec/services/x509_certificate_revoke_service_spec.rb +++ b/spec/services/x509_certificate_revoke_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe X509CertificateRevokeService do +RSpec.describe X509CertificateRevokeService, feature_category: :system_access do describe '#execute' do let(:service) { described_class.new } let!(:x509_signature_1) { create(:x509_commit_signature, x509_certificate: x509_certificate, verification_status: :verified) } |