Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-12 15:10:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-12 15:10:24 +0300
commit71a67d17b02e7b8dec2f4c257f6734dc7818fb1e (patch)
tree6b45633257869a67fd534bd2cf899b93a48794c0 /spec
parent133ec9237af290062aae70e6f115db69b51c88de (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/integrations_controller_spec.rb4
-rw-r--r--spec/controllers/groups/settings/integrations_controller_spec.rb4
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb29
-rw-r--r--spec/controllers/projects/mattermosts_controller_spec.rb2
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb11
-rw-r--r--spec/controllers/projects/services_controller_spec.rb2
-rw-r--r--spec/factories/chat_names.rb4
-rw-r--r--spec/factories/integration_data.rb (renamed from spec/factories/services_data.rb)6
-rw-r--r--spec/factories/integrations.rb (renamed from spec/factories/services.rb)35
-rw-r--r--spec/factories/service_hooks.rb2
-rw-r--r--spec/features/admin/services/admin_visits_service_templates_spec.rb2
-rw-r--r--spec/features/groups/members/manage_groups_spec.rb26
-rw-r--r--spec/features/groups/settings/packages_and_registries_spec.rb31
-rw-r--r--spec/features/profiles/chat_names_spec.rb6
-rw-r--r--spec/features/projects/members/invite_group_spec.rb17
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb162
-rw-r--r--spec/frontend/code_quality_walkthrough/components/__snapshots__/step_spec.js.snap174
-rw-r--r--spec/frontend/code_quality_walkthrough/components/step_spec.js156
-rw-r--r--spec/frontend/commit/pipelines/pipelines_spec.js1
-rw-r--r--spec/frontend/jobs/components/job_app_spec.js1
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap18
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js146
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js54
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js82
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js150
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js25
-rw-r--r--spec/frontend/packages_and_registries/settings/group/mock_data.js4
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js20
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js5
-rw-r--r--spec/graphql/types/ci/pipeline_type_spec.rb2
-rw-r--r--spec/graphql/types/projects/services_enum_spec.rb2
-rw-r--r--spec/lib/gitlab/chat/responder_spec.rb4
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml4
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb16
-rw-r--r--spec/lib/gitlab/integrations/sti_type_spec.rb8
-rw-r--r--spec/models/chat_name_spec.rb6
-rw-r--r--spec/models/ci/build_spec.rb8
-rw-r--r--spec/models/concerns/has_integrations_spec.rb (renamed from spec/models/concerns/integration_spec.rb)2
-rw-r--r--spec/models/container_repository_spec.rb95
-rw-r--r--spec/models/group_spec.rb2
-rw-r--r--spec/models/hooks/service_hook_spec.rb4
-rw-r--r--spec/models/integration_spec.rb (renamed from spec/models/service_spec.rb)113
-rw-r--r--spec/models/label_link_spec.rb11
-rw-r--r--spec/models/project_services/data_fields_spec.rb4
-rw-r--r--spec/models/project_services/issue_tracker_data_spec.rb6
-rw-r--r--spec/models/project_services/jira_service_spec.rb4
-rw-r--r--spec/models/project_services/jira_tracker_data_spec.rb2
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/open_project_tracker_data_spec.rb4
-rw-r--r--spec/models/project_spec.rb28
-rw-r--r--spec/policies/integration_policy_spec.rb (renamed from spec/policies/service_policy_spec.rb)2
-rw-r--r--spec/presenters/project_presenter_spec.rb6
-rw-r--r--spec/presenters/service_hook_presenter_spec.rb2
-rw-r--r--spec/presenters/web_hook_log_presenter_spec.rb4
-rw-r--r--spec/requests/api/merge_requests_spec.rb14
-rw-r--r--spec/requests/api/services_spec.rb6
-rw-r--r--spec/serializers/pipeline_details_entity_spec.rb14
-rw-r--r--spec/services/admin/propagate_service_template_spec.rb4
-rw-r--r--spec/services/bulk_create_integration_service_spec.rb4
-rw-r--r--spec/services/bulk_update_integration_service_spec.rb2
-rw-r--r--spec/services/chat_names/find_user_service_spec.rb8
-rw-r--r--spec/services/container_expiration_policies/cleanup_service_spec.rb250
-rw-r--r--spec/services/groups/create_service_spec.rb18
-rw-r--r--spec/services/groups/transfer_service_spec.rb4
-rw-r--r--spec/services/issuable/destroy_label_links_service_spec.rb19
-rw-r--r--spec/services/issuable/destroy_service_spec.rb8
-rw-r--r--spec/services/projects/create_service_spec.rb24
-rw-r--r--spec/services/projects/transfer_service_spec.rb2
-rw-r--r--spec/support/helpers/features/invite_members_modal_helper.rb32
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb4
-rw-r--r--spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/destroy_label_links_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb30
-rw-r--r--spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb357
-rw-r--r--spec/workers/issuable/label_links_destroy_worker_spec.rb17
-rw-r--r--spec/workers/project_service_worker_spec.rb2
-rw-r--r--spec/workers/projects/post_creation_worker_spec.rb2
78 files changed, 1786 insertions, 583 deletions
diff --git a/spec/controllers/admin/integrations_controller_spec.rb b/spec/controllers/admin/integrations_controller_spec.rb
index e14619a9916..971f2f121aa 100644
--- a/spec/controllers/admin/integrations_controller_spec.rb
+++ b/spec/controllers/admin/integrations_controller_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Admin::IntegrationsController do
end
describe '#edit' do
- Service.available_services_names.each do |integration_name|
+ Integration.available_services_names.each do |integration_name|
context "#{integration_name}" do
it 'successfully displays the template' do
get :edit, params: { id: integration_name }
@@ -27,7 +27,7 @@ RSpec.describe Admin::IntegrationsController do
end
it 'returns 404' do
- get :edit, params: { id: Service.available_services_names.sample }
+ get :edit, params: { id: Integration.available_services_names.sample }
expect(response).to have_gitlab_http_status(:not_found)
end
diff --git a/spec/controllers/groups/settings/integrations_controller_spec.rb b/spec/controllers/groups/settings/integrations_controller_spec.rb
index 3233e814184..63d99a1fab1 100644
--- a/spec/controllers/groups/settings/integrations_controller_spec.rb
+++ b/spec/controllers/groups/settings/integrations_controller_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Groups::Settings::IntegrationsController do
describe '#edit' do
context 'when user is not owner' do
it 'renders not_found' do
- get :edit, params: { group_id: group, id: Service.available_services_names(include_project_specific: false).sample }
+ get :edit, params: { group_id: group, id: Integration.available_services_names(include_project_specific: false).sample }
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -47,7 +47,7 @@ RSpec.describe Groups::Settings::IntegrationsController do
group.add_owner(user)
end
- Service.available_services_names(include_project_specific: false).each do |integration_name|
+ Integration.available_services_names(include_project_specific: false).each do |integration_name|
context "#{integration_name}" do
it 'successfully displays the template' do
get :edit, params: { group_id: group, id: integration_name }
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 4bc2a235eec..b965feee645 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -464,11 +464,36 @@ RSpec.describe Projects::BlobController do
sign_in(user)
end
- it_behaves_like 'tracking unique hll events' do
- subject(:request) { post :create, params: default_params }
+ subject(:request) { post :create, params: default_params }
+ it_behaves_like 'tracking unique hll events' do
let(:target_id) { 'g_edit_by_sfe' }
let(:expected_type) { instance_of(Integer) }
end
+
+ it 'redirects to blob' do
+ request
+
+ expect(response).to redirect_to(project_blob_path(project, 'master/docs/EXAMPLE_FILE'))
+ end
+
+ context 'when code_quality_walkthrough param is present' do
+ let(:default_params) { super().merge(code_quality_walkthrough: true) }
+
+ it 'redirects to the pipelines page' do
+ request
+
+ expect(response).to redirect_to(project_pipelines_path(project, code_quality_walkthrough: true))
+ end
+
+ it 'creates an "commit_created" experiment tracking event' do
+ experiment = double(track: true)
+ expect(controller).to receive(:experiment).with(:code_quality_walkthrough, namespace: project.root_ancestor).and_return(experiment)
+
+ request
+
+ expect(experiment).to have_received(:track).with(:commit_created)
+ end
+ end
end
end
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index 001f2564698..10bcee28f71 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -60,7 +60,7 @@ RSpec.describe Projects::MattermostsController do
it 'redirects to the new page' do
subject
- service = project.services.last
+ service = project.integrations.last
expect(subject).to redirect_to(edit_project_service_url(project, service))
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index fc9c67e9e97..dc06389d8b4 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -288,6 +288,17 @@ RSpec.describe Projects::PipelinesController do
get :index, params: { namespace_id: project.namespace, project_id: project }
end
end
+
+ context 'code_quality_walkthrough experiment' do
+ it 'tracks the view', :experiment do
+ expect(experiment(:code_quality_walkthrough))
+ .to track(:view, property: project.root_ancestor.id.to_s)
+ .with_context(namespace: project.root_ancestor)
+ .on_next_instance
+
+ get :index, params: { namespace_id: project.namespace, project_id: project }
+ end
+ end
end
describe 'GET #show' do
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 687a979edc1..d8fb3b226ed 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Projects::ServicesController do
describe '#test' do
context 'when can_test? returns false' do
it 'renders 404' do
- allow_any_instance_of(Service).to receive(:can_test?).and_return(false)
+ allow_any_instance_of(Integration).to receive(:can_test?).and_return(false)
put :test, params: project_params
diff --git a/spec/factories/chat_names.rb b/spec/factories/chat_names.rb
index 73c885806f2..56567394bf5 100644
--- a/spec/factories/chat_names.rb
+++ b/spec/factories/chat_names.rb
@@ -2,8 +2,8 @@
FactoryBot.define do
factory :chat_name, class: 'ChatName' do
- user factory: :user
- service factory: :service
+ user
+ integration
team_id { 'T0001' }
team_domain { 'Awesome Team' }
diff --git a/spec/factories/services_data.rb b/spec/factories/integration_data.rb
index 7b6a705c791..2541a3d2da3 100644
--- a/spec/factories/services_data.rb
+++ b/spec/factories/integration_data.rb
@@ -4,15 +4,15 @@
# The factories are used when creating integrations.
FactoryBot.define do
factory :jira_tracker_data do
- service
+ integration factory: :jira_service
end
factory :issue_tracker_data do
- service
+ integration
end
factory :open_project_tracker_data do
- service
+ integration factory: :open_project_service
url { 'http://openproject.example.com'}
token { 'supersecret' }
project_identifier_code { 'PRJ-1' }
diff --git a/spec/factories/services.rb b/spec/factories/integrations.rb
index 51d9898dbe4..8883e9e05ff 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/integrations.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
FactoryBot.define do
- factory :service do
+ factory :integration, aliases: [:service] do
project
- type { 'Service' }
+ type { 'Integration' }
end
factory :custom_issue_tracker_service, class: 'CustomIssueTrackerService' do
@@ -65,15 +65,15 @@ FactoryBot.define do
deployment_type { 'cloud' }
end
- before(:create) do |service, evaluator|
+ after(:build) do |integration, evaluator|
if evaluator.create_data
- create(:jira_tracker_data, service: service,
- url: evaluator.url, api_url: evaluator.api_url,
- jira_issue_transition_automatic: evaluator.jira_issue_transition_automatic,
- jira_issue_transition_id: evaluator.jira_issue_transition_id,
- username: evaluator.username, password: evaluator.password, issues_enabled: evaluator.issues_enabled,
- project_key: evaluator.project_key, vulnerabilities_enabled: evaluator.vulnerabilities_enabled,
- vulnerabilities_issuetype: evaluator.vulnerabilities_issuetype, deployment_type: evaluator.deployment_type
+ integration.jira_tracker_data = build(:jira_tracker_data,
+ integration: integration, url: evaluator.url, api_url: evaluator.api_url,
+ jira_issue_transition_automatic: evaluator.jira_issue_transition_automatic,
+ jira_issue_transition_id: evaluator.jira_issue_transition_id,
+ username: evaluator.username, password: evaluator.password, issues_enabled: evaluator.issues_enabled,
+ project_key: evaluator.project_key, vulnerabilities_enabled: evaluator.vulnerabilities_enabled,
+ vulnerabilities_issuetype: evaluator.vulnerabilities_issuetype, deployment_type: evaluator.deployment_type
)
end
end
@@ -117,10 +117,11 @@ FactoryBot.define do
new_issue_url { 'http://new-issue.example.com' }
end
- before(:create) do |service, evaluator|
+ after(:build) do |integration, evaluator|
if evaluator.create_data
- create(:issue_tracker_data, service: service,
- project_url: evaluator.project_url, issues_url: evaluator.issues_url, new_issue_url: evaluator.new_issue_url
+ integration.issue_tracker_data = build(:issue_tracker_data,
+ integration: integration, project_url: evaluator.project_url,
+ issues_url: evaluator.issues_url, new_issue_url: evaluator.new_issue_url
)
end
end
@@ -145,9 +146,9 @@ FactoryBot.define do
project_identifier_code { 'PRJ-1' }
end
- before(:create) do |service, evaluator|
- create(:open_project_tracker_data, service: service,
- url: evaluator.url, api_url: evaluator.api_url, token: evaluator.token,
+ after(:build) do |integration, evaluator|
+ integration.open_project_tracker_data = build(:open_project_tracker_data,
+ integration: integration, url: evaluator.url, api_url: evaluator.api_url, token: evaluator.token,
closed_status_id: evaluator.closed_status_id, project_identifier_code: evaluator.project_identifier_code
)
end
@@ -180,7 +181,7 @@ FactoryBot.define do
issue_tracker_data { nil }
create_data { false }
- after(:build) do |service|
+ after(:build) do
IssueTrackerService.skip_callback(:validation, :before, :handle_properties)
end
diff --git a/spec/factories/service_hooks.rb b/spec/factories/service_hooks.rb
index ff819f4f8d0..ea70d2fc433 100644
--- a/spec/factories/service_hooks.rb
+++ b/spec/factories/service_hooks.rb
@@ -3,6 +3,6 @@
FactoryBot.define do
factory :service_hook do
url { generate(:url) }
- service
+ integration
end
end
diff --git a/spec/features/admin/services/admin_visits_service_templates_spec.rb b/spec/features/admin/services/admin_visits_service_templates_spec.rb
index 1fd8c8316e3..9d011b97f63 100644
--- a/spec/features/admin/services/admin_visits_service_templates_spec.rb
+++ b/spec/features/admin/services/admin_visits_service_templates_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe 'Admin visits service templates' do
let(:admin) { create(:user, :admin) }
- let(:slack_service) { Service.for_template.find { |s| s.type == 'SlackService' } }
+ let(:slack_service) { Integration.for_template.find { |s| s.type == 'SlackService' } }
before do
sign_in(admin)
diff --git a/spec/features/groups/members/manage_groups_spec.rb b/spec/features/groups/members/manage_groups_spec.rb
index 3b56305f711..40cd54c1e33 100644
--- a/spec/features/groups/members/manage_groups_spec.rb
+++ b/spec/features/groups/members/manage_groups_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe 'Groups > Members > Manage groups', :js do
include Select2Helper
include Spec::Support::Helpers::Features::MembersHelpers
+ include Spec::Support::Helpers::Features::InviteMembersModalHelper
let_it_be(:user) { create(:user) }
@@ -13,17 +14,21 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
end
context 'with invite_members_group_modal disabled' do
+ before do
+ stub_feature_flags(invite_members_group_modal: false)
+ end
+
context 'when group link does not exist' do
let_it_be(:group) { create(:group) }
let_it_be(:group_to_add) { create(:group) }
before do
- stub_feature_flags(invite_members_group_modal: false)
group.add_owner(user)
+ group_to_add.add_owner(user)
visit group_group_members_path(group)
end
- it 'add group to group' do
+ it 'can share group with group' do
add_group(group_to_add.id, 'Reporter')
click_groups_tab
@@ -44,7 +49,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
group_to_add.add_owner(user)
visit group_group_members_path(group)
- invite_group(group_to_add.name, 'Reporter')
+ invite_group(group_to_add.name, role: 'Reporter')
click_groups_tab
@@ -147,21 +152,6 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
end
end
- def invite_group(name, role)
- click_on 'Invite a group'
-
- click_on 'Select a group'
- wait_for_requests
- click_button name
-
- click_button 'Guest'
- wait_for_requests
- click_button role
-
- click_button 'Invite'
- page.refresh
- end
-
def click_groups_tab
expect(page).to have_link 'Groups'
click_link "Groups"
diff --git a/spec/features/groups/settings/packages_and_registries_spec.rb b/spec/features/groups/settings/packages_and_registries_spec.rb
index 45ea77e3868..551a0bc5375 100644
--- a/spec/features/groups/settings/packages_and_registries_spec.rb
+++ b/spec/features/groups/settings/packages_and_registries_spec.rb
@@ -66,28 +66,31 @@ RSpec.describe 'Group Packages & Registries settings' do
it 'automatically saves changes to the server', :js do
visit_settings_page
- expect(page).to have_content('Allow duplicates')
+ within '[data-testid="maven-settings"]' do
+ expect(page).to have_content('Allow duplicates')
- find('.gl-toggle').click
+ find('.gl-toggle').click
- expect(page).to have_content('Do not allow duplicates')
+ expect(page).to have_content('Do not allow duplicates')
- visit_settings_page
+ visit_settings_page
- expect(page).to have_content('Do not allow duplicates')
+ expect(page).to have_content('Do not allow duplicates')
+ end
end
it 'shows an error on wrong regex', :js do
visit_settings_page
- expect(page).to have_content('Allow duplicates')
-
- find('.gl-toggle').click
+ within '[data-testid="maven-settings"]' do
+ expect(page).to have_content('Allow duplicates')
- expect(page).to have_content('Do not allow duplicates')
+ find('.gl-toggle').click
- fill_in 'Exceptions', with: ')'
+ expect(page).to have_content('Do not allow duplicates')
+ fill_in 'Exceptions', with: ')'
+ end
# simulate blur event
find('body').click
@@ -98,11 +101,13 @@ RSpec.describe 'Group Packages & Registries settings' do
it 'works correctly', :js do
visit_sub_group_settings_page
- expect(page).to have_content('Allow duplicates')
+ within '[data-testid="maven-settings"]' do
+ expect(page).to have_content('Allow duplicates')
- find('.gl-toggle').click
+ find('.gl-toggle').click
- expect(page).to have_content('Do not allow duplicates')
+ expect(page).to have_content('Do not allow duplicates')
+ end
end
end
end
diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb
index ca888018cad..6270fa7347d 100644
--- a/spec/features/profiles/chat_names_spec.rb
+++ b/spec/features/profiles/chat_names_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe 'Profile > Chat' do
let(:user) { create(:user) }
- let(:service) { create(:service) }
+ let(:integration) { create(:service) }
before do
sign_in(user)
@@ -15,7 +15,7 @@ RSpec.describe 'Profile > Chat' do
{ team_id: 'T00', team_domain: 'my_chat_team', user_id: 'U01', user_name: 'my_chat_user' }
end
- let!(:authorize_url) { ChatNames::AuthorizeUserService.new(service, params).execute }
+ let!(:authorize_url) { ChatNames::AuthorizeUserService.new(integration, params).execute }
let(:authorize_path) { URI.parse(authorize_url).request_uri }
before do
@@ -60,7 +60,7 @@ RSpec.describe 'Profile > Chat' do
end
describe 'visits chat accounts' do
- let!(:chat_name) { create(:chat_name, user: user, service: service) }
+ let!(:chat_name) { create(:chat_name, user: user, integration: integration) }
before do
visit profile_chat_names_path
diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb
index d728c7b1a73..4caf3e947c7 100644
--- a/spec/features/projects/members/invite_group_spec.rb
+++ b/spec/features/projects/members/invite_group_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
include Select2Helper
include ActionView::Helpers::DateHelper
include Spec::Support::Helpers::Features::MembersHelpers
+ include Spec::Support::Helpers::Features::InviteMembersModalHelper
let(:maintainer) { create(:user) }
@@ -88,12 +89,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
expect(page).not_to have_link 'Groups'
- click_on 'Invite a group'
-
- click_on 'Select a group'
- wait_for_requests
- click_button group_to_share_with.name
- click_button 'Invite'
+ invite_group(group_to_share_with.name)
visit project_project_members_path(project)
@@ -172,14 +168,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
visit project_project_members_path(project)
- click_on 'Invite a group'
- click_on 'Select a group'
- wait_for_requests
- click_button group.name
- fill_in 'YYYY-MM-DD', with: 5.days.from_now.strftime('%Y-%m-%d')
- click_button 'Invite'
-
- page.refresh
+ invite_group(group.name, role: 'Guest', expires_at: 5.days.from_now)
end
it 'the group link shows the expiration time with a warning class' do
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
index 6df2743b165..d8a55fc7f3b 100644
--- a/spec/features/projects/releases/user_views_releases_spec.rb
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -19,133 +19,129 @@ RSpec.describe 'User views releases', :js do
project.add_guest(guest)
end
- shared_examples 'releases page' do
- context('when the user is a maintainer') do
- before do
- sign_in(maintainer)
+ context('when the user is a maintainer') do
+ before do
+ sign_in(maintainer)
- visit project_releases_path(project)
+ visit project_releases_path(project)
+ end
+
+ it 'sees the release' do
+ page.within("##{release_v1.tag}") do
+ expect(page).to have_content(release_v1.name)
+ expect(page).to have_content(release_v1.tag)
+ expect(page).not_to have_content('Upcoming Release')
end
+ end
- it 'sees the release' do
- page.within("##{release_v1.tag}") do
- expect(page).to have_content(release_v1.name)
- expect(page).to have_content(release_v1.tag)
- expect(page).not_to have_content('Upcoming Release')
+ context 'when there is a link as an asset' do
+ let!(:release_link) { create(:release_link, release: release_v1, url: url ) }
+ let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
+ let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release_v1) << "/downloads#{release_link.filepath}" }
+
+ it 'sees the link' do
+ page.within("##{release_v1.tag} .js-assets-list") do
+ expect(page).to have_link release_link.name, href: direct_asset_link
+ expect(page).not_to have_css('[data-testid="external-link-indicator"]')
end
end
- context 'when there is a link as an asset' do
- let!(:release_link) { create(:release_link, release: release_v1, url: url ) }
+ context 'when there is a link redirect' do
+ let!(:release_link) { create(:release_link, release: release_v1, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) }
let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
- let(:direct_asset_link) { Gitlab::Routing.url_helpers.project_release_url(project, release_v1) << "/downloads#{release_link.filepath}" }
- it 'sees the link' do
+ it 'sees the link', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/329301' do
page.within("##{release_v1.tag} .js-assets-list") do
expect(page).to have_link release_link.name, href: direct_asset_link
expect(page).not_to have_css('[data-testid="external-link-indicator"]')
end
end
+ end
- context 'when there is a link redirect' do
- let!(:release_link) { create(:release_link, release: release_v1, name: 'linux-amd64 binaries', filepath: '/binaries/linux-amd64', url: url) }
- let(:url) { "#{project.web_url}/-/jobs/1/artifacts/download" }
-
- it 'sees the link', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/329301' do
- page.within("##{release_v1.tag} .js-assets-list") do
- expect(page).to have_link release_link.name, href: direct_asset_link
- expect(page).not_to have_css('[data-testid="external-link-indicator"]')
- end
- end
- end
-
- context 'when url points to external resource' do
- let(:url) { 'http://google.com/download' }
+ context 'when url points to external resource' do
+ let(:url) { 'http://google.com/download' }
- it 'sees that the link is external resource', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/329302' do
- page.within("##{release_v1.tag} .js-assets-list") do
- expect(page).to have_css('[data-testid="external-link-indicator"]')
- end
+ it 'sees that the link is external resource', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/329302' do
+ page.within("##{release_v1.tag} .js-assets-list") do
+ expect(page).to have_css('[data-testid="external-link-indicator"]')
end
end
end
+ end
- context 'with an upcoming release' do
- it 'sees the upcoming tag' do
- page.within("##{release_v3.tag}") do
- expect(page).to have_content('Upcoming Release')
- end
+ context 'with an upcoming release' do
+ it 'sees the upcoming tag' do
+ page.within("##{release_v3.tag}") do
+ expect(page).to have_content('Upcoming Release')
end
end
+ end
- context 'with a tag containing a slash' do
- it 'sees the release' do
- page.within("##{release_v2.tag.parameterize}") do
- expect(page).to have_content(release_v2.name)
- expect(page).to have_content(release_v2.tag)
- end
+ context 'with a tag containing a slash' do
+ it 'sees the release' do
+ page.within("##{release_v2.tag.parameterize}") do
+ expect(page).to have_content(release_v2.name)
+ expect(page).to have_content(release_v2.tag)
end
end
+ end
- context 'sorting' do
- def sort_page(by:, direction:)
- within '[data-testid="releases-sort"]' do
- find('.dropdown-toggle').click
+ context 'sorting' do
+ def sort_page(by:, direction:)
+ within '[data-testid="releases-sort"]' do
+ find('.dropdown-toggle').click
- click_button(by, class: 'dropdown-item')
+ click_button(by, class: 'dropdown-item')
- find('.sorting-direction-button').click if direction == :ascending
- end
+ find('.sorting-direction-button').click if direction == :ascending
end
+ end
- shared_examples 'releases sort order' do
- it "sorts the releases #{description}" do
- card_titles = page.all('.release-block .card-title', minimum: expected_releases.count)
+ shared_examples 'releases sort order' do
+ it "sorts the releases #{description}" do
+ card_titles = page.all('.release-block .card-title', minimum: expected_releases.count)
- card_titles.each_with_index do |title, index|
- expect(title).to have_content(expected_releases[index].name)
- end
+ card_titles.each_with_index do |title, index|
+ expect(title).to have_content(expected_releases[index].name)
end
end
+ end
- context "when the page is sorted by the default sort order" do
- let(:expected_releases) { [release_v3, release_v2, release_v1] }
+ context "when the page is sorted by the default sort order" do
+ let(:expected_releases) { [release_v3, release_v2, release_v1] }
- it_behaves_like 'releases sort order'
- end
+ it_behaves_like 'releases sort order'
+ end
- context "when the page is sorted by created_at ascending " do
- let(:expected_releases) { [release_v2, release_v1, release_v3] }
+ context "when the page is sorted by created_at ascending " do
+ let(:expected_releases) { [release_v2, release_v1, release_v3] }
- before do
- sort_page by: 'Created date', direction: :ascending
- end
-
- it_behaves_like 'releases sort order'
+ before do
+ sort_page by: 'Created date', direction: :ascending
end
+
+ it_behaves_like 'releases sort order'
end
end
+ end
- context('when the user is a guest') do
- before do
- sign_in(guest)
- end
+ context('when the user is a guest') do
+ before do
+ sign_in(guest)
+ end
- it 'renders release info except for Git-related data' do
- visit project_releases_path(project)
+ it 'renders release info except for Git-related data' do
+ visit project_releases_path(project)
- within('.release-block', match: :first) do
- expect(page).to have_content(release_v3.description)
+ within('.release-block', match: :first) do
+ expect(page).to have_content(release_v3.description)
- # The following properties (sometimes) include Git info,
- # so they are not rendered for Guest users
- expect(page).not_to have_content(release_v3.name)
- expect(page).not_to have_content(release_v3.tag)
- expect(page).not_to have_content(release_v3.commit.short_id)
- end
+ # The following properties (sometimes) include Git info,
+ # so they are not rendered for Guest users
+ expect(page).not_to have_content(release_v3.name)
+ expect(page).not_to have_content(release_v3.tag)
+ expect(page).not_to have_content(release_v3.commit.short_id)
end
end
end
-
- it_behaves_like 'releases page'
end
diff --git a/spec/frontend/code_quality_walkthrough/components/__snapshots__/step_spec.js.snap b/spec/frontend/code_quality_walkthrough/components/__snapshots__/step_spec.js.snap
new file mode 100644
index 00000000000..f17d99ad257
--- /dev/null
+++ b/spec/frontend/code_quality_walkthrough/components/__snapshots__/step_spec.js.snap
@@ -0,0 +1,174 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`When the code_quality_walkthrough URL parameter is present Code Quality Walkthrough Step component commit_ci_file step renders a popover 1`] = `
+<div>
+ <gl-popover-stub
+ container="viewport"
+ cssclasses=""
+ offset="90"
+ placement="right"
+ show=""
+ target="#js-code-quality-walkthrough"
+ triggers="manual"
+ >
+
+ <gl-sprintf-stub
+ message="To begin with code quality, we first need to create a new CI file using our code editor. We added a code quality template in the code editor to help you get started %{emojiStart}wink%{emojiEnd} .%{lineBreak}Take some time to review the template, when you are ready, use the %{strongStart}commit changes%{strongEnd} button at the bottom of the page."
+ />
+
+ <div
+ class="gl-mt-2 gl-text-right"
+ >
+ <gl-button-stub
+ buttontextclasses=""
+ category="tertiary"
+ href=""
+ icon=""
+ size="medium"
+ variant="link"
+ >
+
+ Got it
+
+ </gl-button-stub>
+ </div>
+ </gl-popover-stub>
+
+ <!---->
+</div>
+`;
+
+exports[`When the code_quality_walkthrough URL parameter is present Code Quality Walkthrough Step component failed_pipeline step renders a popover 1`] = `
+<div>
+ <gl-popover-stub
+ container="viewport"
+ cssclasses=""
+ offset="98"
+ placement="bottom"
+ show=""
+ target="#js-code-quality-walkthrough"
+ triggers="manual"
+ >
+
+ <gl-sprintf-stub
+ message="Your job failed. No worries - this happens. Let's view the logs, and see how we can fix it."
+ />
+
+ <div
+ class="gl-mt-2 gl-text-right"
+ >
+ <gl-button-stub
+ buttontextclasses=""
+ category="tertiary"
+ href="/group/project/-/jobs/:id?code_quality_walkthrough=true"
+ icon=""
+ size="medium"
+ variant="link"
+ >
+
+ View the logs
+
+ </gl-button-stub>
+ </div>
+ </gl-popover-stub>
+
+ <!---->
+</div>
+`;
+
+exports[`When the code_quality_walkthrough URL parameter is present Code Quality Walkthrough Step component running_pipeline step renders a popover 1`] = `
+<div>
+ <gl-popover-stub
+ container="viewport"
+ cssclasses=""
+ offset="97"
+ placement="bottom"
+ show=""
+ target="#js-code-quality-walkthrough"
+ triggers="manual"
+ >
+
+ <gl-sprintf-stub
+ message="Your pipeline can take a few minutes to run. If you enabled email notifications, you'll receive an email with your pipeline status. In the meantime, why don't you get some coffee? You earned it!"
+ />
+
+ <div
+ class="gl-mt-2 gl-text-right"
+ >
+ <gl-button-stub
+ buttontextclasses=""
+ category="tertiary"
+ href=""
+ icon=""
+ size="medium"
+ variant="link"
+ >
+
+ Got it
+
+ </gl-button-stub>
+ </div>
+ </gl-popover-stub>
+
+ <!---->
+</div>
+`;
+
+exports[`When the code_quality_walkthrough URL parameter is present Code Quality Walkthrough Step component success_pipeline step renders a popover 1`] = `
+<div>
+ <gl-popover-stub
+ container="viewport"
+ cssclasses=""
+ offset="98"
+ placement="bottom"
+ show=""
+ target="#js-code-quality-walkthrough"
+ triggers="manual"
+ >
+
+ <gl-sprintf-stub
+ message="A code quality job will now run every time you or your team members commit changes to your project. You can view the results of the code quality job in the job logs."
+ />
+
+ <div
+ class="gl-mt-2 gl-text-right"
+ >
+ <gl-button-stub
+ buttontextclasses=""
+ category="tertiary"
+ href="/group/project/-/jobs/:id?code_quality_walkthrough=true"
+ icon=""
+ size="medium"
+ variant="link"
+ >
+
+ View the logs
+
+ </gl-button-stub>
+ </div>
+ </gl-popover-stub>
+
+ <!---->
+</div>
+`;
+
+exports[`When the code_quality_walkthrough URL parameter is present Code Quality Walkthrough Step component troubleshoot_job step renders an alert 1`] = `
+<div>
+ <!---->
+
+ <gl-alert-stub
+ class="gl-my-5"
+ dismissible="true"
+ dismisslabel="Dismiss"
+ primarybuttontext="Read the documentation"
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ title="Troubleshoot your code quality job"
+ variant="tip"
+ >
+
+ Not sure how to fix your failed job? We have compiled some tips on how to troubleshoot code quality jobs in the documentation.
+
+ </gl-alert-stub>
+</div>
+`;
diff --git a/spec/frontend/code_quality_walkthrough/components/step_spec.js b/spec/frontend/code_quality_walkthrough/components/step_spec.js
new file mode 100644
index 00000000000..c397faf1f35
--- /dev/null
+++ b/spec/frontend/code_quality_walkthrough/components/step_spec.js
@@ -0,0 +1,156 @@
+import { GlButton, GlPopover } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Cookies from 'js-cookie';
+import Step from '~/code_quality_walkthrough/components/step.vue';
+import { EXPERIMENT_NAME, STEPS } from '~/code_quality_walkthrough/constants';
+import { TRACKING_CONTEXT_SCHEMA } from '~/experimentation/constants';
+import { getParameterByName } from '~/lib/utils/common_utils';
+import Tracking from '~/tracking';
+
+jest.mock('~/lib/utils/common_utils', () => ({
+ ...jest.requireActual('~/lib/utils/common_utils'),
+ getParameterByName: jest.fn(),
+}));
+
+let wrapper;
+
+function factory({ step, link }) {
+ wrapper = shallowMount(Step, {
+ propsData: { step, link },
+ });
+}
+
+afterEach(() => {
+ wrapper.destroy();
+});
+
+const dummyLink = '/group/project/-/jobs/:id?code_quality_walkthrough=true';
+const dummyContext = 'experiment_context';
+
+const findButton = () => wrapper.findComponent(GlButton);
+const findPopover = () => wrapper.findComponent(GlPopover);
+
+describe('When the code_quality_walkthrough URL parameter is missing', () => {
+ beforeEach(() => {
+ getParameterByName.mockReturnValue(false);
+ });
+
+ it('does not render the component', () => {
+ factory({
+ step: STEPS.commitCiFile,
+ });
+
+ expect(findPopover().exists()).toBe(false);
+ });
+});
+
+describe('When the code_quality_walkthrough URL parameter is present', () => {
+ beforeEach(() => {
+ getParameterByName.mockReturnValue(true);
+ Cookies.set(EXPERIMENT_NAME, { data: dummyContext });
+ });
+
+ afterEach(() => {
+ Cookies.remove(EXPERIMENT_NAME);
+ });
+
+ describe('When mounting the component', () => {
+ beforeEach(() => {
+ jest.spyOn(Tracking, 'event');
+
+ factory({
+ step: STEPS.commitCiFile,
+ });
+ });
+
+ it('tracks an event', () => {
+ expect(Tracking.event).toHaveBeenCalledWith(
+ EXPERIMENT_NAME,
+ `${STEPS.commitCiFile}_displayed`,
+ {
+ context: {
+ schema: TRACKING_CONTEXT_SCHEMA,
+ data: dummyContext,
+ },
+ },
+ );
+ });
+ });
+
+ describe('When updating the component', () => {
+ beforeEach(() => {
+ factory({
+ step: STEPS.runningPipeline,
+ });
+
+ jest.spyOn(Tracking, 'event');
+
+ wrapper.setProps({ step: STEPS.successPipeline });
+ });
+
+ it('tracks an event', () => {
+ expect(Tracking.event).toHaveBeenCalledWith(
+ EXPERIMENT_NAME,
+ `${STEPS.successPipeline}_displayed`,
+ {
+ context: {
+ schema: TRACKING_CONTEXT_SCHEMA,
+ data: dummyContext,
+ },
+ },
+ );
+ });
+ });
+
+ describe('When dismissing a popover', () => {
+ beforeEach(() => {
+ factory({
+ step: STEPS.commitCiFile,
+ });
+
+ jest.spyOn(Cookies, 'set');
+ jest.spyOn(Tracking, 'event');
+
+ findButton().vm.$emit('click');
+ });
+
+ it('sets a cookie', () => {
+ expect(Cookies.set).toHaveBeenCalledWith(
+ EXPERIMENT_NAME,
+ { commit_ci_file: true, data: dummyContext },
+ { expires: 365 },
+ );
+ });
+
+ it('removes the popover', () => {
+ expect(findPopover().exists()).toBe(false);
+ });
+
+ it('tracks an event', () => {
+ expect(Tracking.event).toHaveBeenCalledWith(
+ EXPERIMENT_NAME,
+ `${STEPS.commitCiFile}_dismissed`,
+ {
+ context: {
+ schema: TRACKING_CONTEXT_SCHEMA,
+ data: dummyContext,
+ },
+ },
+ );
+ });
+ });
+
+ describe('Code Quality Walkthrough Step component', () => {
+ describe.each(Object.values(STEPS))('%s step', (step) => {
+ it(`renders ${step === STEPS.troubleshootJob ? 'an alert' : 'a popover'}`, () => {
+ const options = { step };
+ if ([STEPS.successPipeline, STEPS.failedPipeline].includes(step)) {
+ options.link = dummyLink;
+ }
+ factory(options);
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/commit/pipelines/pipelines_spec.js b/spec/frontend/commit/pipelines/pipelines_spec.js
index bbe02daa24b..fe928a01acf 100644
--- a/spec/frontend/commit/pipelines/pipelines_spec.js
+++ b/spec/frontend/commit/pipelines/pipelines_spec.js
@@ -1,3 +1,4 @@
+import '~/commons';
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
diff --git a/spec/frontend/jobs/components/job_app_spec.js b/spec/frontend/jobs/components/job_app_spec.js
index 2974e91e46d..3fcefde1aba 100644
--- a/spec/frontend/jobs/components/job_app_spec.js
+++ b/spec/frontend/jobs/components/job_app_spec.js
@@ -35,6 +35,7 @@ describe('Job App', () => {
const props = {
artifactHelpUrl: 'help/artifact',
deploymentHelpUrl: 'help/deployment',
+ codeQualityHelpPath: '/help/code_quality',
runnerSettingsUrl: 'settings/ci-cd/runners',
variablesSettingsUrl: 'settings/ci-cd/variables',
terminalPath: 'jobs/123/terminal',
diff --git a/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap b/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
new file mode 100644
index 00000000000..f2087733d2b
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/__snapshots__/settings_titles_spec.js.snap
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`settings_titles renders properly 1`] = `
+<div>
+ <h5
+ class="gl-border-b-solid gl-border-b-1 gl-border-gray-200"
+ >
+
+ foo
+
+ </h5>
+
+ <p>
+ bar
+ </p>
+
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js
new file mode 100644
index 00000000000..0bbb1ce3436
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/duplicates_settings_spec.js
@@ -0,0 +1,146 @@
+import { GlSprintf, GlToggle, GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import component from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
+
+import {
+ DUPLICATES_TOGGLE_LABEL,
+ DUPLICATES_ALLOWED_ENABLED,
+ DUPLICATES_ALLOWED_DISABLED,
+ DUPLICATES_SETTING_EXCEPTION_TITLE,
+ DUPLICATES_SETTINGS_EXCEPTION_LEGEND,
+} from '~/packages_and_registries/settings/group/constants';
+
+describe('Duplicates Settings', () => {
+ let wrapper;
+
+ const defaultProps = {
+ duplicatesAllowed: false,
+ duplicateExceptionRegex: 'foo',
+ modelNames: {
+ allowed: 'allowedModel',
+ exception: 'exceptionModel',
+ },
+ };
+
+ const mountComponent = (propsData = defaultProps) => {
+ wrapper = shallowMount(component, {
+ propsData,
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findToggle = () => wrapper.findComponent(GlToggle);
+ const findToggleLabel = () => wrapper.find('[data-testid="toggle-label"');
+
+ const findInputGroup = () => wrapper.findComponent(GlFormGroup);
+ const findInput = () => wrapper.findComponent(GlFormInput);
+
+ it('has a toggle', () => {
+ mountComponent();
+
+ expect(findToggle().exists()).toBe(true);
+ expect(findToggle().props()).toMatchObject({
+ label: DUPLICATES_TOGGLE_LABEL,
+ value: defaultProps.duplicatesAllowed,
+ });
+ });
+
+ it('toggle emits an update event', () => {
+ mountComponent();
+
+ findToggle().vm.$emit('change', false);
+
+ expect(wrapper.emitted('update')).toStrictEqual([
+ [{ [defaultProps.modelNames.allowed]: false }],
+ ]);
+ });
+
+ describe('when the duplicates are disabled', () => {
+ it('the toggle has the disabled message', () => {
+ mountComponent();
+
+ expect(findToggleLabel().exists()).toBe(true);
+ expect(findToggleLabel().text()).toMatchInterpolatedText(DUPLICATES_ALLOWED_DISABLED);
+ });
+
+ it('shows a form group with an input field', () => {
+ mountComponent();
+
+ expect(findInputGroup().exists()).toBe(true);
+
+ expect(findInputGroup().attributes()).toMatchObject({
+ 'label-for': 'maven-duplicated-settings-regex-input',
+ label: DUPLICATES_SETTING_EXCEPTION_TITLE,
+ description: DUPLICATES_SETTINGS_EXCEPTION_LEGEND,
+ });
+ });
+
+ it('shows an input field', () => {
+ mountComponent();
+
+ expect(findInput().exists()).toBe(true);
+
+ expect(findInput().attributes()).toMatchObject({
+ id: 'maven-duplicated-settings-regex-input',
+ value: defaultProps.duplicateExceptionRegex,
+ });
+ });
+
+ it('input change event emits an update event', () => {
+ mountComponent();
+
+ findInput().vm.$emit('change', 'bar');
+
+ expect(wrapper.emitted('update')).toStrictEqual([
+ [{ [defaultProps.modelNames.exception]: 'bar' }],
+ ]);
+ });
+
+ describe('valid state', () => {
+ it('form group has correct props', () => {
+ mountComponent();
+
+ expect(findInputGroup().attributes()).toMatchObject({
+ state: 'true',
+ 'invalid-feedback': '',
+ });
+ });
+ });
+
+ describe('invalid state', () => {
+ it('form group has correct props', () => {
+ const propsWithError = {
+ ...defaultProps,
+ duplicateExceptionRegexError: 'some error string',
+ };
+
+ mountComponent(propsWithError);
+
+ expect(findInputGroup().attributes()).toMatchObject({
+ 'invalid-feedback': propsWithError.duplicateExceptionRegexError,
+ });
+ });
+ });
+ });
+
+ describe('when the duplicates are enabled', () => {
+ it('has the correct toggle label', () => {
+ mountComponent({ ...defaultProps, duplicatesAllowed: true });
+
+ expect(findToggleLabel().exists()).toBe(true);
+ expect(findToggleLabel().text()).toMatchInterpolatedText(DUPLICATES_ALLOWED_ENABLED);
+ });
+
+ it('hides the form input group', () => {
+ mountComponent({ ...defaultProps, duplicatesAllowed: true });
+
+ expect(findInputGroup().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js
new file mode 100644
index 00000000000..4eafeedd55e
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/generic_settings_spec.js
@@ -0,0 +1,54 @@
+import { shallowMount } from '@vue/test-utils';
+import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
+import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
+
+describe('generic_settings', () => {
+ let wrapper;
+
+ const mountComponent = () => {
+ wrapper = shallowMount(GenericSettings, {
+ scopedSlots: {
+ default: '<div data-testid="default-slot">{{props.modelNames}}</div>',
+ },
+ });
+ };
+
+ const findSettingsTitle = () => wrapper.findComponent(SettingsTitles);
+ const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('title component', () => {
+ it('has a title component', () => {
+ mountComponent();
+
+ expect(findSettingsTitle().exists()).toBe(true);
+ });
+
+ it('passes the correct props', () => {
+ mountComponent();
+
+ expect(findSettingsTitle().props()).toMatchObject({
+ title: 'Generic',
+ subTitle: 'Settings for Generic packages',
+ });
+ });
+ });
+
+ describe('default slot', () => {
+ it('accept a default slots', () => {
+ mountComponent();
+
+ expect(findDefaultSlot().exists()).toBe(true);
+ });
+
+ it('binds model names', () => {
+ mountComponent();
+
+ expect(findDefaultSlot().text()).toContain('genericDuplicatesAllowed');
+ expect(findDefaultSlot().text()).toContain('genericDuplicateExceptionRegex');
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
index 3d95afc3954..14ee3f3e3b8 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
@@ -3,6 +3,8 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
+import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
import component from '~/packages_and_registries/settings/group/components/group_settings_app.vue';
import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
import {
@@ -63,6 +65,8 @@ describe('Group Settings App', () => {
stubs: {
GlSprintf,
SettingsBlock,
+ MavenSettings,
+ GenericSettings,
},
mocks: {
$toast: {
@@ -78,14 +82,17 @@ describe('Group Settings App', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- const findSettingsBlock = () => wrapper.find(SettingsBlock);
+ const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
const findDescription = () => wrapper.find('[data-testid="description"');
- const findLink = () => wrapper.find(GlLink);
- const findMavenSettings = () => wrapper.find(MavenSettings);
- const findAlert = () => wrapper.find(GlAlert);
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findMavenSettings = () => wrapper.findComponent(MavenSettings);
+ const findMavenDuplicatedSettings = () => findMavenSettings().findComponent(DuplicatesSettings);
+ const findGenericSettings = () => wrapper.findComponent(GenericSettings);
+ const findGenericDuplicatedSettings = () =>
+ findGenericSettings().findComponent(DuplicatesSettings);
const waitForApolloQueryAndRender = async () => {
await waitForPromises();
@@ -93,7 +100,7 @@ describe('Group Settings App', () => {
};
const emitSettingsUpdate = (override) => {
- findMavenSettings().vm.$emit('update', {
+ findMavenDuplicatedSettings().vm.$emit('update', {
mavenDuplicateExceptionRegex: ')',
...override,
});
@@ -152,7 +159,7 @@ describe('Group Settings App', () => {
it('assigns duplication allowness and exception props', async () => {
mountComponent();
- expect(findMavenSettings().props('loading')).toBe(true);
+ expect(findMavenDuplicatedSettings().props('loading')).toBe(true);
await waitForApolloQueryAndRender();
@@ -161,10 +168,10 @@ describe('Group Settings App', () => {
mavenDuplicateExceptionRegex,
} = groupPackageSettingsMock.data.group.packageSettings;
- expect(findMavenSettings().props()).toMatchObject({
- mavenDuplicatesAllowed,
- mavenDuplicateExceptionRegex,
- mavenDuplicateExceptionRegexError: '',
+ expect(findMavenDuplicatedSettings().props()).toMatchObject({
+ duplicatesAllowed: mavenDuplicatesAllowed,
+ duplicateExceptionRegex: mavenDuplicateExceptionRegex,
+ duplicateExceptionRegexError: '',
loading: false,
});
});
@@ -183,6 +190,49 @@ describe('Group Settings App', () => {
});
});
+ describe('generic settings', () => {
+ it('exists', () => {
+ mountComponent();
+
+ expect(findGenericSettings().exists()).toBe(true);
+ });
+
+ it('assigns duplication allowness and exception props', async () => {
+ mountComponent();
+
+ expect(findGenericDuplicatedSettings().props('loading')).toBe(true);
+
+ await waitForApolloQueryAndRender();
+
+ const {
+ genericDuplicatesAllowed,
+ genericDuplicateExceptionRegex,
+ } = groupPackageSettingsMock.data.group.packageSettings;
+
+ expect(findGenericDuplicatedSettings().props()).toMatchObject({
+ duplicatesAllowed: genericDuplicatesAllowed,
+ duplicateExceptionRegex: genericDuplicateExceptionRegex,
+ duplicateExceptionRegexError: '',
+ loading: false,
+ });
+ });
+
+ it('on update event calls the mutation', async () => {
+ const mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock());
+ mountComponent({ mutationResolver });
+
+ await waitForApolloQueryAndRender();
+
+ findMavenDuplicatedSettings().vm.$emit('update', {
+ genericDuplicateExceptionRegex: ')',
+ });
+
+ expect(mutationResolver).toHaveBeenCalledWith({
+ input: { genericDuplicateExceptionRegex: ')', namespacePath: 'foo_group_path' },
+ });
+ });
+ });
+
describe('settings update', () => {
describe('success state', () => {
it('shows a success alert', async () => {
@@ -205,21 +255,21 @@ describe('Group Settings App', () => {
await waitForApolloQueryAndRender();
- expect(findMavenSettings().props('mavenDuplicateExceptionRegex')).toBe('');
+ expect(findMavenDuplicatedSettings().props('duplicateExceptionRegex')).toBe('');
emitSettingsUpdate({ mavenDuplicateExceptionRegex });
// wait for apollo to update the model with the optimistic response
await wrapper.vm.$nextTick();
- expect(findMavenSettings().props('mavenDuplicateExceptionRegex')).toBe(
+ expect(findMavenDuplicatedSettings().props('duplicateExceptionRegex')).toBe(
mavenDuplicateExceptionRegex,
);
// wait for the call to resolve
await waitForPromises();
- expect(findMavenSettings().props('mavenDuplicateExceptionRegex')).toBe(
+ expect(findMavenDuplicatedSettings().props('duplicateExceptionRegex')).toBe(
mavenDuplicateExceptionRegex,
);
});
@@ -245,7 +295,7 @@ describe('Group Settings App', () => {
await waitForApolloQueryAndRender();
// errors are bound to the component
- expect(findMavenSettings().props('mavenDuplicateExceptionRegexError')).toBe(
+ expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe(
groupPackageSettingsMutationErrorMock.errors[0].extensions.problems[0].message,
);
@@ -258,7 +308,7 @@ describe('Group Settings App', () => {
await wrapper.vm.$nextTick();
// errors are reset on mutation call
- expect(findMavenSettings().props('mavenDuplicateExceptionRegexError')).toBe('');
+ expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe('');
});
it.each`
diff --git a/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js
index 859d3587223..22644b97b43 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/maven_settings_spec.js
@@ -1,156 +1,54 @@
-import { GlSprintf, GlToggle, GlFormGroup, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import component from '~/packages_and_registries/settings/group/components/maven_settings.vue';
+import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
+import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
-import {
- MAVEN_TITLE,
- MAVEN_SETTINGS_SUBTITLE,
- MAVEN_DUPLICATES_ALLOWED_DISABLED,
- MAVEN_DUPLICATES_ALLOWED_ENABLED,
- MAVEN_SETTING_EXCEPTION_TITLE,
- MAVEN_SETTINGS_EXCEPTION_LEGEND,
-} from '~/packages_and_registries/settings/group/constants';
-
-describe('Maven Settings', () => {
+describe('maven_settings', () => {
let wrapper;
- const defaultProps = {
- mavenDuplicatesAllowed: false,
- mavenDuplicateExceptionRegex: 'foo',
- };
-
- const mountComponent = (propsData = defaultProps) => {
- wrapper = shallowMount(component, {
- propsData,
- stubs: {
- GlSprintf,
+ const mountComponent = () => {
+ wrapper = shallowMount(MavenSettings, {
+ scopedSlots: {
+ default: '<div data-testid="default-slot">{{props.modelNames}}</div>',
},
});
};
+ const findSettingsTitle = () => wrapper.findComponent(SettingsTitles);
+ const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
+
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- const findTitle = () => wrapper.find('h5');
- const findSubTitle = () => wrapper.find('p');
- const findToggle = () => wrapper.find(GlToggle);
- const findToggleLabel = () => wrapper.find('[data-testid="toggle-label"');
-
- const findInputGroup = () => wrapper.find(GlFormGroup);
- const findInput = () => wrapper.find(GlFormInput);
-
- it('has a title', () => {
- mountComponent();
-
- expect(findTitle().exists()).toBe(true);
- expect(findTitle().text()).toBe(MAVEN_TITLE);
- });
-
- it('has a subtitle', () => {
- mountComponent();
-
- expect(findSubTitle().exists()).toBe(true);
- expect(findSubTitle().text()).toBe(MAVEN_SETTINGS_SUBTITLE);
- });
-
- it('has a toggle', () => {
- mountComponent();
-
- expect(findToggle().exists()).toBe(true);
- expect(findToggle().props()).toMatchObject({
- label: component.i18n.MAVEN_TOGGLE_LABEL,
- value: defaultProps.mavenDuplicatesAllowed,
- });
- });
-
- it('toggle emits an update event', () => {
- mountComponent();
-
- findToggle().vm.$emit('change', false);
-
- expect(wrapper.emitted('update')).toEqual([[{ mavenDuplicatesAllowed: false }]]);
- });
-
- describe('when the duplicates are disabled', () => {
- it('the toggle has the disabled message', () => {
+ describe('title component', () => {
+ it('has a title component', () => {
mountComponent();
- expect(findToggleLabel().exists()).toBe(true);
- expect(findToggleLabel().text()).toMatchInterpolatedText(MAVEN_DUPLICATES_ALLOWED_DISABLED);
+ expect(findSettingsTitle().exists()).toBe(true);
});
- it('shows a form group with an input field', () => {
+ it('passes the correct props', () => {
mountComponent();
- expect(findInputGroup().exists()).toBe(true);
-
- expect(findInputGroup().attributes()).toMatchObject({
- 'label-for': 'maven-duplicated-settings-regex-input',
- label: MAVEN_SETTING_EXCEPTION_TITLE,
- description: MAVEN_SETTINGS_EXCEPTION_LEGEND,
+ expect(findSettingsTitle().props()).toMatchObject({
+ title: 'Maven',
+ subTitle: 'Settings for Maven packages',
});
});
+ });
- it('shows an input field', () => {
+ describe('default slot', () => {
+ it('accept a default slots', () => {
mountComponent();
- expect(findInput().exists()).toBe(true);
-
- expect(findInput().attributes()).toMatchObject({
- id: 'maven-duplicated-settings-regex-input',
- value: defaultProps.mavenDuplicateExceptionRegex,
- });
+ expect(findDefaultSlot().exists()).toBe(true);
});
- it('input change event emits an update event', () => {
+ it('binds model names', () => {
mountComponent();
- findInput().vm.$emit('change', 'bar');
-
- expect(wrapper.emitted('update')).toEqual([[{ mavenDuplicateExceptionRegex: 'bar' }]]);
- });
-
- describe('valid state', () => {
- it('form group has correct props', () => {
- mountComponent();
-
- expect(findInputGroup().attributes()).toMatchObject({
- state: 'true',
- 'invalid-feedback': '',
- });
- });
- });
-
- describe('invalid state', () => {
- it('form group has correct props', () => {
- const propsWithError = {
- ...defaultProps,
- mavenDuplicateExceptionRegexError: 'some error string',
- };
-
- mountComponent(propsWithError);
-
- expect(findInputGroup().attributes()).toMatchObject({
- 'invalid-feedback': propsWithError.mavenDuplicateExceptionRegexError,
- });
- });
- });
- });
-
- describe('when the duplicates are enabled', () => {
- it('has the correct toggle label', () => {
- mountComponent({ ...defaultProps, mavenDuplicatesAllowed: true });
-
- expect(findToggleLabel().exists()).toBe(true);
- expect(findToggleLabel().text()).toMatchInterpolatedText(MAVEN_DUPLICATES_ALLOWED_ENABLED);
- });
-
- it('hides the form input group', () => {
- mountComponent({ ...defaultProps, mavenDuplicatesAllowed: true });
-
- expect(findInputGroup().exists()).toBe(false);
+ expect(findDefaultSlot().text()).toContain('mavenDuplicatesAllowed');
+ expect(findDefaultSlot().text()).toContain('mavenDuplicateExceptionRegex');
});
});
});
diff --git a/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js b/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
new file mode 100644
index 00000000000..a61edad8685
--- /dev/null
+++ b/spec/frontend/packages_and_registries/settings/group/components/settings_titles_spec.js
@@ -0,0 +1,25 @@
+import { shallowMount } from '@vue/test-utils';
+import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
+
+describe('settings_titles', () => {
+ let wrapper;
+
+ const mountComponent = () => {
+ wrapper = shallowMount(SettingsTitles, {
+ propsData: {
+ title: 'foo',
+ subTitle: 'bar',
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders properly', () => {
+ mountComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/group/mock_data.js b/spec/frontend/packages_and_registries/settings/group/mock_data.js
index 5322eb4f427..65119e288a1 100644
--- a/spec/frontend/packages_and_registries/settings/group/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/group/mock_data.js
@@ -4,6 +4,8 @@ export const groupPackageSettingsMock = {
packageSettings: {
mavenDuplicatesAllowed: true,
mavenDuplicateExceptionRegex: '',
+ genericDuplicatesAllowed: true,
+ genericDuplicateExceptionRegex: '',
},
},
},
@@ -15,6 +17,8 @@ export const groupPackageSettingsMutationMock = (override) => ({
packageSettings: {
mavenDuplicatesAllowed: true,
mavenDuplicateExceptionRegex: 'latest[main]something',
+ genericDuplicatesAllowed: true,
+ genericDuplicateExceptionRegex: 'latest[main]somethingGeneric',
},
errors: [],
...override,
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index b5b19b55f0d..af0df77e0ec 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -1,3 +1,4 @@
+import '~/commons';
import { GlButton, GlEmptyState, GlFilteredSearch, GlLoadingIcon, GlPagination } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
@@ -6,6 +7,7 @@ import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
+import { getExperimentVariant } from '~/experimentation/utils';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
@@ -19,6 +21,10 @@ import TablePagination from '~/vue_shared/components/pagination/table_pagination
import { stageReply, users, mockSearch, branches } from './mock_data';
jest.mock('~/flash');
+jest.mock('~/experimentation/utils', () => ({
+ ...jest.requireActual('~/experimentation/utils'),
+ getExperimentVariant: jest.fn().mockReturnValue('control'),
+}));
const mockProjectPath = 'twitter/flight';
const mockProjectId = '21';
@@ -41,6 +47,7 @@ describe('Pipelines', () => {
ciLintPath: '/ci/lint',
resetCachePath: `${mockProjectPath}/settings/ci_cd/reset_cache`,
newPipelinePath: `${mockProjectPath}/pipelines/new`,
+ codeQualityPagePath: `${mockProjectPath}/-/new/master?commit_message=Add+.gitlab-ci.yml+and+create+a+code+quality+job&file_name=.gitlab-ci.yml&template=Code-Quality`,
};
const noPermissions = {
@@ -551,6 +558,19 @@ describe('Pipelines', () => {
);
});
+ describe('when the code_quality_walkthrough experiment is active', () => {
+ beforeAll(() => {
+ getExperimentVariant.mockReturnValue('candidate');
+ });
+
+ it('renders another CTA button', () => {
+ expect(findEmptyState().findComponent(GlButton).text()).toBe('Add a code quality job');
+ expect(findEmptyState().findComponent(GlButton).attributes('href')).toBe(
+ paths.codeQualityPagePath,
+ );
+ });
+ });
+
it('does not render filtered search', () => {
expect(findFilteredSearch().exists()).toBe(false);
});
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index 70e47b98575..68b0dfc018e 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -1,3 +1,4 @@
+import '~/commons';
import { GlTable } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -5,11 +6,11 @@ import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mi
import PipelineOperations from '~/pipelines/components/pipelines_list/pipeline_operations.vue';
import PipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_triggerer.vue';
import PipelineUrl from '~/pipelines/components/pipelines_list/pipeline_url.vue';
-import PipelinesStatusBadge from '~/pipelines/components/pipelines_list/pipelines_status_badge.vue';
import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import PipelinesTimeago from '~/pipelines/components/pipelines_list/time_ago.vue';
import eventHub from '~/pipelines/event_hub';
+import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
import CommitComponent from '~/vue_shared/components/commit.vue';
jest.mock('~/pipelines/event_hub');
@@ -42,7 +43,7 @@ describe('Pipelines Table', () => {
};
const findGlTable = () => wrapper.findComponent(GlTable);
- const findStatusBadge = () => wrapper.findComponent(PipelinesStatusBadge);
+ const findStatusBadge = () => wrapper.findComponent(CiBadge);
const findPipelineInfo = () => wrapper.findComponent(PipelineUrl);
const findTriggerer = () => wrapper.findComponent(PipelineTriggerer);
const findCommit = () => wrapper.findComponent(CommitComponent);
diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb
index 45d2748a235..c67e86a7ee1 100644
--- a/spec/graphql/types/ci/pipeline_type_spec.rb
+++ b/spec/graphql/types/ci/pipeline_type_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Types::Ci::PipelineType do
]
if Gitlab.ee?
- expected_fields += %w[security_report_summary security_report_findings]
+ expected_fields += %w[security_report_summary security_report_findings code_quality_reports]
end
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/projects/services_enum_spec.rb b/spec/graphql/types/projects/services_enum_spec.rb
index b8da9305de4..c23c652a378 100644
--- a/spec/graphql/types/projects/services_enum_spec.rb
+++ b/spec/graphql/types/projects/services_enum_spec.rb
@@ -11,5 +11,5 @@ RSpec.describe GitlabSchema.types['ServiceType'] do
end
def available_services_enum
- ::Service.available_services_types(include_dev: false).map(&:underscore).map(&:upcase)
+ ::Integration.available_services_types(include_dev: false).map(&:underscore).map(&:upcase)
end
diff --git a/spec/lib/gitlab/chat/responder_spec.rb b/spec/lib/gitlab/chat/responder_spec.rb
index 6603dbe8d52..803f30da9e7 100644
--- a/spec/lib/gitlab/chat/responder_spec.rb
+++ b/spec/lib/gitlab/chat/responder_spec.rb
@@ -16,8 +16,8 @@ RSpec.describe Gitlab::Chat::Responder do
it 'returns the responder for the build' do
pipeline = create(:ci_pipeline)
build = create(:ci_build, pipeline: pipeline)
- service = double(:service, chat_responder: Gitlab::Chat::Responder::Slack)
- chat_name = double(:chat_name, service: service)
+ integration = double(:integration, chat_responder: Gitlab::Chat::Responder::Slack)
+ chat_name = double(:chat_name, integration: integration)
chat_data = double(:chat_data, chat_name: chat_name)
allow(pipeline)
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index 417bf3e363a..28291508ac0 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -137,11 +137,11 @@ RSpec.describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
it 'creates a Prometheus service' do
expect(result[:status]).to eq(:success)
- services = result[:project].reload.services
+ integrations = result[:project].reload.integrations
- expect(services.count).to eq(1)
+ expect(integrations.count).to eq(1)
# Ensures PrometheusService#self_monitoring_project? is true
- expect(services.first.allow_local_api_url?).to be_truthy
+ expect(integrations.first.allow_local_api_url?).to be_truthy
end
it 'creates an environment for the project' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 340556c0dc4..2e63735195d 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -303,7 +303,7 @@ deploy_keys:
- user
- deploy_keys_projects
- projects
-services:
+integrations:
- project
- service_hook
- jira_tracker_data
@@ -356,7 +356,7 @@ project:
- management_clusters
- boards
- last_event
-- services
+- integrations
- campfire_service
- confluence_service
- datadog_service
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index dc668e972cf..ed4436b7257 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -71,6 +71,22 @@ RSpec.describe Gitlab::ImportExport::FileImporter do
it 'creates the file in the right subfolder' do
expect(shared.export_path).to include('test/abcd')
end
+
+ context 'when the import file is remote' do
+ include AfterNextHelpers
+
+ it 'downloads the file from a remote object storage' do
+ file_url = 'https://remote.url/file'
+ import_export_upload = build(:import_export_upload, remote_import_url: file_url)
+ project = build( :project, import_export_upload: import_export_upload)
+
+ expect_next(described_class)
+ .to receive(:download)
+ .with(file_url, kind_of(String))
+
+ described_class.import(importable: project, archive_file: nil, shared: shared)
+ end
+ end
end
context 'error' do
diff --git a/spec/lib/gitlab/integrations/sti_type_spec.rb b/spec/lib/gitlab/integrations/sti_type_spec.rb
index 7a023d6041c..3154872ed04 100644
--- a/spec/lib/gitlab/integrations/sti_type_spec.rb
+++ b/spec/lib/gitlab/integrations/sti_type_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::Integrations::StiType do
it 'forms SQL SELECT statements correctly' do
sql_statements = types.map do |type|
- Service.where(type: type).to_sql
+ Integration.where(type: type).to_sql
end
expect(sql_statements).to all(eq(expected_sql))
@@ -31,7 +31,7 @@ RSpec.describe Gitlab::Integrations::StiType do
it 'forms SQL CREATE statements correctly' do
sql_statements = types.map do |type|
- record = ActiveRecord::QueryRecorder.new { Service.insert({ type: type }) }
+ record = ActiveRecord::QueryRecorder.new { Integration.insert({ type: type }) }
record.log.first
end
@@ -69,7 +69,7 @@ RSpec.describe Gitlab::Integrations::StiType do
it 'forms SQL DELETE statements correctly' do
sql_statements = types.map do |type|
- record = ActiveRecord::QueryRecorder.new { Service.delete_by(type: type) }
+ record = ActiveRecord::QueryRecorder.new { Integration.delete_by(type: type) }
record.log.first
end
@@ -93,7 +93,7 @@ RSpec.describe Gitlab::Integrations::StiType do
create(:service, type: 'AsanaService')
types.each do |type|
- expect(Service.find_by(type: type)).to be_kind_of(Integrations::Asana)
+ expect(Integration.find_by(type: type)).to be_kind_of(Integrations::Asana)
end
end
end
diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb
index 623e55aad21..4d77bd53158 100644
--- a/spec/models/chat_name_spec.rb
+++ b/spec/models/chat_name_spec.rb
@@ -6,11 +6,11 @@ RSpec.describe ChatName do
let_it_be(:chat_name) { create(:chat_name) }
subject { chat_name }
- it { is_expected.to belong_to(:service) }
+ it { is_expected.to belong_to(:integration) }
it { is_expected.to belong_to(:user) }
it { is_expected.to validate_presence_of(:user) }
- it { is_expected.to validate_presence_of(:service) }
+ it { is_expected.to validate_presence_of(:integration) }
it { is_expected.to validate_presence_of(:team_id) }
it { is_expected.to validate_presence_of(:chat_id) }
@@ -18,7 +18,7 @@ RSpec.describe ChatName do
it { is_expected.to validate_uniqueness_of(:chat_id).scoped_to(:service_id, :team_id) }
it 'is removed when the project is deleted' do
- expect { subject.reload.service.project.delete }.to change { ChatName.count }.by(-1)
+ expect { subject.reload.integration.project.delete }.to change { ChatName.count }.by(-1)
expect(ChatName.where(id: subject.id)).not_to exist
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index e51711c3895..66d2f5f4ee9 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -4697,8 +4697,8 @@ RSpec.describe Ci::Build do
end
it 'executes services' do
- allow_next_found_instance_of(Service) do |service|
- expect(service).to receive(:async_execute)
+ allow_next_found_instance_of(Integration) do |integration|
+ expect(integration).to receive(:async_execute)
end
build.execute_hooks
@@ -4711,8 +4711,8 @@ RSpec.describe Ci::Build do
end
it 'does not execute services' do
- allow_next_found_instance_of(Service) do |service|
- expect(service).not_to receive(:async_execute)
+ allow_next_found_instance_of(Integration) do |integration|
+ expect(integration).not_to receive(:async_execute)
end
build.execute_hooks
diff --git a/spec/models/concerns/integration_spec.rb b/spec/models/concerns/has_integrations_spec.rb
index 781e2aece56..6e55a1c8b01 100644
--- a/spec/models/concerns/integration_spec.rb
+++ b/spec/models/concerns/has_integrations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Integration do
+RSpec.describe HasIntegrations do
let_it_be(:project_1) { create(:project) }
let_it_be(:project_2) { create(:project) }
let_it_be(:project_3) { create(:project) }
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 0ecefff3a97..abaae5b059a 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe ContainerRepository do
+ using RSpec::Parameterized::TableSyntax
+
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, path: 'test', group: group) }
@@ -29,18 +31,6 @@ RSpec.describe ContainerRepository do
end
end
- describe '.exists_by_path?' do
- it 'returns true for known container repository paths' do
- path = ContainerRegistry::Path.new("#{project.full_path}/#{repository.name}")
- expect(described_class.exists_by_path?(path)).to be_truthy
- end
-
- it 'returns false for unknown container repository paths' do
- path = ContainerRegistry::Path.new('you/dont/know/me')
- expect(described_class.exists_by_path?(path)).to be_falsey
- end
- end
-
describe '#tag' do
it 'has a test tag' do
expect(repository.tag('test')).not_to be_nil
@@ -359,6 +349,17 @@ RSpec.describe ContainerRepository do
it { is_expected.to contain_exactly(repository) }
end
+ describe '.expiration_policy_started_at_nil_or_before' do
+ let_it_be(:repository1) { create(:container_repository, expiration_policy_started_at: nil) }
+ let_it_be(:repository2) { create(:container_repository, expiration_policy_started_at: 1.day.ago) }
+ let_it_be(:repository3) { create(:container_repository, expiration_policy_started_at: 2.hours.ago) }
+ let_it_be(:repository4) { create(:container_repository, expiration_policy_started_at: 1.week.ago) }
+
+ subject { described_class.expiration_policy_started_at_nil_or_before(3.hours.ago) }
+
+ it { is_expected.to contain_exactly(repository1, repository2, repository4) }
+ end
+
describe '.waiting_for_cleanup' do
let_it_be(:repository_cleanup_scheduled) { create(:container_repository, :cleanup_scheduled) }
let_it_be(:repository_cleanup_unfinished) { create(:container_repository, :cleanup_unfinished) }
@@ -368,4 +369,74 @@ RSpec.describe ContainerRepository do
it { is_expected.to contain_exactly(repository_cleanup_scheduled, repository_cleanup_unfinished) }
end
+
+ describe '.exists_by_path?' do
+ it 'returns true for known container repository paths' do
+ path = ContainerRegistry::Path.new("#{project.full_path}/#{repository.name}")
+ expect(described_class.exists_by_path?(path)).to be_truthy
+ end
+
+ it 'returns false for unknown container repository paths' do
+ path = ContainerRegistry::Path.new('you/dont/know/me')
+ expect(described_class.exists_by_path?(path)).to be_falsey
+ end
+ end
+
+ describe '.with_enabled_policy' do
+ let_it_be(:repository) { create(:container_repository) }
+ let_it_be(:repository2) { create(:container_repository) }
+
+ subject { described_class.with_enabled_policy }
+
+ before do
+ repository.project.container_expiration_policy.update!(enabled: true)
+ end
+
+ it { is_expected.to eq([repository]) }
+ end
+
+ context 'with repositories' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :cleanup_unscheduled) }
+ let_it_be(:other_repository) { create(:container_repository, :cleanup_unscheduled) }
+
+ let(:policy) { repository.project.container_expiration_policy }
+
+ before do
+ ContainerExpirationPolicy.update_all(enabled: true)
+ end
+
+ describe '.requiring_cleanup' do
+ subject { described_class.requiring_cleanup }
+
+ context 'with next_run_at in the future' do
+ before do
+ policy.update_column(:next_run_at, 10.minutes.from_now)
+ end
+
+ it { is_expected.to eq([]) }
+ end
+
+ context 'with next_run_at in the past' do
+ before do
+ policy.update_column(:next_run_at, 10.minutes.ago)
+ end
+
+ it { is_expected.to eq([repository]) }
+ end
+ end
+
+ describe '.with_unfinished_cleanup' do
+ subject { described_class.with_unfinished_cleanup }
+
+ it { is_expected.to eq([]) }
+
+ context 'with an unfinished repository' do
+ before do
+ repository.cleanup_unfinished!
+ end
+
+ it { is_expected.to eq([repository]) }
+ end
+ end
+ end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 1460fe0e586..db782d16070 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Group do
it { is_expected.to have_many(:container_repositories) }
it { is_expected.to have_many(:milestones) }
it { is_expected.to have_many(:group_deploy_keys) }
- it { is_expected.to have_many(:services) }
+ it { is_expected.to have_many(:integrations) }
it { is_expected.to have_one(:dependency_proxy_setting) }
it { is_expected.to have_many(:dependency_proxy_blobs) }
it { is_expected.to have_many(:dependency_proxy_manifests) }
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index f7045d7ac5e..00ec5a1d9b9 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -4,11 +4,11 @@ require 'spec_helper'
RSpec.describe ServiceHook do
describe 'associations' do
- it { is_expected.to belong_to :service }
+ it { is_expected.to belong_to :integration }
end
describe 'validations' do
- it { is_expected.to validate_presence_of(:service) }
+ it { is_expected.to validate_presence_of(:integration) }
end
describe 'execute' do
diff --git a/spec/models/service_spec.rb b/spec/models/integration_spec.rb
index e471e50498d..77b3778122a 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/integration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Service do
+RSpec.describe Integration do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group) }
@@ -157,7 +157,7 @@ RSpec.describe Service do
end
context 'when instance-level service' do
- Service.available_services_types.each do |service_type|
+ Integration.available_services_types.each do |service_type|
let(:service) do
service_type.constantize.new(instance: true)
end
@@ -167,7 +167,7 @@ RSpec.describe Service do
end
context 'when group-level service' do
- Service.available_services_types.each do |service_type|
+ Integration.available_services_types.each do |service_type|
let(:service) do
service_type.constantize.new(group_id: group.id)
end
@@ -237,22 +237,22 @@ RSpec.describe Service do
let!(:service2) { create(:jira_service) }
it 'returns the right service' do
- expect(Service.find_or_initialize_non_project_specific_integration('jira', group_id: group)).to eq(service1)
+ expect(Integration.find_or_initialize_non_project_specific_integration('jira', group_id: group)).to eq(service1)
end
it 'does not create a new service' do
- expect { Service.find_or_initialize_non_project_specific_integration('redmine', group_id: group) }.not_to change { Service.count }
+ expect { Integration.find_or_initialize_non_project_specific_integration('redmine', group_id: group) }.not_to change { Integration.count }
end
end
describe '.find_or_initialize_all_non_project_specific' do
shared_examples 'service instances' do
it 'returns the available service instances' do
- expect(Service.find_or_initialize_all_non_project_specific(Service.for_instance).map(&:to_param)).to match_array(Service.available_services_names(include_project_specific: false))
+ expect(Integration.find_or_initialize_all_non_project_specific(Integration.for_instance).map(&:to_param)).to match_array(Integration.available_services_names(include_project_specific: false))
end
it 'does not create service instances' do
- expect { Service.find_or_initialize_all_non_project_specific(Service.for_instance) }.not_to change { Service.count }
+ expect { Integration.find_or_initialize_all_non_project_specific(Integration.for_instance) }.not_to change { Integration.count }
end
end
@@ -260,8 +260,8 @@ RSpec.describe Service do
context 'with all existing instances' do
before do
- Service.insert_all(
- Service.available_services_types(include_project_specific: false).map { |type| { instance: true, type: type } }
+ Integration.insert_all(
+ Integration.available_services_types(include_project_specific: false).map { |type| { instance: true, type: type } }
)
end
@@ -269,8 +269,8 @@ RSpec.describe Service do
context 'with a previous existing service (MockCiService) and a new service (Asana)' do
before do
- Service.insert({ type: 'MockCiService', instance: true })
- Service.delete_by(type: 'AsanaService', instance: true)
+ Integration.insert({ type: 'MockCiService', instance: true })
+ Integration.delete_by(type: 'AsanaService', instance: true)
end
it_behaves_like 'service instances'
@@ -289,34 +289,34 @@ RSpec.describe Service do
describe 'template' do
shared_examples 'retrieves service templates' do
it 'returns the available service templates' do
- expect(Service.find_or_create_templates.pluck(:type)).to match_array(Service.available_services_types(include_project_specific: false))
+ expect(Integration.find_or_create_templates.pluck(:type)).to match_array(Integration.available_services_types(include_project_specific: false))
end
end
describe '.find_or_create_templates' do
it 'creates service templates' do
- expect { Service.find_or_create_templates }.to change { Service.count }.from(0).to(Service.available_services_names(include_project_specific: false).size)
+ expect { Integration.find_or_create_templates }.to change { Integration.count }.from(0).to(Integration.available_services_names(include_project_specific: false).size)
end
it_behaves_like 'retrieves service templates'
context 'with all existing templates' do
before do
- Service.insert_all(
- Service.available_services_types(include_project_specific: false).map { |type| { template: true, type: type } }
+ Integration.insert_all(
+ Integration.available_services_types(include_project_specific: false).map { |type| { template: true, type: type } }
)
end
it 'does not create service templates' do
- expect { Service.find_or_create_templates }.not_to change { Service.count }
+ expect { Integration.find_or_create_templates }.not_to change { Integration.count }
end
it_behaves_like 'retrieves service templates'
context 'with a previous existing service (Previous) and a new service (Asana)' do
before do
- Service.insert({ type: 'PreviousService', template: true })
- Service.delete_by(type: 'AsanaService', template: true)
+ Integration.insert({ type: 'PreviousService', template: true })
+ Integration.delete_by(type: 'AsanaService', template: true)
end
it_behaves_like 'retrieves service templates'
@@ -329,7 +329,7 @@ RSpec.describe Service do
end
it 'creates the rest of the service templates' do
- expect { Service.find_or_create_templates }.to change { Service.count }.from(1).to(Service.available_services_names(include_project_specific: false).size)
+ expect { Integration.find_or_create_templates }.to change { Integration.count }.from(1).to(Integration.available_services_names(include_project_specific: false).size)
end
it_behaves_like 'retrieves service templates'
@@ -340,7 +340,7 @@ RSpec.describe Service do
context 'when integration is invalid' do
let(:integration) do
build(:prometheus_service, :template, active: true, properties: {})
- .tap { |integration| integration.save(validate: false) }
+ .tap { |integration| integration.save!(validate: false) }
end
it 'sets service to inactive' do
@@ -434,8 +434,8 @@ RSpec.describe Service do
context 'when data are stored in both properties and separated fields' do
let(:properties) { data_params }
let(:integration) do
- create(:jira_service, :without_properties_callback, active: true, template: true, properties: properties).tap do |service|
- create(:jira_tracker_data, data_params.merge(service: service))
+ create(:jira_service, :without_properties_callback, active: true, template: true, properties: properties).tap do |integration|
+ create(:jira_tracker_data, data_params.merge(integration: integration))
end
end
@@ -446,7 +446,7 @@ RSpec.describe Service do
describe "for pushover service" do
let!(:service_template) do
- PushoverService.create(
+ PushoverService.create!(
template: true,
properties: {
device: 'MyDevice',
@@ -495,6 +495,7 @@ RSpec.describe Service do
context 'with a subgroup' do
let_it_be(:subgroup) { create(:group, parent: group) }
+
let!(:project) { create(:project, group: subgroup) }
it 'returns the closest group service for a project' do
@@ -532,9 +533,9 @@ RSpec.describe Service do
it 'creates a service from the template' do
described_class.create_from_active_default_integrations(project, :project_id, with_templates: true)
- expect(project.reload.services.size).to eq(1)
- expect(project.reload.services.first.api_url).to eq(template_integration.api_url)
- expect(project.reload.services.first.inherit_from_id).to be_nil
+ expect(project.reload.integrations.size).to eq(1)
+ expect(project.reload.integrations.first.api_url).to eq(template_integration.api_url)
+ expect(project.reload.integrations.first.inherit_from_id).to be_nil
end
context 'with an active instance-level integration' do
@@ -543,18 +544,18 @@ RSpec.describe Service do
it 'creates a service from the instance-level integration' do
described_class.create_from_active_default_integrations(project, :project_id, with_templates: true)
- expect(project.reload.services.size).to eq(1)
- expect(project.reload.services.first.api_url).to eq(instance_integration.api_url)
- expect(project.reload.services.first.inherit_from_id).to eq(instance_integration.id)
+ expect(project.reload.integrations.size).to eq(1)
+ expect(project.reload.integrations.first.api_url).to eq(instance_integration.api_url)
+ expect(project.reload.integrations.first.inherit_from_id).to eq(instance_integration.id)
end
context 'passing a group' do
it 'creates a service from the instance-level integration' do
described_class.create_from_active_default_integrations(group, :group_id)
- expect(group.reload.services.size).to eq(1)
- expect(group.reload.services.first.api_url).to eq(instance_integration.api_url)
- expect(group.reload.services.first.inherit_from_id).to eq(instance_integration.id)
+ expect(group.reload.integrations.size).to eq(1)
+ expect(group.reload.integrations.first.api_url).to eq(instance_integration.api_url)
+ expect(group.reload.integrations.first.inherit_from_id).to eq(instance_integration.id)
end
end
@@ -564,9 +565,9 @@ RSpec.describe Service do
it 'creates a service from the group-level integration' do
described_class.create_from_active_default_integrations(project, :project_id, with_templates: true)
- expect(project.reload.services.size).to eq(1)
- expect(project.reload.services.first.api_url).to eq(group_integration.api_url)
- expect(project.reload.services.first.inherit_from_id).to eq(group_integration.id)
+ expect(project.reload.integrations.size).to eq(1)
+ expect(project.reload.integrations.first.api_url).to eq(group_integration.api_url)
+ expect(project.reload.integrations.first.inherit_from_id).to eq(group_integration.id)
end
context 'passing a group' do
@@ -575,9 +576,9 @@ RSpec.describe Service do
it 'creates a service from the group-level integration' do
described_class.create_from_active_default_integrations(subgroup, :group_id)
- expect(subgroup.reload.services.size).to eq(1)
- expect(subgroup.reload.services.first.api_url).to eq(group_integration.api_url)
- expect(subgroup.reload.services.first.inherit_from_id).to eq(group_integration.id)
+ expect(subgroup.reload.integrations.size).to eq(1)
+ expect(subgroup.reload.integrations.first.api_url).to eq(group_integration.api_url)
+ expect(subgroup.reload.integrations.first.inherit_from_id).to eq(group_integration.id)
end
end
@@ -589,9 +590,9 @@ RSpec.describe Service do
it 'creates a service from the subgroup-level integration' do
described_class.create_from_active_default_integrations(project, :project_id, with_templates: true)
- expect(project.reload.services.size).to eq(1)
- expect(project.reload.services.first.api_url).to eq(subgroup_integration.api_url)
- expect(project.reload.services.first.inherit_from_id).to eq(subgroup_integration.id)
+ expect(project.reload.integrations.size).to eq(1)
+ expect(project.reload.integrations.first.api_url).to eq(subgroup_integration.api_url)
+ expect(project.reload.integrations.first.inherit_from_id).to eq(subgroup_integration.id)
end
context 'passing a group' do
@@ -604,9 +605,9 @@ RSpec.describe Service do
sub_subgroup.reload
- expect(sub_subgroup.services.size).to eq(1)
- expect(sub_subgroup.services.first.api_url).to eq(subgroup_integration.api_url)
- expect(sub_subgroup.services.first.inherit_from_id).to eq(subgroup_integration.id)
+ expect(sub_subgroup.integrations.size).to eq(1)
+ expect(sub_subgroup.integrations.first.api_url).to eq(subgroup_integration.api_url)
+ expect(sub_subgroup.integrations.first.inherit_from_id).to eq(subgroup_integration.id)
end
context 'having a service inheriting settings' do
@@ -617,9 +618,9 @@ RSpec.describe Service do
sub_subgroup.reload
- expect(sub_subgroup.services.size).to eq(1)
- expect(sub_subgroup.services.first.api_url).to eq(group_integration.api_url)
- expect(sub_subgroup.services.first.inherit_from_id).to eq(group_integration.id)
+ expect(sub_subgroup.integrations.size).to eq(1)
+ expect(sub_subgroup.integrations.first.api_url).to eq(group_integration.api_url)
+ expect(sub_subgroup.integrations.first.inherit_from_id).to eq(group_integration.id)
end
end
end
@@ -681,7 +682,7 @@ RSpec.describe Service do
describe "{property}_changed?" do
let(:service) do
- Integrations::Bamboo.create(
+ Integrations::Bamboo.create!(
project: project,
properties: {
bamboo_url: 'http://gitlab.com',
@@ -714,14 +715,14 @@ RSpec.describe Service do
it "returns false when the property has been assigned a new value then saved" do
service.bamboo_url = 'http://example.com'
- service.save
+ service.save!
expect(service.bamboo_url_changed?).to be_falsy
end
end
describe "{property}_touched?" do
let(:service) do
- Integrations::Bamboo.create(
+ Integrations::Bamboo.create!(
project: project,
properties: {
bamboo_url: 'http://gitlab.com',
@@ -754,14 +755,14 @@ RSpec.describe Service do
it "returns false when the property has been assigned a new value then saved" do
service.bamboo_url = 'http://example.com'
- service.save
+ service.save!
expect(service.bamboo_url_changed?).to be_falsy
end
end
describe "{property}_was" do
let(:service) do
- Integrations::Bamboo.create(
+ Integrations::Bamboo.create!(
project: project,
properties: {
bamboo_url: 'http://gitlab.com',
@@ -794,14 +795,14 @@ RSpec.describe Service do
it "returns nil when the property has been assigned a new value then saved" do
service.bamboo_url = 'http://example.com'
- service.save
+ service.save!
expect(service.bamboo_url_was).to be_nil
end
end
describe 'initialize service with no properties' do
let(:service) do
- BugzillaService.create(
+ BugzillaService.create!(
project: project,
project_url: 'http://gitlab.example.com'
)
@@ -818,7 +819,7 @@ RSpec.describe Service do
describe '#api_field_names' do
let(:fake_service) do
- Class.new(Service) do
+ Class.new(Integration) do
def fields
[
{ name: 'token' },
@@ -940,7 +941,7 @@ RSpec.describe Service do
describe '.project_specific_services_names' do
it do
expect(described_class.project_specific_services_names)
- .to include(*described_class::PROJECT_SPECIFIC_SERVICE_NAMES)
+ .to include(*described_class::PROJECT_SPECIFIC_INTEGRATION_NAMES)
end
end
end
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index a95481f3083..c6bec215145 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -12,4 +12,15 @@ RSpec.describe LabelLink do
let(:valid_items_for_bulk_insertion) { build_list(:label_link, 10) }
let(:invalid_items_for_bulk_insertion) { [] } # class does not have any validations defined
end
+
+ describe '.for_target' do
+ it 'returns the label links for a given target' do
+ label_link = create(:label_link, target: create(:merge_request))
+
+ create(:label_link, target: create(:issue))
+
+ expect(described_class.for_target(label_link.target_id, label_link.target_type))
+ .to contain_exactly(label_link)
+ end
+ end
end
diff --git a/spec/models/project_services/data_fields_spec.rb b/spec/models/project_services/data_fields_spec.rb
index 9a3042f9f8d..d3e6afe4978 100644
--- a/spec/models/project_services/data_fields_spec.rb
+++ b/spec/models/project_services/data_fields_spec.rb
@@ -138,8 +138,8 @@ RSpec.describe DataFields do
context 'when data are stored in both properties and data_fields' do
let(:service) do
- create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |service|
- create(:jira_tracker_data, properties.merge(service: service))
+ create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |integration|
+ create(:jira_tracker_data, properties.merge(integration: integration))
end
end
diff --git a/spec/models/project_services/issue_tracker_data_spec.rb b/spec/models/project_services/issue_tracker_data_spec.rb
index 3ddb7d9250f..a229285f09b 100644
--- a/spec/models/project_services/issue_tracker_data_spec.rb
+++ b/spec/models/project_services/issue_tracker_data_spec.rb
@@ -3,9 +3,7 @@
require 'spec_helper'
RSpec.describe IssueTrackerData do
- let(:service) { create(:custom_issue_tracker_service, active: false, properties: {}) }
-
- describe 'Associations' do
- it { is_expected.to belong_to :service }
+ describe 'associations' do
+ it { is_expected.to belong_to :integration }
end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index b50fa1edbc3..73e91bf9ea8 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -433,8 +433,8 @@ RSpec.describe JiraService do
context 'when data are stored in both properties and separated fields' do
let(:properties) { data_params }
let(:service) do
- create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |service|
- create(:jira_tracker_data, data_params.merge(service: service))
+ create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |integration|
+ create(:jira_tracker_data, data_params.merge(integration: integration))
end
end
diff --git a/spec/models/project_services/jira_tracker_data_spec.rb b/spec/models/project_services/jira_tracker_data_spec.rb
index a698d3fce5f..72bdbe40a74 100644
--- a/spec/models/project_services/jira_tracker_data_spec.rb
+++ b/spec/models/project_services/jira_tracker_data_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe JiraTrackerData do
describe 'associations' do
- it { is_expected.to belong_to(:service) }
+ it { is_expected.to belong_to(:integration) }
end
describe 'deployment_type' do
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 4fff3bc56cc..87befdd4303 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe MattermostSlashCommandsService do
end
it 'saves the service' do
- expect { subject }.to change { project.services.count }.by(1)
+ expect { subject }.to change { project.integrations.count }.by(1)
end
it 'saves the token' do
diff --git a/spec/models/project_services/open_project_tracker_data_spec.rb b/spec/models/project_services/open_project_tracker_data_spec.rb
index e6a3963ba87..1f7f01cfea4 100644
--- a/spec/models/project_services/open_project_tracker_data_spec.rb
+++ b/spec/models/project_services/open_project_tracker_data_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe OpenProjectTrackerData do
- describe 'Associations' do
- it { is_expected.to belong_to(:service) }
+ describe 'associations' do
+ it { is_expected.to belong_to(:integration) }
end
describe 'closed_status_id' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index bf47e75cc7a..3b884aa2184 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to belong_to(:creator).class_name('User') }
it { is_expected.to belong_to(:pool_repository) }
it { is_expected.to have_many(:users) }
- it { is_expected.to have_many(:services) }
+ it { is_expected.to have_many(:integrations) }
it { is_expected.to have_many(:events) }
it { is_expected.to have_many(:merge_requests) }
it { is_expected.to have_many(:merge_request_metrics).class_name('MergeRequest::Metrics') }
@@ -1076,14 +1076,14 @@ RSpec.describe Project, factory_default: :keep do
it 'returns nil and does not query services when there is no external issue tracker' do
project = create(:project)
- expect(project).not_to receive(:services)
+ expect(project).not_to receive(:integrations)
expect(project.external_issue_tracker).to eq(nil)
end
it 'retrieves external_issue_tracker querying services and cache it when there is external issue tracker' do
project = create(:redmine_project)
- expect(project).to receive(:services).once.and_call_original
+ expect(project).to receive(:integrations).once.and_call_original
2.times { expect(project.external_issue_tracker).to be_a_kind_of(RedmineService) }
end
end
@@ -1116,7 +1116,7 @@ RSpec.describe Project, factory_default: :keep do
it 'becomes false when external issue tracker service is destroyed' do
expect do
- Service.find(service.id).delete
+ Integration.find(service.id).delete
end.to change { subject }.to(false)
end
@@ -1133,7 +1133,7 @@ RSpec.describe Project, factory_default: :keep do
it 'does not become false when external issue tracker service is destroyed' do
expect do
- Service.find(service.id).delete
+ Integration.find(service.id).delete
end.not_to change { subject }
end
@@ -1191,7 +1191,7 @@ RSpec.describe Project, factory_default: :keep do
it 'becomes false if the external wiki service is destroyed' do
expect do
- Service.find(service.id).delete
+ Integration.find(service.id).delete
end.to change { subject }.to(false)
end
@@ -5800,16 +5800,16 @@ RSpec.describe Project, factory_default: :keep do
end
it 'avoids N+1 database queries with more available services' do
- allow(Service).to receive(:available_services_names).and_return(%w[pushover])
+ allow(Integration).to receive(:available_services_names).and_return(%w[pushover])
control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_services }
- allow(Service).to receive(:available_services_names).and_call_original
+ allow(Integration).to receive(:available_services_names).and_call_original
expect { subject.find_or_initialize_services }.not_to exceed_query_limit(control_count)
end
context 'with disabled services' do
before do
- allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover teamcity])
+ allow(Integration).to receive(:available_services_names).and_return(%w[prometheus pushover teamcity])
allow(subject).to receive(:disabled_services).and_return(%w[prometheus])
end
@@ -5844,11 +5844,11 @@ RSpec.describe Project, factory_default: :keep do
describe '#find_or_initialize_service' do
it 'avoids N+1 database queries' do
- allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover])
+ allow(Integration).to receive(:available_services_names).and_return(%w[prometheus pushover])
control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_service('prometheus') }.count
- allow(Service).to receive(:available_services_names).and_call_original
+ allow(Integration).to receive(:available_services_names).and_call_original
expect { subject.find_or_initialize_service('prometheus') }.not_to exceed_query_limit(control_count)
end
@@ -6479,13 +6479,13 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe 'with services and chat names' do
+ describe 'with integrations and chat names' do
subject { create(:project) }
- let(:service) { create(:service, project: subject) }
+ let(:integration) { create(:service, project: subject) }
before do
- create_list(:chat_name, 5, service: service)
+ create_list(:chat_name, 5, integration: integration)
end
it 'removes chat names on removal' do
diff --git a/spec/policies/service_policy_spec.rb b/spec/policies/integration_policy_spec.rb
index 84c74ca7e31..d490045c1e1 100644
--- a/spec/policies/service_policy_spec.rb
+++ b/spec/policies/integration_policy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ServicePolicy, :models do
+RSpec.describe IntegrationPolicy, :models do
let_it_be(:user) { create(:user) }
let(:project) { integration.project }
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 1ea95b7e98c..c834e183e5e 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -794,6 +794,12 @@ RSpec.describe ProjectPresenter do
end
end
+ describe '#add_code_quality_ci_yml_path' do
+ subject { presenter.add_code_quality_ci_yml_path }
+
+ it { is_expected.to match(/code_quality_walkthrough=true.*template=Code-Quality/) }
+ end
+
describe 'empty_repo_upload_experiment?' do
subject { presenter.empty_repo_upload_experiment? }
diff --git a/spec/presenters/service_hook_presenter_spec.rb b/spec/presenters/service_hook_presenter_spec.rb
index adef34a882b..bc6e505d9e1 100644
--- a/spec/presenters/service_hook_presenter_spec.rb
+++ b/spec/presenters/service_hook_presenter_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe ServiceHookPresenter do
let(:web_hook_log) { create(:web_hook_log, web_hook: service_hook) }
- let(:service_hook) { create(:service_hook, service: service) }
+ let(:service_hook) { create(:service_hook, integration: service) }
let(:service) { create(:drone_ci_service, project: project) }
let(:project) { create(:project) }
diff --git a/spec/presenters/web_hook_log_presenter_spec.rb b/spec/presenters/web_hook_log_presenter_spec.rb
index 68c8c6e2a1b..ec930be266d 100644
--- a/spec/presenters/web_hook_log_presenter_spec.rb
+++ b/spec/presenters/web_hook_log_presenter_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe WebHookLogPresenter do
end
context 'service hook' do
- let(:web_hook) { create(:service_hook, service: service) }
+ let(:web_hook) { create(:service_hook, integration: service) }
let(:service) { create(:drone_ci_service, project: project) }
it { is_expected.to eq(project_service_hook_log_path(project, service, web_hook_log)) }
@@ -38,7 +38,7 @@ RSpec.describe WebHookLogPresenter do
end
context 'service hook' do
- let(:web_hook) { create(:service_hook, service: service) }
+ let(:web_hook) { create(:service_hook, integration: service) }
let(:service) { create(:drone_ci_service, project: project) }
it { is_expected.to eq(retry_project_service_hook_log_path(project, service, web_hook_log)) }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 37cb8fb7ee5..e4eac9ee174 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -52,7 +52,7 @@ RSpec.describe API::MergeRequests do
end
context 'when authenticated' do
- it 'avoids N+1 queries' do
+ it 'avoids N+1 queries', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/330335' do
control = ActiveRecord::QueryRecorder.new do
get api(endpoint_path, user)
end
@@ -142,7 +142,7 @@ RSpec.describe API::MergeRequests do
expect(json_response.last['labels'].first).to match_schema('/public_api/v4/label_basic')
end
- it 'avoids N+1 queries' do
+ it 'avoids N+1 queries', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/330335' do
path = endpoint_path + "?with_labels_details=true"
control = ActiveRecord::QueryRecorder.new do
@@ -973,6 +973,14 @@ RSpec.describe API::MergeRequests do
it_behaves_like 'merge requests list'
+ context 'when :api_caching_merge_requests is disabled' do
+ before do
+ stub_feature_flags(api_caching_merge_requests: false)
+ end
+
+ it_behaves_like 'merge requests list'
+ end
+
it "returns 404 for non public projects" do
project = create(:project, :private)
@@ -1049,7 +1057,7 @@ RSpec.describe API::MergeRequests do
include_context 'with merge requests'
- it 'avoids N+1 queries' do
+ it 'avoids N+1 queries', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/330335' do
control = ActiveRecord::QueryRecorder.new do
get api("/projects/#{project.id}/merge_requests", user)
end.count
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 2157e69e7bf..e52e1bf4695 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe API::Services do
end
end
- Service.available_services_names.each do |service|
+ Integration.available_services_names.each do |service|
describe "PUT /projects/:id/services/#{service.dasherize}" do
include_context service
@@ -51,7 +51,7 @@ RSpec.describe API::Services do
expect(response).to have_gitlab_http_status(:ok)
- current_service = project.services.first
+ current_service = project.integrations.first
events = current_service.event_names.empty? ? ["foo"].freeze : current_service.event_names
query_strings = []
events.each do |event|
@@ -66,7 +66,7 @@ RSpec.describe API::Services do
events.each do |event|
next if event == "foo"
- expect(project.services.first[event]).not_to eq(current_service[event]),
+ expect(project.integrations.first[event]).not_to eq(current_service[event]),
"expected #{!current_service[event]} for event #{event} for service #{current_service.title}, got #{current_service[event]}"
end
end
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
index 67f8860ed4a..128f1922887 100644
--- a/spec/serializers/pipeline_details_entity_spec.rb
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -70,6 +70,20 @@ RSpec.describe PipelineDetailsEntity do
expect(subject[:flags][:retryable]).to eq false
end
end
+
+ it 'does not contain code_quality_build_path in details' do
+ expect(subject[:details]).not_to include :code_quality_build_path
+ end
+
+ context 'when option code_quality_walkthrough is set and pipeline is a success' do
+ let(:entity) do
+ described_class.represent(pipeline, request: request, code_quality_walkthrough: true)
+ end
+
+ it 'contains details.code_quality_build_path' do
+ expect(subject[:details]).to include :code_quality_build_path
+ end
+ end
end
context 'when pipeline is cancelable' do
diff --git a/spec/services/admin/propagate_service_template_spec.rb b/spec/services/admin/propagate_service_template_spec.rb
index b3ca7601cd6..406da790a66 100644
--- a/spec/services/admin/propagate_service_template_spec.rb
+++ b/spec/services/admin/propagate_service_template_spec.rb
@@ -50,10 +50,10 @@ RSpec.describe Admin::PropagateServiceTemplate do
end
it 'does not create the service if it exists already' do
- Service.build_from_integration(service_template, project_id: project.id).save!
+ Integration.build_from_integration(service_template, project_id: project.id).save!
expect { described_class.propagate(service_template) }
- .not_to change { Service.count }
+ .not_to change { Integration.count }
end
end
end
diff --git a/spec/services/bulk_create_integration_service_spec.rb b/spec/services/bulk_create_integration_service_spec.rb
index 479309572a5..8369eb48088 100644
--- a/spec/services/bulk_create_integration_service_spec.rb
+++ b/spec/services/bulk_create_integration_service_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe BulkCreateIntegrationService do
context 'with a group association' do
let!(:group) { create(:group) }
- let(:created_integration) { Service.find_by(group: group) }
+ let(:created_integration) { Integration.find_by(group: group) }
let(:batch) { Group.where(id: group.id) }
let(:association) { 'group' }
@@ -86,7 +86,7 @@ RSpec.describe BulkCreateIntegrationService do
context 'with a group association' do
let!(:subgroup) { create(:group, parent: group) }
let(:integration) { create(:jira_service, group: group, project: nil, inherit_from_id: instance_integration.id) }
- let(:created_integration) { Service.find_by(group: subgroup) }
+ let(:created_integration) { Integration.find_by(group: subgroup) }
let(:batch) { Group.where(id: subgroup.id) }
let(:association) { 'group' }
let(:inherit_from_id) { instance_integration.id }
diff --git a/spec/services/bulk_update_integration_service_spec.rb b/spec/services/bulk_update_integration_service_spec.rb
index e20bcd44923..cd50a2a5708 100644
--- a/spec/services/bulk_update_integration_service_spec.rb
+++ b/spec/services/bulk_update_integration_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe BulkUpdateIntegrationService do
let(:excluded_attributes) { %w[id project_id group_id inherit_from_id instance template created_at updated_at] }
let(:batch) do
- Service.inherited_descendants_from_self_or_ancestors_from(subgroup_integration).where(id: group_integration.id..integration.id)
+ Integration.inherited_descendants_from_self_or_ancestors_from(subgroup_integration).where(id: group_integration.id..integration.id)
end
let_it_be(:group) { create(:group) }
diff --git a/spec/services/chat_names/find_user_service_spec.rb b/spec/services/chat_names/find_user_service_spec.rb
index a29b243ad2c..9bbad09cd0d 100644
--- a/spec/services/chat_names/find_user_service_spec.rb
+++ b/spec/services/chat_names/find_user_service_spec.rb
@@ -4,13 +4,13 @@ require 'spec_helper'
RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
describe '#execute' do
- let(:service) { create(:service) }
+ let(:integration) { create(:service) }
- subject { described_class.new(service, params).execute }
+ subject { described_class.new(integration, params).execute }
context 'find user mapping' do
let(:user) { create(:user) }
- let!(:chat_name) { create(:chat_name, user: user, service: service) }
+ let!(:chat_name) { create(:chat_name, user: user, integration: integration) }
context 'when existing user is requested' do
let(:params) { { team_id: chat_name.team_id, user_id: chat_name.chat_id } }
@@ -28,7 +28,7 @@ RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
end
it 'only updates an existing timestamp once within a certain time frame' do
- service = described_class.new(service, params)
+ service = described_class.new(integration, params)
expect(chat_name.last_used_at).to be_nil
diff --git a/spec/services/container_expiration_policies/cleanup_service_spec.rb b/spec/services/container_expiration_policies/cleanup_service_spec.rb
index 746e3464427..c6faae7449d 100644
--- a/spec/services/container_expiration_policies/cleanup_service_spec.rb
+++ b/spec/services/container_expiration_policies/cleanup_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe ContainerExpirationPolicies::CleanupService do
- let_it_be(:repository, reload: true) { create(:container_repository) }
+ let_it_be(:repository, reload: true) { create(:container_repository, expiration_policy_started_at: 30.minutes.ago) }
let_it_be(:project) { repository.project }
let(:service) { described_class.new(repository) }
@@ -11,59 +11,35 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
describe '#execute' do
subject { service.execute }
- context 'with a successful cleanup tags service execution' do
- let(:cleanup_tags_service_params) { project.container_expiration_policy.policy_params.merge('container_expiration_policy' => true) }
- let(:cleanup_tags_service) { instance_double(Projects::ContainerRepository::CleanupTagsService) }
+ shared_examples 'cleaning up a container repository' do
+ context 'with a successful cleanup tags service execution' do
+ let(:cleanup_tags_service_params) { project.container_expiration_policy.policy_params.merge('container_expiration_policy' => true) }
+ let(:cleanup_tags_service) { instance_double(Projects::ContainerRepository::CleanupTagsService) }
- it 'completely clean up the repository' do
- expect(Projects::ContainerRepository::CleanupTagsService)
- .to receive(:new).with(project, nil, cleanup_tags_service_params).and_return(cleanup_tags_service)
- expect(cleanup_tags_service).to receive(:execute).with(repository).and_return(status: :success)
+ it 'completely clean up the repository' do
+ expect(Projects::ContainerRepository::CleanupTagsService)
+ .to receive(:new).with(project, nil, cleanup_tags_service_params).and_return(cleanup_tags_service)
+ expect(cleanup_tags_service).to receive(:execute).with(repository).and_return(status: :success)
- response = subject
+ response = subject
- aggregate_failures "checking the response and container repositories" do
- expect(response.success?).to eq(true)
- expect(response.payload).to include(cleanup_status: :finished, container_repository_id: repository.id)
- expect(ContainerRepository.waiting_for_cleanup.count).to eq(0)
- expect(repository.reload.cleanup_unscheduled?).to be_truthy
- expect(repository.expiration_policy_started_at).to eq(nil)
- expect(repository.expiration_policy_completed_at).not_to eq(nil)
+ aggregate_failures "checking the response and container repositories" do
+ expect(response.success?).to eq(true)
+ expect(response.payload).to include(cleanup_status: :finished, container_repository_id: repository.id)
+ expect(ContainerRepository.waiting_for_cleanup.count).to eq(0)
+ expect(repository.reload.cleanup_unscheduled?).to be_truthy
+ expect(repository.expiration_policy_completed_at).not_to eq(nil)
+ expect(repository.expiration_policy_started_at).not_to eq(nil)
+ end
end
end
- end
-
- context 'without a successful cleanup tags service execution' do
- let(:cleanup_tags_service_response) { { status: :error, message: 'timeout' } }
-
- before do
- expect(Projects::ContainerRepository::CleanupTagsService)
- .to receive(:new).and_return(double(execute: cleanup_tags_service_response))
- end
- it 'partially clean up the repository' do
- response = subject
+ context 'without a successful cleanup tags service execution' do
+ let(:cleanup_tags_service_response) { { status: :error, message: 'timeout' } }
- aggregate_failures "checking the response and container repositories" do
- expect(response.success?).to eq(true)
- expect(response.payload).to include(cleanup_status: :unfinished, container_repository_id: repository.id)
- expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
- expect(repository.reload.cleanup_unfinished?).to be_truthy
- expect(repository.expiration_policy_started_at).not_to eq(nil)
- expect(repository.expiration_policy_completed_at).to eq(nil)
- end
- end
-
- context 'with a truncated cleanup tags service response' do
- let(:cleanup_tags_service_response) do
- {
- status: :error,
- original_size: 1000,
- before_truncate_size: 800,
- after_truncate_size: 200,
- before_delete_size: 100,
- deleted_size: 100
- }
+ before do
+ expect(Projects::ContainerRepository::CleanupTagsService)
+ .to receive(:new).and_return(double(execute: cleanup_tags_service_response))
end
it 'partially clean up the repository' do
@@ -71,49 +47,179 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do
aggregate_failures "checking the response and container repositories" do
expect(response.success?).to eq(true)
- expect(response.payload)
- .to include(
- cleanup_status: :unfinished,
- container_repository_id: repository.id,
- cleanup_tags_service_original_size: 1000,
- cleanup_tags_service_before_truncate_size: 800,
- cleanup_tags_service_after_truncate_size: 200,
- cleanup_tags_service_before_delete_size: 100,
- cleanup_tags_service_deleted_size: 100
- )
+ expect(response.payload).to include(cleanup_status: :unfinished, container_repository_id: repository.id)
expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
expect(repository.reload.cleanup_unfinished?).to be_truthy
expect(repository.expiration_policy_started_at).not_to eq(nil)
expect(repository.expiration_policy_completed_at).to eq(nil)
end
end
+
+ context 'with a truncated cleanup tags service response' do
+ let(:cleanup_tags_service_response) do
+ {
+ status: :error,
+ original_size: 1000,
+ before_truncate_size: 800,
+ after_truncate_size: 200,
+ before_delete_size: 100,
+ deleted_size: 100
+ }
+ end
+
+ it 'partially clean up the repository' do
+ response = subject
+
+ aggregate_failures "checking the response and container repositories" do
+ expect(response.success?).to eq(true)
+ expect(response.payload)
+ .to include(
+ cleanup_status: :unfinished,
+ container_repository_id: repository.id,
+ cleanup_tags_service_original_size: 1000,
+ cleanup_tags_service_before_truncate_size: 800,
+ cleanup_tags_service_after_truncate_size: 200,
+ cleanup_tags_service_before_delete_size: 100,
+ cleanup_tags_service_deleted_size: 100
+ )
+ expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
+ expect(repository.reload.cleanup_unfinished?).to be_truthy
+ expect(repository.expiration_policy_started_at).not_to eq(nil)
+ expect(repository.expiration_policy_completed_at).to eq(nil)
+ end
+ end
+ end
end
- end
- context 'with no repository' do
- let(:service) { described_class.new(nil) }
+ context 'with no repository' do
+ let(:service) { described_class.new(nil) }
+
+ it 'returns an error response' do
+ expect(subject.success?).to eq(false)
+ expect(subject.message).to eq('no repository')
+ end
+ end
- it 'returns an error response' do
- response = subject
+ context 'with an invalid policy' do
+ let(:policy) { repository.project.container_expiration_policy }
- expect(response.success?).to eq(false)
+ before do
+ policy.name_regex = nil
+ policy.enabled = true
+ repository.expiration_policy_cleanup_status = :cleanup_ongoing
+ end
+
+ it 'returns an error response' do
+ expect { subject }.to change { repository.expiration_policy_cleanup_status }.from('cleanup_ongoing').to('cleanup_unscheduled')
+ expect(subject.success?).to eq(false)
+ expect(subject.message).to eq('invalid policy')
+ expect(policy).not_to be_enabled
+ end
+ end
+
+ context 'with a network error' do
+ before do
+ expect(Projects::ContainerRepository::CleanupTagsService)
+ .to receive(:new).and_raise(Faraday::TimeoutError)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Faraday::TimeoutError)
+
+ expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
+ expect(repository.reload.cleanup_unfinished?).to be_truthy
+ expect(repository.expiration_policy_started_at).not_to eq(nil)
+ expect(repository.expiration_policy_completed_at).to eq(nil)
+ end
end
end
- context 'with a network error' do
+ context 'with loopless enabled' do
+ let(:policy) { repository.project.container_expiration_policy }
+
before do
- expect(Projects::ContainerRepository::CleanupTagsService)
- .to receive(:new).and_raise(Faraday::TimeoutError)
+ policy.update!(enabled: true)
+ policy.update_column(:next_run_at, 5.minutes.ago)
end
- it 'raises an error' do
- expect { subject }.to raise_error(Faraday::TimeoutError)
+ it_behaves_like 'cleaning up a container repository'
+
+ context 'next run scheduling' do
+ let_it_be_with_reload(:repository2) { create(:container_repository, project: project) }
+ let_it_be_with_reload(:repository3) { create(:container_repository, project: project) }
+
+ before do
+ cleanup_tags_service = instance_double(Projects::ContainerRepository::CleanupTagsService)
+ allow(Projects::ContainerRepository::CleanupTagsService)
+ .to receive(:new).and_return(cleanup_tags_service)
+ allow(cleanup_tags_service).to receive(:execute).and_return(status: :success)
+ end
+
+ shared_examples 'not scheduling the next run' do
+ it 'does not scheduled the next run' do
+ expect(policy).not_to receive(:schedule_next_run!)
+
+ expect { subject }.not_to change { policy.reload.next_run_at }
+ end
+ end
+
+ shared_examples 'scheduling the next run' do
+ it 'schedules the next run' do
+ expect(policy).to receive(:schedule_next_run!).and_call_original
+
+ expect { subject }.to change { policy.reload.next_run_at }
+ end
+ end
+
+ context 'with cleanups started_at before policy next_run_at' do
+ before do
+ ContainerRepository.update_all(expiration_policy_started_at: 10.minutes.ago)
+ end
+
+ it_behaves_like 'not scheduling the next run'
+ end
+
+ context 'with cleanups started_at around policy next_run_at' do
+ before do
+ repository3.update!(expiration_policy_started_at: policy.next_run_at + 10.minutes.ago)
+ end
- expect(ContainerRepository.waiting_for_cleanup.count).to eq(1)
- expect(repository.reload.cleanup_unfinished?).to be_truthy
- expect(repository.expiration_policy_started_at).not_to eq(nil)
- expect(repository.expiration_policy_completed_at).to eq(nil)
+ it_behaves_like 'not scheduling the next run'
+ end
+
+ context 'with only the current repository started_at before the policy next_run_at' do
+ before do
+ 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
+
+ it_behaves_like 'scheduling the next run'
+ end
+
+ context 'with cleanups started_at after policy next_run_at' do
+ before do
+ ContainerRepository.update_all(expiration_policy_started_at: policy.next_run_at + 10.minutes)
+ end
+
+ it_behaves_like 'scheduling the next run'
+ end
+
+ context 'with a future policy next_run_at' do
+ before do
+ policy.update_column(:next_run_at, 5.minutes.from_now)
+ end
+
+ it_behaves_like 'not scheduling the next run'
+ end
end
end
+
+ context 'with loopless disabled' do
+ before do
+ stub_feature_flags(container_registry_expiration_policies_loopless: false)
+ end
+
+ it_behaves_like 'cleaning up a container repository'
+ end
end
end
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index f0cd42c1948..dca5497de06 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -164,9 +164,9 @@ RSpec.describe Groups::CreateService, '#execute' do
let!(:instance_integration) { create(:prometheus_service, :instance, api_url: 'https://prometheus.instance.com/') }
it 'creates a service from the instance-level integration' do
- expect(created_group.services.count).to eq(1)
- expect(created_group.services.first.api_url).to eq(instance_integration.api_url)
- expect(created_group.services.first.inherit_from_id).to eq(instance_integration.id)
+ expect(created_group.integrations.count).to eq(1)
+ expect(created_group.integrations.first.api_url).to eq(instance_integration.api_url)
+ expect(created_group.integrations.first.inherit_from_id).to eq(instance_integration.id)
end
context 'with an active group-level integration' do
@@ -179,9 +179,9 @@ RSpec.describe Groups::CreateService, '#execute' do
end
it 'creates a service from the group-level integration' do
- expect(created_group.services.count).to eq(1)
- expect(created_group.services.first.api_url).to eq(group_integration.api_url)
- expect(created_group.services.first.inherit_from_id).to eq(group_integration.id)
+ expect(created_group.integrations.count).to eq(1)
+ expect(created_group.integrations.first.api_url).to eq(group_integration.api_url)
+ expect(created_group.integrations.first.inherit_from_id).to eq(group_integration.id)
end
context 'with an active subgroup' do
@@ -194,9 +194,9 @@ RSpec.describe Groups::CreateService, '#execute' do
end
it 'creates a service from the subgroup-level integration' do
- expect(created_group.services.count).to eq(1)
- expect(created_group.services.first.api_url).to eq(subgroup_integration.api_url)
- expect(created_group.services.first.inherit_from_id).to eq(subgroup_integration.id)
+ expect(created_group.integrations.count).to eq(1)
+ expect(created_group.integrations.first.api_url).to eq(subgroup_integration.api_url)
+ expect(created_group.integrations.first.inherit_from_id).to eq(subgroup_integration.id)
end
end
end
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index 21f59fb5994..2fbd5eeef5f 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -274,7 +274,7 @@ RSpec.describe Groups::TransferService do
end
context 'with a group integration' do
- let(:new_created_integration) { Service.find_by(group: group) }
+ let(:new_created_integration) { Integration.find_by(group: group) }
context 'with an inherited integration' do
let_it_be(:instance_integration) { create(:slack_service, :instance, webhook: 'http://project.slack.com') }
@@ -283,7 +283,7 @@ RSpec.describe Groups::TransferService do
it 'replaces inherited integrations', :aggregate_failures do
expect(new_created_integration.webhook).to eq(new_parent_group_integration.webhook)
expect(PropagateIntegrationWorker).to have_received(:perform_async).with(new_created_integration.id)
- expect(Service.count).to eq(3)
+ expect(Integration.count).to eq(3)
end
end
diff --git a/spec/services/issuable/destroy_label_links_service_spec.rb b/spec/services/issuable/destroy_label_links_service_spec.rb
new file mode 100644
index 00000000000..bbc69e266c9
--- /dev/null
+++ b/spec/services/issuable/destroy_label_links_service_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issuable::DestroyLabelLinksService do
+ describe '#execute' do
+ context 'when target is an Issue' do
+ let_it_be(:target) { create(:issue) }
+
+ it_behaves_like 'service deleting label links of an issuable'
+ end
+
+ context 'when target is a MergeRequest' do
+ let_it_be(:target) { create(:merge_request) }
+
+ it_behaves_like 'service deleting label links of an issuable'
+ end
+ end
+end
diff --git a/spec/services/issuable/destroy_service_spec.rb b/spec/services/issuable/destroy_service_spec.rb
index 21063539a4b..c72d48d5b77 100644
--- a/spec/services/issuable/destroy_service_spec.rb
+++ b/spec/services/issuable/destroy_service_spec.rb
@@ -31,6 +31,10 @@ RSpec.describe Issuable::DestroyService do
it_behaves_like 'service deleting todos' do
let(:issuable) { issue }
end
+
+ it_behaves_like 'service deleting label links' do
+ let(:issuable) { issue }
+ end
end
context 'when issuable is a merge request' do
@@ -54,6 +58,10 @@ RSpec.describe Issuable::DestroyService do
it_behaves_like 'service deleting todos' do
let(:issuable) { merge_request }
end
+
+ it_behaves_like 'service deleting label links' do
+ let(:issuable) { merge_request }
+ end
end
end
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 36ae0571ad2..cd659bf5e60 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -564,18 +564,18 @@ RSpec.describe Projects::CreateService, '#execute' do
let!(:template_integration) { create(:prometheus_service, :template, api_url: 'https://prometheus.template.com/') }
it 'creates a service from the template' do
- expect(project.services.count).to eq(1)
- expect(project.services.first.api_url).to eq(template_integration.api_url)
- expect(project.services.first.inherit_from_id).to be_nil
+ expect(project.integrations.count).to eq(1)
+ expect(project.integrations.first.api_url).to eq(template_integration.api_url)
+ expect(project.integrations.first.inherit_from_id).to be_nil
end
context 'with an active instance-level integration' do
let!(:instance_integration) { create(:prometheus_service, :instance, api_url: 'https://prometheus.instance.com/') }
it 'creates a service from the instance-level integration' do
- expect(project.services.count).to eq(1)
- expect(project.services.first.api_url).to eq(instance_integration.api_url)
- expect(project.services.first.inherit_from_id).to eq(instance_integration.id)
+ expect(project.integrations.count).to eq(1)
+ expect(project.integrations.first.api_url).to eq(instance_integration.api_url)
+ expect(project.integrations.first.inherit_from_id).to eq(instance_integration.id)
end
context 'with an active group-level integration' do
@@ -594,9 +594,9 @@ RSpec.describe Projects::CreateService, '#execute' do
end
it 'creates a service from the group-level integration' do
- expect(project.services.count).to eq(1)
- expect(project.services.first.api_url).to eq(group_integration.api_url)
- expect(project.services.first.inherit_from_id).to eq(group_integration.id)
+ expect(project.integrations.count).to eq(1)
+ expect(project.integrations.first.api_url).to eq(group_integration.api_url)
+ expect(project.integrations.first.inherit_from_id).to eq(group_integration.id)
end
context 'with an active subgroup' do
@@ -615,9 +615,9 @@ RSpec.describe Projects::CreateService, '#execute' do
end
it 'creates a service from the subgroup-level integration' do
- expect(project.services.count).to eq(1)
- expect(project.services.first.api_url).to eq(subgroup_integration.api_url)
- expect(project.services.first.inherit_from_id).to eq(subgroup_integration.id)
+ expect(project.integrations.count).to eq(1)
+ expect(project.integrations.first.api_url).to eq(subgroup_integration.api_url)
+ expect(project.integrations.first.inherit_from_id).to eq(subgroup_integration.id)
end
end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 5f41ec1d610..8498b752610 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -130,7 +130,7 @@ RSpec.describe Projects::TransferService do
execute_transfer
expect(project.slack_service.webhook).to eq(group_integration.webhook)
- expect(Service.count).to eq(3)
+ expect(Integration.count).to eq(3)
end
end
diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb
index d3014474b62..1127c817656 100644
--- a/spec/support/helpers/features/invite_members_modal_helper.rb
+++ b/spec/support/helpers/features/invite_members_modal_helper.rb
@@ -13,20 +13,36 @@ module Spec
wait_for_requests
click_button name
-
- fill_in 'YYYY-MM-DD', with: expires_at.try(:strftime, '%Y-%m-%d')
-
- unless role == 'Guest'
- click_button 'Guest'
- wait_for_requests
- click_button role
- end
+ choose_options(role, expires_at)
click_button 'Invite'
+
+ page.refresh
end
+ end
+
+ def invite_group(name, role: 'Guest', expires_at: nil)
+ click_on 'Invite a group'
+
+ click_on 'Select a group'
+ wait_for_requests
+ click_button name
+ choose_options(role, expires_at)
+
+ click_button 'Invite'
page.refresh
end
+
+ def choose_options(role, expires_at)
+ unless role == 'Guest'
+ click_button 'Guest'
+ wait_for_requests
+ click_button role
+ end
+
+ fill_in 'YYYY-MM-DD', with: expires_at.try(:strftime, '%Y-%m-%d')
+ end
end
end
end
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index 13b6b9283c3..576261030a7 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
-Service.available_services_names.each do |service|
+Integration.available_services_names.each do |service|
RSpec.shared_context service do
include JiraServiceHelper if service == 'jira'
let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym }
- let(:service_klass) { Service.service_name_to_model(service) }
+ let(:service_klass) { Integration.service_name_to_model(service) }
let(:service_instance) { service_klass.new }
let(:service_fields) { service_instance.fields }
let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
diff --git a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb
index 0ee24dd93d7..49729afce61 100644
--- a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb
@@ -81,7 +81,7 @@ RSpec.shared_examples 'chat slash commands service' do
end
context 'when the user is authenticated' do
- let!(:chat_name) { create(:chat_name, service: subject) }
+ let!(:chat_name) { create(:chat_name, integration: subject) }
let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } }
subject do
diff --git a/spec/support/shared_examples/services/destroy_label_links_shared_examples.rb b/spec/support/shared_examples/services/destroy_label_links_shared_examples.rb
new file mode 100644
index 00000000000..d2b52468c25
--- /dev/null
+++ b/spec/support/shared_examples/services/destroy_label_links_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'service deleting label links of an issuable' do
+ let_it_be(:label_link) { create(:label_link, target: target) }
+
+ def execute
+ described_class.new(target.id, target.class.name).execute
+ end
+
+ it 'deletes label links for specified target ID and type' do
+ control_count = ActiveRecord::QueryRecorder.new { execute }.count
+
+ # Create more label links for the target
+ create(:label_link, target: target)
+ create(:label_link, target: target)
+
+ expect { execute }.not_to exceed_query_limit(control_count)
+ expect(target.reload.label_links.count).to eq(0)
+ end
+end
diff --git a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
index ccc287c10de..0e9e978fbd9 100644
--- a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb
@@ -29,3 +29,33 @@ shared_examples_for 'service deleting todos' do
end
end
end
+
+shared_examples_for 'service deleting label links' do
+ before do
+ stub_feature_flags(destroy_issuable_label_links_async: group)
+ end
+
+ it 'destroys associated label links asynchronously' do
+ expect(Issuable::LabelLinksDestroyWorker)
+ .to receive(:perform_async)
+ .with(issuable.id, issuable.class.name)
+
+ subject.execute(issuable)
+ end
+
+ context 'when destroy_issuable_label_links_async feature is disabled for group' do
+ before do
+ stub_feature_flags(destroy_issuable_label_links_async: false)
+ end
+
+ it 'destroy associated label links synchronously' do
+ expect_next_instance_of(Issuable::LabelLinksDestroyWorker) do |worker|
+ expect(worker)
+ .to receive(:perform)
+ .with(issuable.id, issuable.class.name)
+ end
+
+ subject.execute(issuable)
+ end
+ end
+end
diff --git a/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb b/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb
index 45f6d05cd4e..04f568515ed 100644
--- a/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb
+++ b/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb
@@ -5,11 +5,11 @@ require 'spec_helper'
RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
using RSpec::Parameterized::TableSyntax
- let_it_be(:repository, refind: true) { create(:container_repository, :cleanup_scheduled) }
- let_it_be(:project) { repository.project }
- let_it_be(:policy) { project.container_expiration_policy }
- let_it_be(:other_repository) { create(:container_repository) }
+ let_it_be(:repository, refind: true) { create(:container_repository, :cleanup_scheduled, expiration_policy_started_at: 1.month.ago) }
+ let_it_be(:other_repository, refind: true) { create(:container_repository, expiration_policy_started_at: 15.days.ago) }
+ let(:project) { repository.project }
+ let(:policy) { project.container_expiration_policy }
let(:worker) { described_class.new }
describe '#perform_work' do
@@ -19,7 +19,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
policy.update_column(:enabled, true)
end
- RSpec.shared_examples 'handling all repository conditions' do
+ shared_examples 'handling all repository conditions' do
it 'sends the repository for cleaning' do
service_response = cleanup_service_response(repository: repository)
expect(ContainerExpirationPolicies::CleanupService)
@@ -72,11 +72,21 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
end
end
+ context 'with an erroneous cleanup' do
+ it 'logs an error' do
+ service_response = ServiceResponse.error(message: 'cleanup in an error')
+ expect(ContainerExpirationPolicies::CleanupService)
+ .to receive(:new).with(repository).and_return(double(execute: service_response))
+ expect_log_extra_metadata(service_response: service_response, cleanup_status: :error)
+
+ subject
+ end
+ end
+
context 'with policy running shortly' do
before do
- repository.project
- .container_expiration_policy
- .update_column(:next_run_at, 1.minute.from_now)
+ repository.cleanup_unfinished! if loopless_enabled?
+ policy.update_column(:next_run_at, 1.minute.from_now)
end
it 'skips the repository' do
@@ -84,24 +94,285 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
expect(worker).to receive(:log_extra_metadata_on_done).with(:container_repository_id, repository.id)
expect(worker).to receive(:log_extra_metadata_on_done).with(:project_id, repository.project.id)
expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_status, :skipped)
-
expect { subject }.to change { ContainerRepository.waiting_for_cleanup.count }.from(1).to(0)
+
expect(repository.reload.cleanup_unscheduled?).to be_truthy
end
end
context 'with disabled policy' do
before do
- repository.project
- .container_expiration_policy
- .disable!
+ policy.disable!
end
it 'skips the repository' do
expect(ContainerExpirationPolicies::CleanupService).not_to receive(:new)
- expect { subject }.to change { ContainerRepository.waiting_for_cleanup.count }.from(1).to(0)
- expect(repository.reload.cleanup_unscheduled?).to be_truthy
+ if loopless_enabled?
+ expect { subject }
+ .to not_change { ContainerRepository.waiting_for_cleanup.count }
+ .and not_change { repository.reload.expiration_policy_cleanup_status }
+ else
+ expect { subject }.to change { ContainerRepository.waiting_for_cleanup.count }.from(1).to(0)
+ expect(repository.reload.cleanup_unscheduled?).to be_truthy
+ end
+ end
+ end
+ end
+
+ context 'with loopless enabled' do
+ before do
+ stub_feature_flags(container_registry_expiration_policies_loopless: true)
+ end
+
+ context 'with repository in cleanup unscheduled state' do
+ before do
+ policy.update_column(:next_run_at, 5.minutes.ago)
+ end
+
+ it_behaves_like 'handling all repository conditions'
+ end
+
+ context 'with repository in cleanup unfinished state' do
+ before do
+ repository.cleanup_unfinished!
+ end
+
+ it_behaves_like 'handling all repository conditions'
+ end
+
+ context 'container repository selection' do
+ where(:repository_cleanup_status, :repository_policy_status, :other_repository_cleanup_status, :other_repository_policy_status, :expected_selected_repository) do
+ :unscheduled | :disabled | :unscheduled | :disabled | :none
+ :unscheduled | :disabled | :unscheduled | :runnable | :other_repository
+ :unscheduled | :disabled | :unscheduled | :not_runnable | :none
+
+ :unscheduled | :disabled | :scheduled | :disabled | :none
+ :unscheduled | :disabled | :scheduled | :runnable | :other_repository
+ :unscheduled | :disabled | :scheduled | :not_runnable | :none
+
+ :unscheduled | :disabled | :unfinished | :disabled | :none
+ :unscheduled | :disabled | :unfinished | :runnable | :other_repository
+ :unscheduled | :disabled | :unfinished | :not_runnable | :other_repository
+
+ :unscheduled | :disabled | :ongoing | :disabled | :none
+ :unscheduled | :disabled | :ongoing | :runnable | :none
+ :unscheduled | :disabled | :ongoing | :not_runnable | :none
+
+ :unscheduled | :runnable | :unscheduled | :disabled | :repository
+ :unscheduled | :runnable | :unscheduled | :runnable | :repository
+ :unscheduled | :runnable | :unscheduled | :not_runnable | :repository
+
+ :unscheduled | :runnable | :scheduled | :disabled | :repository
+ :unscheduled | :runnable | :scheduled | :runnable | :repository
+ :unscheduled | :runnable | :scheduled | :not_runnable | :repository
+
+ :unscheduled | :runnable | :unfinished | :disabled | :repository
+ :unscheduled | :runnable | :unfinished | :runnable | :repository
+ :unscheduled | :runnable | :unfinished | :not_runnable | :repository
+
+ :unscheduled | :runnable | :ongoing | :disabled | :repository
+ :unscheduled | :runnable | :ongoing | :runnable | :repository
+ :unscheduled | :runnable | :ongoing | :not_runnable | :repository
+
+ :scheduled | :disabled | :unscheduled | :disabled | :none
+ :scheduled | :disabled | :unscheduled | :runnable | :other_repository
+ :scheduled | :disabled | :unscheduled | :not_runnable | :none
+
+ :scheduled | :disabled | :scheduled | :disabled | :none
+ :scheduled | :disabled | :scheduled | :runnable | :other_repository
+ :scheduled | :disabled | :scheduled | :not_runnable | :none
+
+ :scheduled | :disabled | :unfinished | :disabled | :none
+ :scheduled | :disabled | :unfinished | :runnable | :other_repository
+ :scheduled | :disabled | :unfinished | :not_runnable | :other_repository
+
+ :scheduled | :disabled | :ongoing | :disabled | :none
+ :scheduled | :disabled | :ongoing | :runnable | :none
+ :scheduled | :disabled | :ongoing | :not_runnable | :none
+
+ :scheduled | :runnable | :unscheduled | :disabled | :repository
+ :scheduled | :runnable | :unscheduled | :runnable | :other_repository
+ :scheduled | :runnable | :unscheduled | :not_runnable | :repository
+
+ :scheduled | :runnable | :scheduled | :disabled | :repository
+ :scheduled | :runnable | :scheduled | :runnable | :repository
+ :scheduled | :runnable | :scheduled | :not_runnable | :repository
+
+ :scheduled | :runnable | :unfinished | :disabled | :repository
+ :scheduled | :runnable | :unfinished | :runnable | :repository
+ :scheduled | :runnable | :unfinished | :not_runnable | :repository
+
+ :scheduled | :runnable | :ongoing | :disabled | :repository
+ :scheduled | :runnable | :ongoing | :runnable | :repository
+ :scheduled | :runnable | :ongoing | :not_runnable | :repository
+
+ :scheduled | :not_runnable | :unscheduled | :disabled | :none
+ :scheduled | :not_runnable | :unscheduled | :runnable | :other_repository
+ :scheduled | :not_runnable | :unscheduled | :not_runnable | :none
+
+ :scheduled | :not_runnable | :scheduled | :disabled | :none
+ :scheduled | :not_runnable | :scheduled | :runnable | :other_repository
+ :scheduled | :not_runnable | :scheduled | :not_runnable | :none
+
+ :scheduled | :not_runnable | :unfinished | :disabled | :none
+ :scheduled | :not_runnable | :unfinished | :runnable | :other_repository
+ :scheduled | :not_runnable | :unfinished | :not_runnable | :other_repository
+
+ :scheduled | :not_runnable | :ongoing | :disabled | :none
+ :scheduled | :not_runnable | :ongoing | :runnable | :none
+ :scheduled | :not_runnable | :ongoing | :not_runnable | :none
+
+ :unfinished | :disabled | :unscheduled | :disabled | :none
+ :unfinished | :disabled | :unscheduled | :runnable | :other_repository
+ :unfinished | :disabled | :unscheduled | :not_runnable | :none
+
+ :unfinished | :disabled | :scheduled | :disabled | :none
+ :unfinished | :disabled | :scheduled | :runnable | :other_repository
+ :unfinished | :disabled | :scheduled | :not_runnable | :none
+
+ :unfinished | :disabled | :unfinished | :disabled | :none
+ :unfinished | :disabled | :unfinished | :runnable | :other_repository
+ :unfinished | :disabled | :unfinished | :not_runnable | :other_repository
+
+ :unfinished | :disabled | :ongoing | :disabled | :none
+ :unfinished | :disabled | :ongoing | :runnable | :none
+ :unfinished | :disabled | :ongoing | :not_runnable | :none
+
+ :unfinished | :runnable | :unscheduled | :disabled | :repository
+ :unfinished | :runnable | :unscheduled | :runnable | :other_repository
+ :unfinished | :runnable | :unscheduled | :not_runnable | :repository
+
+ :unfinished | :runnable | :scheduled | :disabled | :repository
+ :unfinished | :runnable | :scheduled | :runnable | :other_repository
+ :unfinished | :runnable | :scheduled | :not_runnable | :repository
+
+ :unfinished | :runnable | :unfinished | :disabled | :repository
+ :unfinished | :runnable | :unfinished | :runnable | :repository
+ :unfinished | :runnable | :unfinished | :not_runnable | :repository
+
+ :unfinished | :runnable | :ongoing | :disabled | :repository
+ :unfinished | :runnable | :ongoing | :runnable | :repository
+ :unfinished | :runnable | :ongoing | :not_runnable | :repository
+
+ :unfinished | :not_runnable | :unscheduled | :disabled | :repository
+ :unfinished | :not_runnable | :unscheduled | :runnable | :other_repository
+ :unfinished | :not_runnable | :unscheduled | :not_runnable | :repository
+
+ :unfinished | :not_runnable | :scheduled | :disabled | :repository
+ :unfinished | :not_runnable | :scheduled | :runnable | :other_repository
+ :unfinished | :not_runnable | :scheduled | :not_runnable | :repository
+
+ :unfinished | :not_runnable | :unfinished | :disabled | :repository
+ :unfinished | :not_runnable | :unfinished | :runnable | :repository
+ :unfinished | :not_runnable | :unfinished | :not_runnable | :repository
+
+ :unfinished | :not_runnable | :ongoing | :disabled | :repository
+ :unfinished | :not_runnable | :ongoing | :runnable | :repository
+ :unfinished | :not_runnable | :ongoing | :not_runnable | :repository
+
+ :ongoing | :disabled | :unscheduled | :disabled | :none
+ :ongoing | :disabled | :unscheduled | :runnable | :other_repository
+ :ongoing | :disabled | :unscheduled | :not_runnable | :none
+
+ :ongoing | :disabled | :scheduled | :disabled | :none
+ :ongoing | :disabled | :scheduled | :runnable | :other_repository
+ :ongoing | :disabled | :scheduled | :not_runnable | :none
+
+ :ongoing | :disabled | :unfinished | :disabled | :none
+ :ongoing | :disabled | :unfinished | :runnable | :other_repository
+ :ongoing | :disabled | :unfinished | :not_runnable | :other_repository
+
+ :ongoing | :disabled | :ongoing | :disabled | :none
+ :ongoing | :disabled | :ongoing | :runnable | :none
+ :ongoing | :disabled | :ongoing | :not_runnable | :none
+
+ :ongoing | :runnable | :unscheduled | :disabled | :none
+ :ongoing | :runnable | :unscheduled | :runnable | :other_repository
+ :ongoing | :runnable | :unscheduled | :not_runnable | :none
+
+ :ongoing | :runnable | :scheduled | :disabled | :none
+ :ongoing | :runnable | :scheduled | :runnable | :other_repository
+ :ongoing | :runnable | :scheduled | :not_runnable | :none
+
+ :ongoing | :runnable | :unfinished | :disabled | :none
+ :ongoing | :runnable | :unfinished | :runnable | :other_repository
+ :ongoing | :runnable | :unfinished | :not_runnable | :other_repository
+
+ :ongoing | :runnable | :ongoing | :disabled | :none
+ :ongoing | :runnable | :ongoing | :runnable | :none
+ :ongoing | :runnable | :ongoing | :not_runnable | :none
+
+ :ongoing | :not_runnable | :unscheduled | :disabled | :none
+ :ongoing | :not_runnable | :unscheduled | :runnable | :other_repository
+ :ongoing | :not_runnable | :unscheduled | :not_runnable | :none
+
+ :ongoing | :not_runnable | :scheduled | :disabled | :none
+ :ongoing | :not_runnable | :scheduled | :runnable | :other_repository
+ :ongoing | :not_runnable | :scheduled | :not_runnable | :none
+
+ :ongoing | :not_runnable | :unfinished | :disabled | :none
+ :ongoing | :not_runnable | :unfinished | :runnable | :other_repository
+ :ongoing | :not_runnable | :unfinished | :not_runnable | :other_repository
+
+ :ongoing | :not_runnable | :ongoing | :disabled | :none
+ :ongoing | :not_runnable | :ongoing | :runnable | :none
+ :ongoing | :not_runnable | :ongoing | :not_runnable | :none
+ end
+
+ with_them do
+ before do
+ update_container_repository(repository, repository_cleanup_status, repository_policy_status)
+ update_container_repository(other_repository, other_repository_cleanup_status, other_repository_policy_status)
+ end
+
+ subject { worker.send(:container_repository) }
+
+ if params[:expected_selected_repository] == :none
+ it 'does not select any repository' do
+ expect(subject).to eq(nil)
+ end
+ else
+ it 'does select a repository' do
+ selected_repository = expected_selected_repository == :repository ? repository : other_repository
+
+ expect(subject).to eq(selected_repository)
+ end
+ end
+
+ def update_container_repository(container_repository, cleanup_status, policy_status)
+ container_repository.update_column(:expiration_policy_cleanup_status, "cleanup_#{cleanup_status}")
+
+ policy = container_repository.project.container_expiration_policy
+
+ case policy_status
+ when :disabled
+ policy.update!(enabled: false)
+ when :runnable
+ policy.update!(enabled: true)
+ policy.update_column(:next_run_at, 5.minutes.ago)
+ when :not_runnable
+ policy.update!(enabled: true)
+ policy.update_column(:next_run_at, 5.minutes.from_now)
+ end
+ end
+ end
+ end
+
+ context 'with another repository in cleanup unfinished state' do
+ let_it_be(:another_repository) { create(:container_repository, :cleanup_unfinished) }
+
+ before do
+ policy.update_column(:next_run_at, 5.minutes.ago)
+ end
+
+ it 'process the cleanup scheduled repository first' do
+ service_response = cleanup_service_response(repository: repository)
+ expect(ContainerExpirationPolicies::CleanupService)
+ .to receive(:new).with(repository).and_return(double(execute: service_response))
+ expect_log_extra_metadata(service_response: service_response)
+
+ subject
end
end
end
@@ -230,17 +501,18 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
end
expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_tags_service_truncated, truncated)
expect(worker).to receive(:log_extra_metadata_on_done).with(:running_jobs_count, 0)
+
+ if service_response.error?
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:cleanup_error_message, service_response.message)
+ end
end
end
describe '#remaining_work_count' do
subject { worker.remaining_work_count }
- context 'with loopless disabled' do
- before do
- stub_feature_flags(container_registry_expiration_policies_loopless: false)
- end
- context 'with container repositoires waiting for cleanup' do
+ shared_examples 'handling all conditions' do
+ context 'with container repositories waiting for cleanup' do
let_it_be(:unfinished_repositories) { create_list(:container_repository, 2, :cleanup_unfinished) }
it { is_expected.to eq(3) }
@@ -259,6 +531,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
context 'with no container repositories waiting for cleanup' do
before do
repository.cleanup_ongoing!
+ policy.update_column(:next_run_at, 5.minutes.from_now)
end
it { is_expected.to eq(0) }
@@ -274,6 +547,32 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
end
end
end
+
+ context 'with loopless enabled' do
+ let_it_be(:disabled_repository) { create(:container_repository, :cleanup_scheduled) }
+
+ let(:capacity) { 10 }
+
+ before do
+ stub_feature_flags(container_registry_expiration_policies_loopless: true)
+ stub_application_setting(container_registry_expiration_policies_worker_capacity: capacity)
+
+ # loopless mode is more accurate that non loopless: policies need to be enabled
+ ContainerExpirationPolicy.update_all(enabled: true)
+ repository.project.container_expiration_policy.update_column(:next_run_at, 5.minutes.ago)
+ disabled_repository.project.container_expiration_policy.update_column(:enabled, false)
+ end
+
+ it_behaves_like 'handling all conditions'
+ end
+
+ context 'with loopless disabled' do
+ before do
+ stub_feature_flags(container_registry_expiration_policies_loopless: false)
+ end
+
+ it_behaves_like 'handling all conditions'
+ end
end
describe '#max_running_jobs' do
@@ -285,20 +584,14 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
stub_application_setting(container_registry_expiration_policies_worker_capacity: capacity)
end
- context 'with loopless disabled' do
+ it { is_expected.to eq(capacity) }
+
+ context 'with feature flag disabled' do
before do
- stub_feature_flags(container_registry_expiration_policies_loopless: false)
+ stub_feature_flags(container_registry_expiration_policies_throttling: false)
end
- it { is_expected.to eq(capacity) }
-
- context 'with feature flag disabled' do
- before do
- stub_feature_flags(container_registry_expiration_policies_throttling: false)
- end
-
- it { is_expected.to eq(0) }
- end
+ it { is_expected.to eq(0) }
end
end
@@ -306,4 +599,8 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
expect(worker.logger)
.to receive(:info).with(worker.structured_payload(structure))
end
+
+ def loopless_enabled?
+ Feature.enabled?(:container_registry_expiration_policies_loopless)
+ end
end
diff --git a/spec/workers/issuable/label_links_destroy_worker_spec.rb b/spec/workers/issuable/label_links_destroy_worker_spec.rb
new file mode 100644
index 00000000000..a838f1c8017
--- /dev/null
+++ b/spec/workers/issuable/label_links_destroy_worker_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issuable::LabelLinksDestroyWorker do
+ let(:job_args) { [1, 'MergeRequest'] }
+ let(:service) { double }
+
+ include_examples 'an idempotent worker' do
+ it 'calls the Issuable::DestroyLabelLinksService' do
+ expect(::Issuable::DestroyLabelLinksService).to receive(:new).twice.and_return(service)
+ expect(service).to receive(:execute).twice
+
+ subject
+ end
+ end
+end
diff --git a/spec/workers/project_service_worker_spec.rb b/spec/workers/project_service_worker_spec.rb
index c638b7472ff..237f501e0ec 100644
--- a/spec/workers/project_service_worker_spec.rb
+++ b/spec/workers/project_service_worker_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe ProjectServiceWorker, '#perform' do
let(:service) { JiraService.new }
before do
- allow(Service).to receive(:find).and_return(service)
+ allow(Integration).to receive(:find).and_return(service)
end
it 'executes service with given data' do
diff --git a/spec/workers/projects/post_creation_worker_spec.rb b/spec/workers/projects/post_creation_worker_spec.rb
index b15b7b76b56..c2f42f03299 100644
--- a/spec/workers/projects/post_creation_worker_spec.rb
+++ b/spec/workers/projects/post_creation_worker_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Projects::PostCreationWorker do
let(:job_args) { [nil] }
it 'does not create prometheus service' do
- expect { subject }.not_to change { Service.count }
+ expect { subject }.not_to change { Integration.count }
end
end