diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-12 15:10:24 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-12 15:10:24 +0300 |
commit | 71a67d17b02e7b8dec2f4c257f6734dc7818fb1e (patch) | |
tree | 6b45633257869a67fd534bd2cf899b93a48794c0 /spec | |
parent | 133ec9237af290062aae70e6f115db69b51c88de (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
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 |