diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-15 12:08:57 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-15 12:08:57 +0300 |
commit | 45a8c43afe8a17de19a92708b380b29b6ae04ce6 (patch) | |
tree | 4104e6ac741fbbdeefe9b8b699650a06c14e9056 /spec | |
parent | 6bc327a3491069240bd73cc83e17b3078c4148b0 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
85 files changed, 1788 insertions, 587 deletions
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index f4046cb97a0..87030448b30 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -260,6 +260,21 @@ RSpec.describe Groups::MilestonesController do expect(response).to redirect_to(group_milestone_path(group, milestone.iid)) expect(milestone.title).to eq("title changed") end + + it "handles ActiveRecord::StaleObjectError" do + milestone_params[:title] = "title changed" + # Purposely reduce the lock_version to trigger an ActiveRecord::StaleObjectError + milestone_params[:lock_version] = milestone.lock_version - 1 + + put :update, params: { + id: milestone.iid, + group_id: group.to_param, + milestone: milestone_params + } + + expect(response).not_to redirect_to(group_milestone_path(group, milestone.iid)) + expect(response).to render_template(:edit) + end end describe "#destroy" do diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 28da7eff8fc..e2b73e55145 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -156,6 +156,27 @@ RSpec.describe Projects::MilestonesController do end end + describe "#update" do + let(:milestone_params) do + { title: "title changed" } + end + + it "handles ActiveRecord::StaleObjectError" do + # Purposely reduce the lock_version to trigger an ActiveRecord::StaleObjectError + milestone_params[:lock_version] = milestone.lock_version - 1 + + put :update, params: { + id: milestone.iid, + milestone: milestone_params, + namespace_id: project.namespace.id, + project_id: project.id + } + + expect(response).not_to redirect_to(project_milestone_path(project, milestone.iid)) + expect(response).to render_template(:edit) + end + end + describe "#destroy" do it "removes milestone" do expect(issue.milestone_id).to eq(milestone.id) diff --git a/spec/factories/container_registry/data_repair_detail.rb b/spec/factories/container_registry/data_repair_detail.rb new file mode 100644 index 00000000000..79467c464db --- /dev/null +++ b/spec/factories/container_registry/data_repair_detail.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :container_registry_data_repair_detail, class: 'ContainerRegistry::DataRepairDetail' do + project + updated_at { 1.hour.ago } + + trait :ongoing do + status { :ongoing } + end + + trait :completed do + status { :completed } + end + + trait :failed do + status { :failed } + end + end +end diff --git a/spec/features/groups/milestones/milestone_editing_spec.rb b/spec/features/groups/milestones/milestone_editing_spec.rb new file mode 100644 index 00000000000..b3c7cfe88af --- /dev/null +++ b/spec/features/groups/milestones/milestone_editing_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Milestone editing", feature_category: :team_planning do + let_it_be(:group) { create(:group, owner: user) } + let_it_be(:user) { create(:group_member, :maintainer, user: create(:user), group: group).user } + + let(:milestone) { create(:milestone, group: group, title: "12345676543") } + + before do + sign_in(user) + + visit(edit_group_milestone_path(group, milestone)) + end + + it_behaves_like 'milestone handling version conflicts' +end diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index c1cf8fada26..bc20660d2a0 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -54,7 +54,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin first('.js-user-search').click click_link 'Unassigned' - click_button 'Save changes' + click_button _('Save changes') page.within('.assignee') do expect(page).to have_content 'None - assign yourself' @@ -79,7 +79,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin expect(find('#issuable-due-date').value).to eq date.to_s - click_button 'Save changes' + click_button _('Save changes') page.within '.issuable-sidebar' do expect(page).to have_content date.to_s(:medium) @@ -92,9 +92,15 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin fill_in 'issue_title', with: 'bug 345' fill_in 'issue_description', with: 'bug description' - click_button 'Save changes' + click_button _('Save changes') - expect(page).to have_content 'Someone edited the issue the same time you did' + expect(page).to have_content( + format( + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + model_name: _('issue'), + link_to_model: _('issue') + ) + ) end end end diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb index 114fa5b6ecb..76588832ee1 100644 --- a/spec/features/merge_request/user_edits_mr_spec.rb +++ b/spec/features/merge_request/user_edits_mr_spec.rb @@ -90,9 +90,15 @@ RSpec.describe 'Merge request > User edits MR', feature_category: :code_review_w fill_in 'merge_request_title', with: 'bug 345' fill_in 'merge_request_description', with: 'bug description' - click_button 'Save changes' - - expect(page).to have_content 'Someone edited the merge request the same time you did' + click_button _('Save changes') + + expect(page).to have_content( + format( + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + model_name: _('merge request'), + link_to_model: _('merge request') + ) + ) end it 'preserves description textarea height', :js do diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 74e5dc9f8c3..cd1dde55e30 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -580,7 +580,11 @@ RSpec.describe 'File blob', :js, feature_category: :projects do end describe '.gitlab/dashboards/custom-dashboard.yml' do + let(:remove_monitor_metrics) { false } + before do + stub_feature_flags(remove_monitor_metrics: remove_monitor_metrics) + project.add_maintainer(project.creator) Files::CreateService.new( @@ -608,6 +612,15 @@ RSpec.describe 'File blob', :js, feature_category: :projects do expect(page).to have_link('Learn more') end end + + context 'when metrics dashboard feature is unavailable' do + let(:remove_monitor_metrics) { true } + + it 'displays the blob without an auxiliary viewer' do + expect(page).to have_content('Environment metrics') + expect(page).not_to have_content('Metrics Dashboard YAML definition', wait: 0) + end + end end context 'invalid dashboard file' do diff --git a/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb index c2370f38faa..aea76944c7f 100644 --- a/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb +++ b/spec/features/projects/integrations/user_activates_mattermost_slash_command_spec.rb @@ -99,7 +99,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js, feature_category: :integ click_link 'Add to Mattermost' - expect(find('input[type="submit"]')['disabled']).not_to eq("true") + expect(find('button[type="submit"]')['disabled']).not_to eq("true") end it 'disables the submit button if the required fields are not provided', :js do @@ -109,7 +109,7 @@ RSpec.describe 'Set up Mattermost slash commands', :js, feature_category: :integ fill_in('mattermost_trigger', with: '') - expect(find('input[type="submit"]')['disabled']).to eq("true") + expect(find('button[type="submit"]')['disabled']).to eq("true") end def stub_teams(count: 0) diff --git a/spec/features/projects/milestones/milestone_editing_spec.rb b/spec/features/projects/milestones/milestone_editing_spec.rb new file mode 100644 index 00000000000..8a03683eb35 --- /dev/null +++ b/spec/features/projects/milestones/milestone_editing_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Milestone editing", :js, feature_category: :team_planning do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, name: 'test', namespace: user.namespace) } + + let(:milestone) { create(:milestone, project: project, start_date: Date.today, due_date: 5.days.from_now) } + + before do + sign_in(user) + + visit(edit_project_milestone_path(project, milestone)) + end + + it_behaves_like 'milestone handling version conflicts' +end diff --git a/spec/finders/template_finder_spec.rb b/spec/finders/template_finder_spec.rb index 21fea7863ff..c466f533a61 100644 --- a/spec/finders/template_finder_spec.rb +++ b/spec/finders/template_finder_spec.rb @@ -103,6 +103,10 @@ RSpec.describe TemplateFinder do describe '#build' do let(:project) { build_stubbed(:project) } + before do + stub_feature_flags(remove_monitor_metrics: false) + end + where(:type, :expected_class) do :dockerfiles | described_class :gitignores | described_class @@ -119,6 +123,16 @@ RSpec.describe TemplateFinder do it { is_expected.to be_a(expected_class) } it { expect(finder.project).to eq(project) } end + + context 'when metrics dashboard is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + subject(:finder) { described_class.build(:metrics_dashboard_ymls, project) } + + it { is_expected.to be_nil } + end end describe '#execute' do diff --git a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item_spec.js b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item_spec.js index c7c40c3a4b9..f99d7277612 100644 --- a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item_spec.js @@ -8,8 +8,8 @@ describe('Image item', () => { const findImageNameInput = () => wrapper.findByTestId('image-name-input'); const findImageEntrypointInput = () => wrapper.findByTestId('image-entrypoint-input'); - const dummyImageName = 'dummyImageName'; - const dummyImageEntrypoint = 'dummyImageEntrypoint'; + const dummyImageName = 'a'; + const dummyImageEntrypoint = ['b', 'c']; const createComponent = ({ job = JSON.parse(JSON.stringify(JOB_TEMPLATE)) } = {}) => { wrapper = shallowMountExtended(ImageItem, { @@ -31,9 +31,9 @@ describe('Image item', () => { expect(wrapper.emitted('update-job')).toHaveLength(1); expect(wrapper.emitted('update-job')[0]).toEqual(['image.name', dummyImageName]); - findImageEntrypointInput().vm.$emit('input', dummyImageEntrypoint); + findImageEntrypointInput().vm.$emit('input', dummyImageEntrypoint.join('\n')); expect(wrapper.emitted('update-job')).toHaveLength(2); - expect(wrapper.emitted('update-job')[1]).toEqual(['image.entrypoint', [dummyImageEntrypoint]]); + expect(wrapper.emitted('update-job')[1]).toEqual(['image.entrypoint', dummyImageEntrypoint]); }); }); diff --git a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/services_item_spec.js b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/services_item_spec.js index 07b3526f5fa..284d639c77f 100644 --- a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/services_item_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/services_item_spec.js @@ -34,7 +34,7 @@ describe('Services item', () => { expect(wrapper.emitted('update-job')).toHaveLength(1); expect(wrapper.emitted('update-job')[0]).toEqual(['services[0].name', dummyServiceName]); - findServiceEntrypointInputByIndex(0).vm.$emit('input', dummyServiceEntrypoint.join(',')); + findServiceEntrypointInputByIndex(0).vm.$emit('input', dummyServiceEntrypoint.join('\n')); expect(wrapper.emitted('update-job')).toHaveLength(2); expect(wrapper.emitted('update-job')[1]).toEqual([ diff --git a/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb b/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb index a83cef40bdf..2ca194d519c 100644 --- a/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb +++ b/spec/graphql/resolvers/metrics/dashboards/annotation_resolver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Resolvers::Metrics::Dashboards::AnnotationResolver do +RSpec.describe Resolvers::Metrics::Dashboards::AnnotationResolver, feature_category: :metrics do include GraphqlHelpers describe '#resolve' do @@ -25,6 +25,10 @@ RSpec.describe Resolvers::Metrics::Dashboards::AnnotationResolver do environment.project.add_developer(current_user) end + before do + stub_feature_flags(remove_monitor_metrics: false) + end + context 'with annotation records' do let_it_be(:annotation_1) { create(:metrics_dashboard_annotation, environment: environment, starting_at: 9.minutes.ago, dashboard_path: path) } @@ -55,6 +59,16 @@ RSpec.describe Resolvers::Metrics::Dashboards::AnnotationResolver do expect(resolve_annotations).to be_empty end end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns nothing' do + expect(resolve_annotations).to be_nil + end + end end end end diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb index 58c5808cbcc..8f43a4a44a0 100644 --- a/spec/graphql/types/permission_types/issue_spec.rb +++ b/spec/graphql/types/permission_types/issue_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Types::PermissionTypes::Issue do expected_permissions = [ :read_issue, :admin_issue, :update_issue, :reopen_issue, :read_design, :create_design, :destroy_design, - :create_note + :create_note, :update_design ] expected_permissions.each do |permission| diff --git a/spec/graphql/types/project_statistics_redirect_type_spec.rb b/spec/graphql/types/project_statistics_redirect_type_spec.rb new file mode 100644 index 00000000000..3600a5b932f --- /dev/null +++ b/spec/graphql/types/project_statistics_redirect_type_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ProjectStatisticsRedirect'], feature_category: :consumables_cost_management do + it 'has all the required fields' do + expect(described_class).to have_graphql_fields(:repository, :build_artifacts, :packages, + :wiki, :snippets, :container_registry) + end +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 0f980241db3..bcfdb05ca90 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -24,7 +24,7 @@ RSpec.describe GitlabSchema.types['Project'] do snippets_enabled jobs_enabled public_jobs open_issues_count import_status only_allow_merge_if_pipeline_succeeds request_access_enabled only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled - namespace group statistics repository merge_requests merge_request issues + namespace group statistics statistics_details_paths repository merge_requests merge_request issues issue milestones pipelines removeSourceBranchAfterMerge pipeline_counts sentryDetailedError snippets grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments environment boards jira_import_status jira_imports services releases release diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index f7cbe8c816b..3eb1090c9dc 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -777,6 +777,34 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do end end + describe '#show_mobile_devops_project_promo?' do + using RSpec::Parameterized::TableSyntax + + where(:hide_cookie, :feature_flag_enabled, :mobile_target_platform, :result) do + false | true | true | true + false | false | true | false + false | false | false | false + false | true | false | false + true | false | false | false + true | true | false | false + true | true | true | false + true | false | true | false + end + + with_them do + before do + allow(Gitlab).to receive(:com?) { gitlab_com } + Feature.enable(:mobile_devops_projects_promo, feature_flag_enabled) + project.project_setting.target_platforms << 'ios' if mobile_target_platform + helper.request.cookies["hide_mobile_devops_promo_#{project.id}"] = true if hide_cookie + end + + it 'resolves if the user can import members' do + expect(helper.show_mobile_devops_project_promo?(project)).to eq result + end + end + end + describe '#can_admin_project_member?' do context 'when user is project owner' do before do diff --git a/spec/lib/api/entities/package_spec.rb b/spec/lib/api/entities/package_spec.rb index 53d9a0b4557..9288f6fe8eb 100644 --- a/spec/lib/api/entities/package_spec.rb +++ b/spec/lib/api/entities/package_spec.rb @@ -40,13 +40,4 @@ RSpec.describe API::Entities::Package do expect(subject[:_links]).not_to have_key(:web_path) end end - - context 'with build info' do - let_it_be(:project) { create(:project) } - let_it_be(:package) { create(:npm_package, :with_build, project: project) } - - it 'returns an empty array for pipelines' do - expect(subject[:pipelines]).to eq([]) - end - end end diff --git a/spec/lib/container_registry/gitlab_api_client_spec.rb b/spec/lib/container_registry/gitlab_api_client_spec.rb index ac15048e4b5..c70dd265073 100644 --- a/spec/lib/container_registry/gitlab_api_client_spec.rb +++ b/spec/lib/container_registry/gitlab_api_client_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ContainerRegistry::GitlabApiClient do +RSpec.describe ContainerRegistry::GitlabApiClient, feature_category: :container_registry do using RSpec::Parameterized::TableSyntax include_context 'container registry client' @@ -615,6 +615,159 @@ RSpec.describe ContainerRegistry::GitlabApiClient do end end + describe '#each_sub_repositories_with_tag_page' do + let(:page_size) { 100 } + let(:project_path) { 'repo/project' } + + shared_examples 'iterating through a page' do |expected_tags: true| + it 'iterates through one page' do + expect_next_instance_of(described_class) do |client| + expect(client).to receive(:sub_repositories_with_tag).with(project_path, page_size: page_size, last: nil).and_return(client_response) + end + + expect { |b| described_class.each_sub_repositories_with_tag_page(path: project_path, page_size: page_size, &b) } + .to yield_with_args(expected_tags ? client_response_repositories : []) + end + end + + context 'when no block is given' do + it 'raises an Argument error' do + expect do + described_class.each_sub_repositories_with_tag_page(path: project_path, page_size: page_size) + end.to raise_error(ArgumentError, 'block not given') + end + end + + context 'when a block is given' do + before do + expect(Auth::ContainerRegistryAuthenticationService).to receive(:pull_nested_repositories_access_token).with(project_path).and_return(token) + stub_container_registry_config(enabled: true, api_url: registry_api_url, key: 'spec/fixtures/x509_certificate_pk.key') + end + + context 'with an empty page' do + let(:client_response) { { pagination: {}, response_body: [] } } + + it_behaves_like 'iterating through a page', expected_tags: false + end + + context 'with one page' do + let(:client_response) { { pagination: {}, response_body: client_response_repositories } } + let(:client_response_repositories) do + [ + { + "name": "docker-alpine", + "path": "gitlab-org/build/cng/docker-alpine", + "created_at": "2022-06-07T12:11:13.633+00:00", + "updated_at": "2022-06-07T14:37:49.251+00:00" + }, + { + "name": "git-base", + "path": "gitlab-org/build/cng/git-base", + "created_at": "2022-06-07T12:11:13.633+00:00", + "updated_at": "2022-06-07T14:37:49.251+00:00" + } + ] + end + + it_behaves_like 'iterating through a page' + end + + context 'with two pages' do + let(:client_response1) { { pagination: { next: { uri: URI('http://localhost/next?last=latest') } }, response_body: client_response_repositories1 } } + let(:client_response_repositories1) do + [ + { + "name": "docker-alpine", + "path": "gitlab-org/build/cng/docker-alpine", + "created_at": "2022-06-07T12:11:13.633+00:00", + "updated_at": "2022-06-07T14:37:49.251+00:00" + }, + { + "name": "git-base", + "path": "gitlab-org/build/cng/git-base", + "created_at": "2022-06-07T12:11:13.633+00:00", + "updated_at": "2022-06-07T14:37:49.251+00:00" + } + ] + end + + let(:client_response2) { { pagination: {}, response_body: client_response_repositories2 } } + let(:client_response_repositories2) do + [ + { + "name": "docker-alpine1", + "path": "gitlab-org/build/cng/docker-alpine", + "created_at": "2022-06-07T12:11:13.633+00:00", + "updated_at": "2022-06-07T14:37:49.251+00:00" + }, + { + "name": "git-base1", + "path": "gitlab-org/build/cng/git-base", + "created_at": "2022-06-07T12:11:13.633+00:00", + "updated_at": "2022-06-07T14:37:49.251+00:00" + } + ] + end + + it 'iterates through two pages' do + expect_next_instance_of(described_class) do |client| + expect(client).to receive(:sub_repositories_with_tag).with(project_path, page_size: page_size, last: nil).and_return(client_response1) + expect(client).to receive(:sub_repositories_with_tag).with(project_path, page_size: page_size, last: 'latest').and_return(client_response2) + end + + expect { |b| described_class.each_sub_repositories_with_tag_page(path: project_path, page_size: page_size, &b) } + .to yield_successive_args(client_response_repositories1, client_response_repositories2) + end + end + + context 'when max pages is reached' do + let(:client_response) { { pagination: {}, response_body: [] } } + + before do + stub_const('ContainerRegistry::GitlabApiClient::MAX_REPOSITORIES_PAGE_SIZE', 0) + expect_next_instance_of(described_class) do |client| + expect(client).to receive(:sub_repositories_with_tag).with(project_path, page_size: page_size, last: nil).and_return(client_response) + end + end + + it 'raises an error' do + expect { described_class.each_sub_repositories_with_tag_page(path: project_path, page_size: page_size) {} } # rubocop:disable Lint/EmptyBlock + .to raise_error(StandardError, 'too many pages requested') + end + end + + context 'without a page size set' do + let(:client_response) { { pagination: {}, response_body: [] } } + + it 'uses a default size' do + expect_next_instance_of(described_class) do |client| + expect(client).to receive(:sub_repositories_with_tag).with(project_path, page_size: page_size, last: nil).and_return(client_response) + end + + expect { |b| described_class.each_sub_repositories_with_tag_page(path: project_path, &b) }.to yield_with_args([]) + end + end + + context 'with an empty client response' do + let(:client_response) { {} } + + it 'breaks the loop' do + expect_next_instance_of(described_class) do |client| + expect(client).to receive(:sub_repositories_with_tag).with(project_path, page_size: page_size, last: nil).and_return(client_response) + end + + expect { |b| described_class.each_sub_repositories_with_tag_page(path: project_path, page_size: page_size, &b) }.not_to yield_control + end + end + + context 'with a nil page' do + let(:client_response) { { pagination: {}, response_body: nil } } + + it_behaves_like 'iterating through a page', expected_tags: false + end + end + end + def stub_pre_import(path, status_code, pre:) import_type = pre ? 'pre' : 'final' stub_request(:put, "#{registry_api_url}/gitlab/v1/import/#{path}/?import_type=#{import_type}") diff --git a/spec/lib/gitlab/background_migration/backfill_cluster_agents_has_vulnerabilities_spec.rb b/spec/lib/gitlab/background_migration/backfill_cluster_agents_has_vulnerabilities_spec.rb index 3aab0cdf54b..edb6ff59340 100644 --- a/spec/lib/gitlab/background_migration/backfill_cluster_agents_has_vulnerabilities_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_cluster_agents_has_vulnerabilities_spec.rb @@ -4,10 +4,12 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::BackfillClusterAgentsHasVulnerabilities, :migration do # rubocop:disable Layout/LineLength let(:migration) do - described_class.new(start_id: 1, end_id: 10, - batch_table: table_name, batch_column: batch_column, - sub_batch_size: sub_batch_size, pause_ms: pause_ms, - connection: ApplicationRecord.connection) + described_class.new( + start_id: 1, end_id: 10, + batch_table: table_name, batch_column: batch_column, + sub_batch_size: sub_batch_size, pause_ms: pause_ms, + connection: ApplicationRecord.connection + ) end let(:users_table) { table(:users) } diff --git a/spec/lib/gitlab/background_migration/backfill_environment_tiers_spec.rb b/spec/lib/gitlab/background_migration/backfill_environment_tiers_spec.rb index 788ed40b61e..9026c327e3c 100644 --- a/spec/lib/gitlab/background_migration/backfill_environment_tiers_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_environment_tiers_spec.rb @@ -8,10 +8,12 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillEnvironmentTiers, let!(:project) { table(:projects).create!(namespace_id: namespace.id, project_namespace_id: namespace.id) } let(:migration) do - described_class.new(start_id: 1, end_id: 1000, - batch_table: :environments, batch_column: :id, - sub_batch_size: 10, pause_ms: 0, - connection: ApplicationRecord.connection) + described_class.new( + start_id: 1, end_id: 1000, + batch_table: :environments, batch_column: :id, + sub_batch_size: 10, pause_ms: 0, + connection: ApplicationRecord.connection + ) end describe '#perform' do diff --git a/spec/lib/gitlab/background_migration/backfill_group_features_spec.rb b/spec/lib/gitlab/background_migration/backfill_group_features_spec.rb index e0be5a785b8..023d4b04e63 100644 --- a/spec/lib/gitlab/background_migration/backfill_group_features_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_group_features_spec.rb @@ -7,14 +7,16 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillGroupFeatures, :migration, s let(:namespaces) { table(:namespaces) } subject do - described_class.new(start_id: 1, - end_id: 4, - batch_table: :namespaces, - batch_column: :id, - sub_batch_size: 10, - pause_ms: 0, - job_arguments: [4], - connection: ActiveRecord::Base.connection) + described_class.new( + start_id: 1, + end_id: 4, + batch_table: :namespaces, + batch_column: :id, + sub_batch_size: 10, + pause_ms: 0, + job_arguments: [4], + connection: ActiveRecord::Base.connection + ) end describe '#perform' do diff --git a/spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb b/spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb index 479afb56210..b3f04055e0a 100644 --- a/spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_imported_issue_search_data_spec.rb @@ -30,13 +30,15 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillImportedIssueSearchData, end let(:migration) do - described_class.new(start_id: issue.id, - end_id: issue.id + 30, - batch_table: :issues, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ApplicationRecord.connection) + described_class.new( + start_id: issue.id, + end_id: issue.id + 30, + batch_table: :issues, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ApplicationRecord.connection + ) end let(:perform_migration) { migration.perform } diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_details_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_details_spec.rb index b6282de0da6..39ad60fb13b 100644 --- a/spec/lib/gitlab/background_migration/backfill_namespace_details_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_namespace_details_spec.rb @@ -7,27 +7,36 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceDetails, :migration let(:namespace_details) { table(:namespace_details) } subject(:perform_migration) do - described_class.new(start_id: namespaces.minimum(:id), - end_id: namespaces.maximum(:id), - batch_table: :namespaces, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: namespaces.minimum(:id), + end_id: namespaces.maximum(:id), + batch_table: :namespaces, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end describe '#perform' do it 'creates details for all namespaces in range' do - namespace1 = namespaces.create!(id: 5, name: 'test1', path: 'test1', description: "Some description1", - description_html: "Some description html1", cached_markdown_version: 4) - namespaces.create!(id: 6, name: 'test2', path: 'test2', type: 'Project', - description: "Some description2", description_html: "Some description html2", - cached_markdown_version: 4) - namespace3 = namespaces.create!(id: 7, name: 'test3', path: 'test3', description: "Some description3", - description_html: "Some description html3", cached_markdown_version: 4) - namespace4 = namespaces.create!(id: 8, name: 'test4', path: 'test4', description: "Some description3", - description_html: "Some description html4", cached_markdown_version: 4) + namespace1 = namespaces.create!( + id: 5, name: 'test1', path: 'test1', description: "Some description1", + description_html: "Some description html1", cached_markdown_version: 4 + ) + namespaces.create!( + id: 6, name: 'test2', path: 'test2', type: 'Project', + description: "Some description2", description_html: "Some description html2", + cached_markdown_version: 4 + ) + namespace3 = namespaces.create!( + id: 7, name: 'test3', path: 'test3', description: "Some description3", + description_html: "Some description html3", cached_markdown_version: 4 + ) + namespace4 = namespaces.create!( + id: 8, name: 'test4', path: 'test4', description: "Some description3", + description_html: "Some description html4", cached_markdown_version: 4 + ) namespace_details.delete_all expect(namespace_details.pluck(:namespace_id)).to eql [] diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route_spec.rb index b821efcadb0..3a8a327550b 100644 --- a/spec/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_namespace_id_for_namespace_route_spec.rb @@ -22,18 +22,29 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceIdForNamespaceRoute subject(:perform_migration) { migration.perform(1, 10, table_name, batch_column, sub_batch_size, pause_ms) } before do - routes_table.create!(id: 1, name: 'test1', path: 'test1', source_id: namespace1.id, - source_type: namespace1.class.sti_name) - routes_table.create!(id: 2, name: 'test2', path: 'test2', source_id: namespace2.id, - source_type: namespace2.class.sti_name) - routes_table.create!(id: 5, name: 'test3', path: 'test3', source_id: project1.id, - source_type: project1.class.sti_name) # should be ignored - project route - routes_table.create!(id: 6, name: 'test4', path: 'test4', source_id: non_existing_record_id, - source_type: namespace3.class.sti_name) # should be ignored - invalid source_id - routes_table.create!(id: 10, name: 'test5', path: 'test5', source_id: namespace3.id, - source_type: namespace3.class.sti_name) - routes_table.create!(id: 11, name: 'test6', path: 'test6', source_id: namespace4.id, - source_type: namespace4.class.sti_name) # should be ignored - outside the scope + routes_table.create!( + id: 1, name: 'test1', path: 'test1', source_id: namespace1.id, source_type: namespace1.class.sti_name + ) + + routes_table.create!( + id: 2, name: 'test2', path: 'test2', source_id: namespace2.id, source_type: namespace2.class.sti_name + ) + + routes_table.create!( + id: 5, name: 'test3', path: 'test3', source_id: project1.id, source_type: project1.class.sti_name + ) # should be ignored - project route + + routes_table.create!( + id: 6, name: 'test4', path: 'test4', source_id: non_existing_record_id, source_type: namespace3.class.sti_name + ) # should be ignored - invalid source_id + + routes_table.create!( + id: 10, name: 'test5', path: 'test5', source_id: namespace3.id, source_type: namespace3.class.sti_name + ) + + routes_table.create!( + id: 11, name: 'test6', path: 'test6', source_id: namespace4.id, source_type: namespace4.class.sti_name + ) # should be ignored - outside the scope end it 'backfills `type` for the selected records', :aggregate_failures do diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_id_of_vulnerability_reads_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_id_of_vulnerability_reads_spec.rb index 564aa3b8c01..6a55c6951d5 100644 --- a/spec/lib/gitlab/background_migration/backfill_namespace_id_of_vulnerability_reads_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_namespace_id_of_vulnerability_reads_spec.rb @@ -38,14 +38,15 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceIdOfVulnerabilityRe end subject(:perform_migration) do - described_class.new(start_id: vulnerability_read.vulnerability_id, - end_id: vulnerability_read.vulnerability_id, - batch_table: :vulnerability_reads, - batch_column: :vulnerability_id, - sub_batch_size: 1, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: vulnerability_read.vulnerability_id, + end_id: vulnerability_read.vulnerability_id, + batch_table: :vulnerability_reads, + batch_column: :vulnerability_id, + sub_batch_size: 1, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end it 'sets the namespace_id of existing record' do diff --git a/spec/lib/gitlab/background_migration/backfill_project_feature_package_registry_access_level_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_feature_package_registry_access_level_spec.rb index fd6c055b9f6..47ff2883fb2 100644 --- a/spec/lib/gitlab/background_migration/backfill_project_feature_package_registry_access_level_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_project_feature_package_registry_access_level_spec.rb @@ -101,14 +101,15 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillProjectFeaturePackageRegistr end subject(:perform_migration) do - described_class.new(start_id: project1.id, - end_id: project5.id, - batch_table: :projects, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: project1.id, + end_id: project5.id, + batch_table: :projects, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end it 'backfills project_features.package_registry_access_level', :aggregate_failures do diff --git a/spec/lib/gitlab/background_migration/backfill_project_member_namespace_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_member_namespace_id_spec.rb index ca7ca41a33e..96f49624d22 100644 --- a/spec/lib/gitlab/background_migration/backfill_project_member_namespace_id_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_project_member_namespace_id_spec.rb @@ -4,10 +4,12 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::BackfillProjectMemberNamespaceId, :migration, schema: 20220516054011 do let(:migration) do - described_class.new(start_id: 1, end_id: 10, - batch_table: table_name, batch_column: batch_column, - sub_batch_size: sub_batch_size, pause_ms: pause_ms, - connection: ApplicationRecord.connection) + described_class.new( + start_id: 1, end_id: 10, + batch_table: table_name, batch_column: batch_column, + sub_batch_size: sub_batch_size, pause_ms: pause_ms, + connection: ApplicationRecord.connection + ) end let(:members_table) { table(:members) } @@ -35,37 +37,55 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillProjectMemberNamespaceId, :m projects_table.create!(id: 102, name: 'project3', path: 'project3', namespace_id: 202, project_namespace_id: 302) # project1, no member namespace (fill in) - members_table.create!(id: 1, source_id: 100, - source_type: 'Project', type: 'ProjectMember', - member_namespace_id: nil, access_level: 10, notification_level: 3) + members_table.create!( + id: 1, source_id: 100, + source_type: 'Project', type: 'ProjectMember', + member_namespace_id: nil, access_level: 10, notification_level: 3 + ) + # bogus source id, no member namespace id (do nothing) - members_table.create!(id: 2, source_id: non_existing_record_id, - source_type: 'Project', type: 'ProjectMember', - member_namespace_id: nil, access_level: 10, notification_level: 3) + members_table.create!( + id: 2, source_id: non_existing_record_id, + source_type: 'Project', type: 'ProjectMember', + member_namespace_id: nil, access_level: 10, notification_level: 3 + ) + # project3, existing member namespace id (do nothing) - members_table.create!(id: 3, source_id: 102, - source_type: 'Project', type: 'ProjectMember', - member_namespace_id: 300, access_level: 10, notification_level: 3) + members_table.create!( + id: 3, source_id: 102, + source_type: 'Project', type: 'ProjectMember', + member_namespace_id: 300, access_level: 10, notification_level: 3 + ) # Group memberships (do not change) # group1, no member namespace (do nothing) - members_table.create!(id: 4, source_id: 201, - source_type: 'Namespace', type: 'GroupMember', - member_namespace_id: nil, access_level: 10, notification_level: 3) + members_table.create!( + id: 4, source_id: 201, + source_type: 'Namespace', type: 'GroupMember', + member_namespace_id: nil, access_level: 10, notification_level: 3 + ) + # group2, existing member namespace (do nothing) - members_table.create!(id: 5, source_id: 202, - source_type: 'Namespace', type: 'GroupMember', - member_namespace_id: 201, access_level: 10, notification_level: 3) + members_table.create!( + id: 5, source_id: 202, + source_type: 'Namespace', type: 'GroupMember', + member_namespace_id: 201, access_level: 10, notification_level: 3 + ) # Project Namespace memberships (do not change) # project namespace, existing member namespace (do nothing) - members_table.create!(id: 6, source_id: 300, - source_type: 'Namespace', type: 'ProjectNamespaceMember', - member_namespace_id: 201, access_level: 10, notification_level: 3) + members_table.create!( + id: 6, source_id: 300, + source_type: 'Namespace', type: 'ProjectNamespaceMember', + member_namespace_id: 201, access_level: 10, notification_level: 3 + ) + # project namespace, not member namespace (do nothing) - members_table.create!(id: 7, source_id: 301, - source_type: 'Namespace', type: 'ProjectNamespaceMember', - member_namespace_id: 201, access_level: 10, notification_level: 3) + members_table.create!( + id: 7, source_id: 301, + source_type: 'Namespace', type: 'ProjectNamespaceMember', + member_namespace_id: 201, access_level: 10, notification_level: 3 + ) end it 'backfills `member_namespace_id` for the selected records', :aggregate_failures do diff --git a/spec/lib/gitlab/background_migration/backfill_project_namespace_details_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_namespace_details_spec.rb index 01daf16d10c..aac17a426b5 100644 --- a/spec/lib/gitlab/background_migration/backfill_project_namespace_details_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_project_namespace_details_spec.rb @@ -8,32 +8,41 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillProjectNamespaceDetails, :mi let!(:projects) { table(:projects) } subject(:perform_migration) do - described_class.new(start_id: projects.minimum(:id), - end_id: projects.maximum(:id), - batch_table: :projects, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: projects.minimum(:id), + end_id: projects.maximum(:id), + batch_table: :projects, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end describe '#perform' do it 'creates details for all project namespaces in range' do - namespaces.create!(id: 5, name: 'test1', path: 'test1', description: "Some description1", - description_html: "Some description html1", cached_markdown_version: 4) + namespaces.create!( + id: 5, name: 'test1', path: 'test1', description: "Some description1", + description_html: "Some description html1", cached_markdown_version: 4 + ) project_namespace1 = namespaces.create!(id: 6, name: 'test2', path: 'test2', type: 'Project') - namespaces.create!(id: 7, name: 'test3', path: 'test3', description: "Some description3", - description_html: "Some description html3", cached_markdown_version: 4) + namespaces.create!( + id: 7, name: 'test3', path: 'test3', description: "Some description3", + description_html: "Some description html3", cached_markdown_version: 4 + ) project_namespace2 = namespaces.create!(id: 8, name: 'test4', path: 'test4', type: 'Project') - project1 = projects.create!(namespace_id: project_namespace1.id, name: 'gitlab1', path: 'gitlab1', - project_namespace_id: project_namespace1.id, description: "Some description2", - description_html: "Some description html2", cached_markdown_version: 4) - project2 = projects.create!(namespace_id: project_namespace2.id, name: 'gitlab2', path: 'gitlab2', - project_namespace_id: project_namespace2.id, - description: "Some description3", - description_html: "Some description html4", cached_markdown_version: 4) + project1 = projects.create!( + namespace_id: project_namespace1.id, name: 'gitlab1', path: 'gitlab1', + project_namespace_id: project_namespace1.id, description: "Some description2", + description_html: "Some description html2", cached_markdown_version: 4 + ) + project2 = projects.create!( + namespace_id: project_namespace2.id, name: 'gitlab2', path: 'gitlab2', + project_namespace_id: project_namespace2.id, + description: "Some description3", + description_html: "Some description html4", cached_markdown_version: 4 + ) namespace_details.delete_all diff --git a/spec/lib/gitlab/background_migration/backfill_releases_author_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_releases_author_id_spec.rb index d8ad10849f2..898f241a930 100644 --- a/spec/lib/gitlab/background_migration/backfill_releases_author_id_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_releases_author_id_spec.rb @@ -10,35 +10,52 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillReleasesAuthorId, let!(:test_user) { user_table.create!(name: 'test', email: 'test@example.com', username: 'test', projects_limit: 10) } let!(:ghost_user) do - user_table.create!(name: 'ghost', email: 'ghost@example.com', - username: 'ghost', user_type: User::USER_TYPES['ghost'], projects_limit: 100000) + user_table.create!( + name: 'ghost', email: 'ghost@example.com', + username: 'ghost', user_type: User::USER_TYPES['ghost'], projects_limit: 100000 + ) end let(:migration) do - described_class.new(start_id: 1, end_id: 100, - batch_table: :releases, batch_column: :id, - sub_batch_size: 10, pause_ms: 0, - job_arguments: [ghost_user.id], - connection: ApplicationRecord.connection) + described_class.new( + start_id: 1, end_id: 100, + batch_table: :releases, batch_column: :id, + sub_batch_size: 10, pause_ms: 0, + job_arguments: [ghost_user.id], + connection: ApplicationRecord.connection + ) end subject(:perform_migration) { migration.perform } before do - releases_table.create!(tag: 'tag1', name: 'tag1', - released_at: (date_time - 1.minute), author_id: test_user.id) - releases_table.create!(tag: 'tag2', name: 'tag2', - released_at: (date_time - 2.minutes), author_id: test_user.id) - releases_table.new(tag: 'tag3', name: 'tag3', - released_at: (date_time - 3.minutes), author_id: nil).save!(validate: false) - releases_table.new(tag: 'tag4', name: 'tag4', - released_at: (date_time - 4.minutes), author_id: nil).save!(validate: false) - releases_table.new(tag: 'tag5', name: 'tag5', - released_at: (date_time - 5.minutes), author_id: nil).save!(validate: false) - releases_table.create!(tag: 'tag6', name: 'tag6', - released_at: (date_time - 6.minutes), author_id: test_user.id) - releases_table.new(tag: 'tag7', name: 'tag7', - released_at: (date_time - 7.minutes), author_id: nil).save!(validate: false) + releases_table.create!( + tag: 'tag1', name: 'tag1', released_at: (date_time - 1.minute), author_id: test_user.id + ) + + releases_table.create!( + tag: 'tag2', name: 'tag2', released_at: (date_time - 2.minutes), author_id: test_user.id + ) + + releases_table.new( + tag: 'tag3', name: 'tag3', released_at: (date_time - 3.minutes), author_id: nil + ).save!(validate: false) + + releases_table.new( + tag: 'tag4', name: 'tag4', released_at: (date_time - 4.minutes), author_id: nil + ).save!(validate: false) + + releases_table.new( + tag: 'tag5', name: 'tag5', released_at: (date_time - 5.minutes), author_id: nil + ).save!(validate: false) + + releases_table.create!( + tag: 'tag6', name: 'tag6', released_at: (date_time - 6.minutes), author_id: test_user.id + ) + + releases_table.new( + tag: 'tag7', name: 'tag7', released_at: (date_time - 7.minutes), author_id: nil + ).save!(validate: false) end it 'backfills `author_id` for the selected records', :aggregate_failures do diff --git a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb index 4a50d08b2aa..d8874cb811b 100644 --- a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 20211202041233, -feature_category: :source_code_management do + feature_category: :source_code_management do let(:gitlab_shell) { Gitlab::Shell.new } let(:users) { table(:users) } let(:snippets) { table(:snippets) } @@ -14,24 +14,28 @@ feature_category: :source_code_management do let(:user_name) { 'Test' } let!(:user) do - users.create!(id: 1, - email: 'user@example.com', - projects_limit: 10, - username: 'test', - name: user_name, - state: user_state, - last_activity_on: 1.minute.ago, - user_type: user_type, - confirmed_at: 1.day.ago) + users.create!( + id: 1, + email: 'user@example.com', + projects_limit: 10, + username: 'test', + name: user_name, + state: user_state, + last_activity_on: 1.minute.ago, + user_type: user_type, + confirmed_at: 1.day.ago + ) end let!(:migration_bot) do - users.create!(id: 100, - email: "noreply+gitlab-migration-bot%s@#{Settings.gitlab.host}", - user_type: HasUserType::USER_TYPES[:migration_bot], - name: 'GitLab Migration Bot', - projects_limit: 10, - username: 'bot') + users.create!( + id: 100, + email: "noreply+gitlab-migration-bot%s@#{Settings.gitlab.host}", + user_type: HasUserType::USER_TYPES[:migration_bot], + name: 'GitLab Migration Bot', + projects_limit: 10, + username: 'bot' + ) end let!(:snippet_with_repo) { snippets.create!(id: 1, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) } @@ -260,15 +264,17 @@ feature_category: :source_code_management do context 'when both user name and snippet file_name are invalid' do let(:user_name) { '.' } let!(:other_user) do - users.create!(id: 2, - email: 'user2@example.com', - projects_limit: 10, - username: 'test2', - name: 'Test2', - state: user_state, - last_activity_on: 1.minute.ago, - user_type: user_type, - confirmed_at: 1.day.ago) + users.create!( + id: 2, + email: 'user2@example.com', + projects_limit: 10, + username: 'test2', + name: 'Test2', + state: user_state, + last_activity_on: 1.minute.ago, + user_type: user_type, + confirmed_at: 1.day.ago + ) end let!(:invalid_snippet) { snippets.create!(id: 4, type: 'PersonalSnippet', author_id: user.id, file_name: '.', content: content) } @@ -322,10 +328,12 @@ feature_category: :source_code_management do end def raw_repository(snippet) - Gitlab::Git::Repository.new('default', - "#{disk_path(snippet)}.git", - Gitlab::GlRepository::SNIPPET.identifier_for_container(snippet), - "@snippets/#{snippet.id}") + Gitlab::Git::Repository.new( + 'default', + "#{disk_path(snippet)}.git", + Gitlab::GlRepository::SNIPPET.identifier_for_container(snippet), + "@snippets/#{snippet.id}" + ) end def hashed_repository(snippet) diff --git a/spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb b/spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb index f642ec8c20d..3f1a57434a7 100644 --- a/spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb @@ -4,10 +4,12 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::BackfillVulnerabilityReadsClusterAgent, :migration, schema: 20220525221133 do # rubocop:disable Layout/LineLength let(:migration) do - described_class.new(start_id: 1, end_id: 10, - batch_table: table_name, batch_column: batch_column, - sub_batch_size: sub_batch_size, pause_ms: pause_ms, - connection: ApplicationRecord.connection) + described_class.new( + start_id: 1, end_id: 10, + batch_table: table_name, batch_column: batch_column, + sub_batch_size: sub_batch_size, pause_ms: pause_ms, + connection: ApplicationRecord.connection + ) end let(:users_table) { table(:users) } diff --git a/spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb b/spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb index 2f0eef3c399..5b234679e22 100644 --- a/spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb +++ b/spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::NullifyOrphanRunnerIdOnCiBuilds, - :suppress_gitlab_schemas_validate_connection, migration: :gitlab_ci, schema: 20220223112304 do + :suppress_gitlab_schemas_validate_connection, migration: :gitlab_ci, schema: 20220223112304 do let(:namespaces) { table(:namespaces) } let(:projects) { table(:projects) } let(:ci_runners) { table(:ci_runners) } diff --git a/spec/lib/gitlab/background_migration/prune_stale_project_export_jobs_spec.rb b/spec/lib/gitlab/background_migration/prune_stale_project_export_jobs_spec.rb index 5150d0ea4b0..3446b9f0676 100644 --- a/spec/lib/gitlab/background_migration/prune_stale_project_export_jobs_spec.rb +++ b/spec/lib/gitlab/background_migration/prune_stale_project_export_jobs_spec.rb @@ -10,14 +10,15 @@ RSpec.describe Gitlab::BackgroundMigration::PruneStaleProjectExportJobs, feature let(:uploads) { table(:project_relation_export_uploads) } subject(:perform_migration) do - described_class.new(start_id: 1, - end_id: 300, - batch_table: :project_export_jobs, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: 1, + end_id: 300, + batch_table: :project_export_jobs, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end it 'removes export jobs and associated relations older than 7 days' do diff --git a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb index 5fede892463..582c0fe1b1b 100644 --- a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb +++ b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb @@ -86,8 +86,10 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveBackfilledJobArtifactsExpireAt def create_job_artifact(id:, file_type:, expire_at:) job = table(:ci_builds, database: :ci).create!(id: id, partition_id: 100) - job_artifact.create!(id: id, job_id: job.id, expire_at: expire_at, project_id: project.id, - file_type: file_type, partition_id: 100) + job_artifact.create!( + id: id, job_id: job.id, expire_at: expire_at, project_id: project.id, + file_type: file_type, partition_id: 100 + ) end end end diff --git a/spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb b/spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb index 1844347f4a9..60ee61cf50a 100644 --- a/spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb +++ b/spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::RemoveOccurrencePipelinesAndDuplicateVulnerabilitiesFindings, :migration, - :suppress_gitlab_schemas_validate_connection, schema: 20220326161803 do + :suppress_gitlab_schemas_validate_connection, schema: 20220326161803 do let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } let(:users) { table(:users) } let(:user) { create_user! } diff --git a/spec/lib/gitlab/background_migration/remove_self_managed_wiki_notes_spec.rb b/spec/lib/gitlab/background_migration/remove_self_managed_wiki_notes_spec.rb index 81927100562..59d5d56ebe8 100644 --- a/spec/lib/gitlab/background_migration/remove_self_managed_wiki_notes_spec.rb +++ b/spec/lib/gitlab/background_migration/remove_self_managed_wiki_notes_spec.rb @@ -6,14 +6,15 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveSelfManagedWikiNotes, :migrati let(:notes) { table(:notes) } subject(:perform_migration) do - described_class.new(start_id: 1, - end_id: 30, - batch_table: :notes, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: 1, + end_id: 30, + batch_table: :notes, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end it 'removes all wiki notes' do diff --git a/spec/lib/gitlab/background_migration/reset_too_many_tags_skipped_registry_imports_spec.rb b/spec/lib/gitlab/background_migration/reset_too_many_tags_skipped_registry_imports_spec.rb index 3f59b0a24a3..afdd855c5a8 100644 --- a/spec/lib/gitlab/background_migration/reset_too_many_tags_skipped_registry_imports_spec.rb +++ b/spec/lib/gitlab/background_migration/reset_too_many_tags_skipped_registry_imports_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::ResetTooManyTagsSkippedRegistryImports, :migration, - :aggregate_failures, - schema: 20220502173045 do + :aggregate_failures, + schema: 20220502173045 do let(:namespaces) { table(:namespaces) } let(:projects) { table(:projects) } let(:container_repositories) { table(:container_repositories) } @@ -15,46 +15,54 @@ RSpec.describe Gitlab::BackgroundMigration::ResetTooManyTagsSkippedRegistryImpor let!(:project) { projects.create!(id: 1, project_namespace_id: 1, namespace_id: 1, path: 'bar', name: 'bar') } let!(:container_repository1) do - container_repositories.create!(id: 1, - project_id: 1, - name: 'a', - migration_state: 'import_skipped', - migration_skipped_at: Time.zone.now, - migration_skipped_reason: 2, - migration_pre_import_started_at: Time.zone.now, - migration_pre_import_done_at: Time.zone.now, - migration_import_started_at: Time.zone.now, - migration_import_done_at: Time.zone.now, - migration_aborted_at: Time.zone.now, - migration_retries_count: 2, - migration_aborted_in_state: 'importing') + container_repositories.create!( + id: 1, + project_id: 1, + name: 'a', + migration_state: 'import_skipped', + migration_skipped_at: Time.zone.now, + migration_skipped_reason: 2, + migration_pre_import_started_at: Time.zone.now, + migration_pre_import_done_at: Time.zone.now, + migration_import_started_at: Time.zone.now, + migration_import_done_at: Time.zone.now, + migration_aborted_at: Time.zone.now, + migration_retries_count: 2, + migration_aborted_in_state: 'importing' + ) end let!(:container_repository2) do - container_repositories.create!(id: 2, - project_id: 1, - name: 'b', - migration_state: 'import_skipped', - migration_skipped_at: Time.zone.now, - migration_skipped_reason: 2) + container_repositories.create!( + id: 2, + project_id: 1, + name: 'b', + migration_state: 'import_skipped', + migration_skipped_at: Time.zone.now, + migration_skipped_reason: 2 + ) end let!(:container_repository3) do - container_repositories.create!(id: 3, - project_id: 1, - name: 'c', - migration_state: 'import_skipped', - migration_skipped_at: Time.zone.now, - migration_skipped_reason: 1) + container_repositories.create!( + id: 3, + project_id: 1, + name: 'c', + migration_state: 'import_skipped', + migration_skipped_at: Time.zone.now, + migration_skipped_reason: 1 + ) end # This is an unlikely state, but included here to test the edge case let!(:container_repository4) do - container_repositories.create!(id: 4, - project_id: 1, - name: 'd', - migration_state: 'default', - migration_skipped_reason: 2) + container_repositories.create!( + id: 4, + project_id: 1, + name: 'd', + migration_state: 'default', + migration_skipped_reason: 2 + ) end describe '#up' do diff --git a/spec/lib/gitlab/background_migration/set_correct_vulnerability_state_spec.rb b/spec/lib/gitlab/background_migration/set_correct_vulnerability_state_spec.rb index 2372ce21c4c..df1ee494987 100644 --- a/spec/lib/gitlab/background_migration/set_correct_vulnerability_state_spec.rb +++ b/spec/lib/gitlab/background_migration/set_correct_vulnerability_state_spec.rb @@ -35,13 +35,15 @@ RSpec.describe Gitlab::BackgroundMigration::SetCorrectVulnerabilityState do let(:dismissed_state) { 2 } let(:migration_job) do - described_class.new(start_id: vulnerability_with_dismissed_at.id, - end_id: vulnerability_without_dismissed_at.id, - batch_table: :vulnerabilities, - batch_column: :id, - sub_batch_size: 1, - pause_ms: 0, - connection: ActiveRecord::Base.connection) + described_class.new( + start_id: vulnerability_with_dismissed_at.id, + end_id: vulnerability_without_dismissed_at.id, + batch_table: :vulnerabilities, + batch_column: :id, + sub_batch_size: 1, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ) end describe '#filter_batch' do diff --git a/spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb b/spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb index e9f73672144..5109c3ec0c2 100644 --- a/spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb +++ b/spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb @@ -3,21 +3,22 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::SetLegacyOpenSourceLicenseAvailableForNonPublicProjects, - :migration, - schema: 20220722110026 do + :migration, + schema: 20220722110026 do let(:namespaces_table) { table(:namespaces) } let(:projects_table) { table(:projects) } let(:project_settings_table) { table(:project_settings) } subject(:perform_migration) do - described_class.new(start_id: projects_table.minimum(:id), - end_id: projects_table.maximum(:id), - batch_table: :projects, - batch_column: :id, - sub_batch_size: 2, - pause_ms: 0, - connection: ActiveRecord::Base.connection) - .perform + described_class.new( + start_id: projects_table.minimum(:id), + end_id: projects_table.maximum(:id), + batch_table: :projects, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform end it 'sets `legacy_open_source_license_available` attribute to false for non-public projects', :aggregate_failures do @@ -37,11 +38,13 @@ RSpec.describe Gitlab::BackgroundMigration::SetLegacyOpenSourceLicenseAvailableF def create_legacy_license_project(path, visibility_level:) namespace = namespaces_table.create!(name: "namespace-#{path}", path: "namespace-#{path}") project_namespace = namespaces_table.create!(name: "project-namespace-#{path}", path: path, type: 'Project') - project = projects_table.create!(name: path, - path: path, - namespace_id: namespace.id, - project_namespace_id: project_namespace.id, - visibility_level: visibility_level) + project = projects_table.create!( + name: path, + path: path, + namespace_id: namespace.id, + project_namespace_id: project_namespace.id, + visibility_level: visibility_level + ) project_settings_table.create!(project_id: project.id, legacy_open_source_license_available: true) project diff --git a/spec/lib/gitlab/background_migration/update_delayed_project_removal_to_null_for_user_namespaces_spec.rb b/spec/lib/gitlab/background_migration/update_delayed_project_removal_to_null_for_user_namespaces_spec.rb index 980a7771f4c..0579a299c74 100644 --- a/spec/lib/gitlab/background_migration/update_delayed_project_removal_to_null_for_user_namespaces_spec.rb +++ b/spec/lib/gitlab/background_migration/update_delayed_project_removal_to_null_for_user_namespaces_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::BackgroundMigration::UpdateDelayedProjectRemovalToNullForUserNamespaces, - :migration do + :migration do let(:namespaces_table) { table(:namespaces) } let(:namespace_settings_table) { table(:namespace_settings) } diff --git a/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb b/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb index c090c1df424..75fe5699986 100644 --- a/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb +++ b/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb @@ -13,10 +13,12 @@ RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeB let(:sub_batch_size) { 1 } let(:pause_ms) { 0 } let(:migration) do - described_class.new(start_id: 1, end_id: 10, - batch_table: table_name, batch_column: batch_column, - sub_batch_size: sub_batch_size, pause_ms: pause_ms, - connection: ApplicationRecord.connection) + described_class.new( + start_id: 1, end_id: 10, + batch_table: table_name, batch_column: batch_column, + sub_batch_size: sub_batch_size, pause_ms: pause_ms, + connection: ApplicationRecord.connection + ) end subject(:perform_migration) do diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb index bf08e782035..82ec3e791a4 100644 --- a/spec/lib/gitlab/data_builder/deployment_spec.rb +++ b/spec/lib/gitlab/data_builder/deployment_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::DataBuilder::Deployment do +RSpec.describe Gitlab::DataBuilder::Deployment, feature_category: :continuous_delivery do describe '.build' do it 'returns the object kind for a deployment' do deployment = build(:deployment, deployable: nil, environment: create(:environment)) @@ -40,6 +40,7 @@ RSpec.describe Gitlab::DataBuilder::Deployment do expect(data[:commit_url]).to eq(expected_commit_url) expect(data[:commit_title]).to eq(commit.title) expect(data[:ref]).to eq(deployment.ref) + expect(data[:environment_tier]).to eq('other') end it 'does not include the deployable URL when there is no deployable' do diff --git a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb index 4f347034c0b..0b25389c667 100644 --- a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb +++ b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb @@ -18,7 +18,9 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do let(:migration_name) { 'test' } let(:migration_version) { '12345' } let(:migration_meta) { { 'max_batch_size' => 1, 'total_tuple_count' => 10, 'interval' => 60 } } - let(:expected_json_keys) { %w[version name walltime success total_database_size_change query_statistics] } + let(:expected_json_keys) do + %w[version name walltime success total_database_size_change query_statistics error_message] + end it 'executes the given block' do expect do |b| @@ -90,16 +92,14 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do end context 'upon failure' do - where(exception: ['something went wrong', SystemStackError, Interrupt]) + where(:exception, :error_message) do + [[StandardError, 'something went wrong'], [ActiveRecord::StatementTimeout, 'timeout']] + end with_them do subject(:observe) do instrumentation.observe(version: migration_version, name: migration_name, - connection: connection, meta: migration_meta) { raise exception } - end - - it 'raises the exception' do - expect { observe }.to raise_error(exception) + connection: connection, meta: migration_meta) { raise exception, error_message } end context 'retrieving observations' do @@ -107,10 +107,6 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do before do observe - # rubocop:disable Lint/RescueException - rescue Exception - # rubocop:enable Lint/RescueException - # ignore (we expect this exception) end it 'records a valid observation', :aggregate_failures do @@ -118,6 +114,7 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do expect(subject['success']).to be_falsey expect(subject['version']).to eq(migration_version) expect(subject['name']).to eq(migration_name) + expect(subject['error_message']).to eq(error_message) end it 'transforms observation to expected json' do diff --git a/spec/lib/gitlab/database/schema_validation/inconsistency_spec.rb b/spec/lib/gitlab/database/schema_validation/inconsistency_spec.rb index cb3df75b3fb..a49ff8339a1 100644 --- a/spec/lib/gitlab/database/schema_validation/inconsistency_spec.rb +++ b/spec/lib/gitlab/database/schema_validation/inconsistency_spec.rb @@ -50,6 +50,32 @@ RSpec.describe Gitlab::Database::SchemaValidation::Inconsistency, feature_catego end end + describe '#object_type' do + it 'returns the structure sql object type' do + expect(inconsistency.object_type).to eq('Index') + end + + context 'when the structure sql object is not available' do + subject(:inconsistency) { described_class.new(validator, nil, database_object) } + + it 'returns the database object type' do + expect(inconsistency.object_type).to eq('Index') + end + end + end + + describe '#structure_sql_statement' do + it 'returns structure sql statement' do + expect(inconsistency.structure_sql_statement).to eq("#{structure_sql_statement}\n") + end + end + + describe '#database_statement' do + it 'returns database statement' do + expect(inconsistency.database_statement).to eq("#{database_statement}\n") + end + end + describe '#inspect' do let(:expected_output) do <<~MSG diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8a2602ea9f6..34f9948b9dc 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -647,6 +647,7 @@ project: - redirect_routes - statistics - container_repositories +- container_registry_data_repair_detail - uploads - file_uploads - import_state diff --git a/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb b/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb index d28fa0bbe97..c9ac13eefc0 100644 --- a/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb +++ b/spec/models/blob_viewer/metrics_dashboard_yml_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BlobViewer::MetricsDashboardYml do +RSpec.describe BlobViewer::MetricsDashboardYml, feature_category: :metrics do include FakeBlobHelpers include RepoHelpers @@ -119,4 +119,18 @@ RSpec.describe BlobViewer::MetricsDashboardYml do expect(viewer.errors).to eq ["YAML syntax: The parsed YAML is too big"] end end + + describe '.can_render?' do + subject { described_class.can_render?(blob) } + + it { is_expected.to be false } + + context 'when metrics dashboard feature is available' do + before do + stub_feature_flags(remove_monitor_metrics: false) + end + + it { is_expected.to be true } + end + end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index df630d6336c..edb856d34df 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -885,4 +885,94 @@ eos expect(commit.has_been_reverted?(user, issue.notes_with_associations)).to eq(false) end end + + describe '#tipping_refs' do + let_it_be(:tag_name) { 'v1.1.0' } + let_it_be(:branch_names) { %w[master not-merged-branch v1.1.0] } + + shared_examples 'tipping ref names' do + context 'when called without limits' do + it 'return tipping refs names' do + expect(called_method.call).to eq(expected) + end + end + + context 'when called with limits' do + it 'return tipping refs names' do + limit = 1 + expect(called_method.call(limit).size).to be <= limit + end + end + + describe '#tipping_branches' do + let(:called_method) { ->(limit = 0) { commit.tipping_branches(limit: limit) } } + let(:expected) { branch_names } + + it_behaves_like 'with tipping ref names' + end + + describe '#tipping_tags' do + let(:called_method) { ->(limit = 0) { commit.tipping_tags(limit: limit) } } + let(:expected) { [tag_name] } + + it_behaves_like 'with tipping ref names' + end + end + end + + context 'containing refs' do + shared_examples 'containing ref names' do + context 'without arguments' do + it 'returns branch names containing the commit' do + expect(ref_containing.call).to eq(containing_refs) + end + end + + context 'with limit argument' do + it 'returns the appropriate amount branch names' do + limit = 2 + expect(ref_containing.call(limit: limit).size).to be <= limit + end + end + + context 'with tipping refs excluded' do + let(:excluded_refs) do + project.repository.refs_by_oid(oid: commit_sha, ref_patterns: [ref_prefix]).map { |n| n.delete_prefix(ref_prefix) } + end + + it 'returns branch names containing the commit without the one with the commit at tip' do + expect(ref_containing.call(excluded_tipped: true)).to eq(containing_refs - excluded_refs) + end + + it 'returns the appropriate amount branch names with limit argument' do + limit = 2 + expect(ref_containing.call(limit: limit, excluded_tipped: true).size).to be <= limit + end + end + end + + describe '#branches_containing' do + let_it_be(:commit_sha) { project.commit.sha } + let_it_be(:containing_refs) { project.repository.branch_names_contains(commit_sha) } + + let(:ref_prefix) { Gitlab::Git::BRANCH_REF_PREFIX } + + let(:ref_containing) { ->(limit: 0, excluded_tipped: false) { commit.branches_containing(exclude_tipped: excluded_tipped, limit: limit) } } + + it_behaves_like 'containing ref names' + end + + describe '#tags_containing' do + let_it_be(:tag_name) { 'v1.1.0' } + let_it_be(:commit_sha) { project.repository.find_tag(tag_name).target_commit.sha } + let_it_be(:containing_refs) { %w[v1.1.0 v1.1.1] } + + let(:ref_prefix) { Gitlab::Git::TAG_REF_PREFIX } + + let(:commit) { project.repository.commit(commit_sha) } + let(:ref_containing) { ->(limit: 0, excluded_tipped: false) { commit.tags_containing(exclude_tipped: excluded_tipped, limit: limit) } } + + it_behaves_like 'containing ref names' + end + end end diff --git a/spec/models/container_registry/data_repair_detail_spec.rb b/spec/models/container_registry/data_repair_detail_spec.rb index 92833553a1e..4d2ac5fff42 100644 --- a/spec/models/container_registry/data_repair_detail_spec.rb +++ b/spec/models/container_registry/data_repair_detail_spec.rb @@ -8,4 +8,22 @@ RSpec.describe ContainerRegistry::DataRepairDetail, type: :model, feature_catego subject { described_class.new(project: project) } it { is_expected.to belong_to(:project).required } + + it_behaves_like 'having unique enum values' + + describe '.ongoing_since' do + let_it_be(:repair_detail1) { create(:container_registry_data_repair_detail, :ongoing, updated_at: 1.day.ago) } + let_it_be(:repair_detail2) { create(:container_registry_data_repair_detail, :ongoing, updated_at: 20.minutes.ago) } + let_it_be(:repair_detail3) do + create(:container_registry_data_repair_detail, :completed, updated_at: 20.minutes.ago) + end + + let_it_be(:repair_detail4) do + create(:container_registry_data_repair_detail, :completed, updated_at: 31.minutes.ago) + end + + subject { described_class.ongoing_since(30.minutes.ago) } + + it { is_expected.to contain_exactly(repair_detail1) } + end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 8bd7c057a6e..1c43eafb576 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -724,4 +724,12 @@ RSpec.describe Milestone do end end end + + describe '#lock_version' do + let_it_be(:milestone) { create(:milestone, project: project) } + + it 'ensures that lock_version and optimistic locking is enabled' do + expect(milestone.lock_version).to be_present + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 855c5f66554..e9bb01f4b23 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -95,6 +95,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do it { is_expected.to have_one(:mock_ci_integration) } it { is_expected.to have_one(:mock_monitoring_integration) } it { is_expected.to have_one(:service_desk_custom_email_verification).class_name('ServiceDesk::CustomEmailVerification') } + it { is_expected.to have_one(:container_registry_data_repair_detail).class_name('ContainerRegistry::DataRepairDetail') } it { is_expected.to have_many(:commit_statuses) } it { is_expected.to have_many(:ci_pipelines) } it { is_expected.to have_many(:ci_refs) } @@ -6800,6 +6801,19 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do end end + describe '.pending_data_repair_analysis' do + it 'returns projects that are not in ContainerRegistry::DataRepairDetail' do + project_1 = create(:project) + project_2 = create(:project) + + expect(described_class.pending_data_repair_analysis).to match_array([project_1, project_2]) + + create(:container_registry_data_repair_detail, project: project_1) + + expect(described_class.pending_data_repair_analysis).to match_array([project_2]) + end + end + describe '.deployments' do subject { project.deployments } diff --git a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb index a3103b1af57..143bc1672f8 100644 --- a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb +++ b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb @@ -22,6 +22,7 @@ RSpec.describe 'Getting Metrics Dashboard Annotations', feature_category: :metri create(:metrics_dashboard_annotation, environment: environment, starting_at: to.advance(minutes: 5), dashboard_path: path) end + let(:remove_monitor_metrics) { false } let(:args) { "from: \"#{from}\", to: \"#{to}\"" } let(:fields) do <<~QUERY @@ -50,7 +51,7 @@ RSpec.describe 'Getting Metrics Dashboard Annotations', feature_category: :metri end before do - stub_feature_flags(remove_monitor_metrics: false) + stub_feature_flags(remove_monitor_metrics: remove_monitor_metrics) project.add_developer(current_user) post_graphql(query, current_user: current_user) end @@ -86,4 +87,18 @@ RSpec.describe 'Getting Metrics Dashboard Annotations', feature_category: :metri it_behaves_like 'a working graphql query' end end + + context 'when metrics dashboard feature is unavailable' do + let(:remove_monitor_metrics) { true } + + it_behaves_like 'a working graphql query' + + it 'returns nil' do + annotations = graphql_data.dig( + 'project', 'environments', 'nodes', 0, 'metricsDashboard', 'annotations' + ) + + expect(annotations).to be_nil + end + end end diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb index 3c7f4a030f9..d81744abe1b 100644 --- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb +++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb @@ -19,6 +19,10 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create, feature_categ graphql_mutation_response(:create_annotation) end + before do + stub_feature_flags(remove_monitor_metrics: false) + end + specify { expect(described_class).to require_graphql_authorizations(:admin_metrics_dashboard_annotation) } context 'when annotation source is environment' do @@ -103,6 +107,15 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create, feature_categ it_behaves_like 'an invalid argument to the mutation', argument_name: :environment_id end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end end end diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb index c104138b725..09977cd19d7 100644 --- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb @@ -17,6 +17,10 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete, feature_categ graphql_mutation_response(:delete_annotation) end + before do + stub_feature_flags(remove_monitor_metrics: false) + end + specify { expect(described_class).to require_graphql_authorizations(:admin_metrics_dashboard_annotation) } context 'when the user has permission to delete the annotation' do @@ -54,6 +58,15 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete, feature_categ expect(mutation_response['errors']).to eq([service_response[:message]]) end end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end end context 'when the user does not have permission to delete the annotation' do diff --git a/spec/requests/api/graphql/project/branches_tipping_at_commit_spec.rb b/spec/requests/api/graphql/project/branches_tipping_at_commit_spec.rb deleted file mode 100644 index bba8977078d..00000000000 --- a/spec/requests/api/graphql/project/branches_tipping_at_commit_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Query.project(fullPath).tagsTippingAtCommit(commitSha)', feature_category: :source_code_management do - include GraphqlHelpers - include Presentable - - let_it_be(:project) { create(:project, :repository) } - let_it_be(:repository) { project.repository.raw } - let_it_be(:current_user) { project.first_owner } - let_it_be(:branches_names) { %w[master not-merged-branch v1.1.0] } - - let(:post_query) { post_graphql(query, current_user: current_user) } - let(:path) { %w[project branchesTippingAtCommit names] } - let(:data) { graphql_data.dig(*path) } - - let(:query) do - graphql_query_for( - :project, - { fullPath: project.full_path }, - query_graphql_field(:branchesTippingAtCommit, { commitSha: commit_sha }, :names) - ) - end - - context 'when commit exists and is tipping branches' do - let_it_be(:commit_sha) { repository.commit.id } - - context 'with authorized user' do - it 'returns branches names tipping the commit' do - post_query - - expect(data).to eq(branches_names) - end - end - - context 'when user is not authorized' do - let(:current_user) { create(:user) } - - it 'returns branches names tipping the commit' do - post_query - - expect(data).to eq(nil) - end - end - end - - context 'when commit does not exist' do - let(:commit_sha) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff4' } - - it 'returns tags names tipping the commit' do - post_query - - expect(data).to eq([]) - end - end - - context 'when commit exists but does not tip any branches' do - let(:commit_sha) { project.repository.commits(nil, { limit: 4 }).commits[2].id } - - it 'returns tags names tipping the commit' do - post_query - - expect(data).to eq([]) - end - end -end diff --git a/spec/requests/api/graphql/project/commit_references_spec.rb b/spec/requests/api/graphql/project/commit_references_spec.rb new file mode 100644 index 00000000000..4b545adee12 --- /dev/null +++ b/spec/requests/api/graphql/project/commit_references_spec.rb @@ -0,0 +1,240 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.project(fullPath).commitReferences(commitSha)', feature_category: :source_code_management do + include GraphqlHelpers + include Presentable + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:repository) { project.repository.raw } + let_it_be(:current_user) { project.first_owner } + let_it_be(:branches_names) { %w[master not-merged-branch v1.1.0] } + let_it_be(:tag_name) { 'v1.0.0' } + let_it_be(:commit_sha) { repository.commit.id } + + let(:post_query) { post_graphql(query, current_user: current_user) } + let(:data) { graphql_data.dig(*path) } + let(:base_args) { {} } + let(:args) { base_args } + + shared_context 'with the limit argument' do + context 'with limit of 2' do + let(:args) { { limit: 2 } } + + it 'returns the right amount of refs' do + post_query + expect(data.count).to be <= 2 + end + end + + context 'with limit of -2' do + let(:args) { { limit: -2 } } + + it 'casts an argument error "limit must be greater then 0"' do + post_query + expect(graphql_errors).to include(custom_graphql_error(path - ['names'], + 'limit must be within 1..1000')) + end + end + + context 'with limit of 1001' do + let(:args) { { limit: 1001 } } + + it 'casts an argument error "limit must be greater then 0"' do + post_query + expect(graphql_errors).to include(custom_graphql_error(path - ['names'], + 'limit must be within 1..1000')) + end + end + end + + describe 'the path commitReferences should return nil' do + let(:path) { %w[project commitReferences] } + + let(:query) do + graphql_query_for(:project, { fullPath: project.full_path }, + query_graphql_field( + :commitReferences, + { commitSha: commit_sha }, + query_graphql_field(:tippingTags, :names) + ) + ) + end + + context 'when commit does not exist' do + let(:commit_sha) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff4' } + + it 'commitReferences returns nil' do + post_query + expect(data).to eq(nil) + end + end + + context 'when sha length is incorrect' do + let(:commit_sha) { 'foo' } + + it 'commitReferences returns nil' do + post_query + expect(data).to eq(nil) + end + end + + context 'when user is not authorized' do + let(:commit_sha) { repository.commit.id } + let(:current_user) { create(:user) } + + it 'commitReferences returns nil' do + post_query + expect(data).to eq(nil) + end + end + end + + context 'with containing refs' do + let(:base_args) { { excludeTipped: false } } + let(:excluded_tipped_args) do + hash = base_args.dup + hash[:excludeTipped] = true + hash + end + + context 'with path Query.project(fullPath).commitReferences(commitSha).containingTags' do + let_it_be(:commit_sha) { repository.find_tag(tag_name).target_commit.sha } + let_it_be(:path) { %w[project commitReferences containingTags names] } + let(:query) do + graphql_query_for( + :project, + { fullPath: project.full_path }, + query_graphql_field( + :commitReferences, + { commitSha: commit_sha }, + query_graphql_field(:containingTags, args, :names) + ) + ) + end + + context 'without excludeTipped argument' do + it 'returns tags names containing the commit' do + post_query + expect(data).to eq(%w[v1.0.0 v1.1.0 v1.1.1]) + end + end + + context 'with excludeTipped argument' do + let_it_be(:ref_prefix) { Gitlab::Git::TAG_REF_PREFIX } + + let(:args) { excluded_tipped_args } + + it 'returns tags names containing the commit without the tipped tags' do + excluded_refs = project.repository + .refs_by_oid(oid: commit_sha, ref_patterns: [ref_prefix]) + .map { |n| n.delete_prefix(ref_prefix) } + + post_query + expect(data).to eq(%w[v1.0.0 v1.1.0 v1.1.1] - excluded_refs) + end + end + + include_context 'with the limit argument' + end + + context 'with path Query.project(fullPath).commitReferences(commitSha).containingBranches' do + let_it_be(:ref_prefix) { Gitlab::Git::BRANCH_REF_PREFIX } + let_it_be(:path) { %w[project commitReferences containingBranches names] } + + let(:query) do + graphql_query_for( + :project, + { fullPath: project.full_path }, + query_graphql_field( + :commitReferences, + { commitSha: commit_sha }, + query_graphql_field(:containingBranches, args, :names) + ) + ) + end + + context 'without excludeTipped argument' do + it 'returns branch names containing the commit' do + refs = project.repository.branch_names_contains(commit_sha) + + post_query + + expect(data).to eq(refs) + end + end + + context 'with excludeTipped argument' do + let(:args) { excluded_tipped_args } + + it 'returns branch names containing the commit without the tipped branch' do + refs = project.repository.branch_names_contains(commit_sha) + + excluded_refs = project.repository + .refs_by_oid(oid: commit_sha, ref_patterns: [ref_prefix]) + .map { |n| n.delete_prefix(ref_prefix) } + + post_query + + expect(data).to eq(refs - excluded_refs) + end + end + + include_context 'with the limit argument' + end + end + + context 'with tipping refs' do + context 'with path Query.project(fullPath).commitReferences(commitSha).tippingTags' do + let(:commit_sha) { repository.find_tag(tag_name).dereferenced_target.sha } + let(:path) { %w[project commitReferences tippingTags names] } + + let(:query) do + graphql_query_for( + :project, + { fullPath: project.full_path }, + query_graphql_field( + :commitReferences, + { commitSha: commit_sha }, + query_graphql_field(:tippingTags, args, :names) + ) + ) + end + + context 'with authorized user' do + it 'returns tags names tipping the commit' do + post_query + + expect(data).to eq([tag_name]) + end + end + + include_context 'with the limit argument' + end + + context 'with path Query.project(fullPath).commitReferences(commitSha).tippingBranches' do + let(:path) { %w[project commitReferences tippingBranches names] } + + let(:query) do + graphql_query_for( + :project, + { fullPath: project.full_path }, + query_graphql_field( + :commitReferences, + { commitSha: commit_sha }, + query_graphql_field(:tippingBranches, args, :names) + ) + ) + end + + it 'returns branches names tipping the commit' do + post_query + + expect(data).to eq(branches_names) + end + + include_context 'with the limit argument' + end + end +end diff --git a/spec/requests/api/graphql/project/project_statistics_redirect_spec.rb b/spec/requests/api/graphql/project/project_statistics_redirect_spec.rb new file mode 100644 index 00000000000..8049a75ace3 --- /dev/null +++ b/spec/requests/api/graphql/project/project_statistics_redirect_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'rendering project storage type routes', feature_category: :shared do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + + let(:query) do + graphql_query_for('project', + { 'fullPath' => project.full_path }, + "statisticsDetailsPaths { #{all_graphql_fields_for('ProjectStatisticsRedirect')} }") + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: user) + end + end + + shared_examples 'valid routes for storage type' do + it 'contains all keys' do + post_graphql(query, current_user: user) + + expect(graphql_data['project']['statisticsDetailsPaths'].keys).to match_array( + %w[repository buildArtifacts wiki packages snippets containerRegistry] + ) + end + + it 'contains valid paths' do + repository_url = Gitlab::Routing.url_helpers.project_tree_url(project, "master") + wiki_url = Gitlab::Routing.url_helpers.project_wikis_pages_url(project) + build_artifacts_url = Gitlab::Routing.url_helpers.project_artifacts_url(project) + packages_url = Gitlab::Routing.url_helpers.project_packages_url(project) + snippets_url = Gitlab::Routing.url_helpers.project_snippets_url(project) + container_registry_url = Gitlab::Routing.url_helpers.project_container_registry_index_url(project) + + post_graphql(query, current_user: user) + + expect(graphql_data['project']['statisticsDetailsPaths'].values).to match_array [repository_url, + wiki_url, + build_artifacts_url, + packages_url, + snippets_url, + container_registry_url] + end + end + + context 'when project is public' do + it_behaves_like 'valid routes for storage type' + + context 'when user is nil' do + let_it_be(:user) { nil } + + it_behaves_like 'valid routes for storage type' + end + end + + context 'when project is private' do + let_it_be(:project) { create(:project, :private) } + + before do + project.add_reporter(user) + end + + it_behaves_like 'valid routes for storage type' + + context 'when user is nil' do + it 'hides statisticsDetailsPaths for nil users' do + post_graphql(query, current_user: nil) + + expect(graphql_data['project']).to be_blank + end + end + end +end diff --git a/spec/requests/api/graphql/project/tags_tipping_at_commit_spec.rb b/spec/requests/api/graphql/project/tags_tipping_at_commit_spec.rb deleted file mode 100644 index a5e26482a9e..00000000000 --- a/spec/requests/api/graphql/project/tags_tipping_at_commit_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Query.project(fullPath).tagsTippingAtCommit(commitSha)', feature_category: :source_code_management do - include GraphqlHelpers - include Presentable - - let_it_be(:project) { create(:project, :repository) } - let_it_be(:repository) { project.repository.raw } - let_it_be(:current_user) { project.first_owner } - let_it_be(:tag_name) { 'v1.0.0' } - - let(:post_query) { post_graphql(query, current_user: current_user) } - let(:path) { %w[project tagsTippingAtCommit names] } - let(:data) { graphql_data.dig(*path) } - - let(:query) do - graphql_query_for( - :project, - { fullPath: project.full_path }, - query_graphql_field(:tagsTippingAtCommit, { commitSha: commit_sha }, :names) - ) - end - - context 'when commit exists and is tipping tags' do - let(:commit_sha) { repository.find_tag(tag_name).dereferenced_target.sha } - - context 'with authorized user' do - it 'returns tags names tipping the commit' do - post_query - - expect(data).to eq([tag_name]) - end - end - - context 'when user is not authorized' do - let(:current_user) { create(:user) } - - it 'returns tags names tipping the commit' do - post_query - - expect(data).to eq(nil) - end - end - end - - context 'when commit does not exist' do - let(:commit_sha) { '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff4' } - - it 'returns tags names tipping the commit' do - post_query - - expect(data).to eq([]) - end - end - - context 'when commit exists but does not tip any tags' do - let(:commit_sha) { project.repository.commits(nil, { limit: 4 }).commits[2].id } - - it 'returns tags names tipping the commit' do - post_query - - expect(data).to eq([]) - end - end -end diff --git a/spec/requests/api/metrics/dashboard/annotations_spec.rb b/spec/requests/api/metrics/dashboard/annotations_spec.rb index cefd5896158..250fe2a3ee3 100644 --- a/spec/requests/api/metrics/dashboard/annotations_spec.rb +++ b/spec/requests/api/metrics/dashboard/annotations_spec.rb @@ -16,6 +16,7 @@ RSpec.describe API::Metrics::Dashboard::Annotations, feature_category: :metrics let(:url) { "/#{source_type.pluralize}/#{source.id}/metrics_dashboard/annotations" } before do + stub_feature_flags(remove_monitor_metrics: false) project.add_developer(user) end @@ -104,6 +105,18 @@ RSpec.describe API::Metrics::Dashboard::Annotations, feature_category: :metrics expect(response).to have_gitlab_http_status(:forbidden) end end + + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns 404 not found' do + post api(url, user), params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + end end end diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb index 69579323908..c003ae9cd48 100644 --- a/spec/requests/api/project_packages_spec.rb +++ b/spec/requests/api/project_packages_spec.rb @@ -224,19 +224,15 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do context 'without the need for a license' do context 'with build info' do - let_it_be(:package1) { create(:npm_package, :with_build, project: project) } - - it 'returns an empty array for the pipelines attribute' do - subject - - expect(json_response['pipelines']).to be_empty - end - it 'does not result in additional queries' do control = ActiveRecord::QueryRecorder.new do get api(package_url, user) end + pipeline = create(:ci_pipeline, user: user, project: project) + create(:ci_build, user: user, pipeline: pipeline, project: project) + create(:package_build_info, package: package1, pipeline: pipeline) + expect do get api(package_url, user) end.not_to exceed_query_limit(control) diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb index 38d6a05a104..91e5ed76c37 100644 --- a/spec/requests/api/project_templates_spec.rb +++ b/spec/requests/api/project_templates_spec.rb @@ -10,6 +10,7 @@ RSpec.describe API::ProjectTemplates, feature_category: :source_code_management let(:url_encoded_path) { "#{public_project.namespace.path}%2F#{public_project.path}" } before do + stub_feature_flags(remove_monitor_metrics: false) private_project.add_developer(developer) end @@ -71,6 +72,18 @@ RSpec.describe API::ProjectTemplates, feature_category: :source_code_management expect(json_response).to satisfy_one { |template| template['key'] == 'Default' } end + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns 400 bad request like other unknown types' do + get api("/projects/#{public_project.id}/templates/metrics_dashboard_ymls") + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + it 'returns issue templates' do get api("/projects/#{private_project.id}/templates/issues", developer) @@ -171,6 +184,18 @@ RSpec.describe API::ProjectTemplates, feature_category: :source_code_management expect(json_response['name']).to eq('Default') end + context 'when metrics dashboard feature is unavailable' do + before do + stub_feature_flags(remove_monitor_metrics: true) + end + + it 'returns 400 bad request like other unknown types' do + get api("/projects/#{public_project.id}/templates/metrics_dashboard_ymls/Default") + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + it 'returns a specific license' do get api("/projects/#{public_project.id}/templates/licenses/mit") diff --git a/spec/services/jira_connect/sync_service_spec.rb b/spec/services/jira_connect/sync_service_spec.rb index fc1b4e997a5..7457cdca13c 100644 --- a/spec/services/jira_connect/sync_service_spec.rb +++ b/spec/services/jira_connect/sync_service_spec.rb @@ -44,16 +44,18 @@ RSpec.describe JiraConnect::SyncService, feature_category: :integrations do subject end - context 'when a request returns an error' do - it 'logs the response as an error' do + context 'when a request returns errors' do + it 'logs each response as an error' do expect_next(client).to store_info( [ { 'errorMessages' => ['some error message'] }, - { 'errorMessages' => ['x'] } + { 'errorMessage' => 'a single error message' }, + { 'errorMessages' => [] }, + { 'errorMessage' => '' } ]) expect_log(:error, { 'errorMessages' => ['some error message'] }) - expect_log(:error, { 'errorMessages' => ['x'] }) + expect_log(:error, { 'errorMessage' => 'a single error message' }) subject end diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb index 9a26f50903f..ee1b4a3c33a 100644 --- a/spec/support/import_export/export_file_helper.rb +++ b/spec/support/import_export/export_file_helper.rb @@ -21,21 +21,25 @@ module ExportFileHelper create(:label_link, label: label, target: issue) - ci_pipeline = create(:ci_pipeline, - project: project, - sha: merge_request.diff_head_sha, - ref: merge_request.source_branch, - statuses: [commit_status]) + ci_pipeline = create( + :ci_pipeline, + project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch, + statuses: [commit_status] + ) create(:ci_build, pipeline: ci_pipeline, project: project) create(:milestone, project: project) create(:note, noteable: issue, project: project) create(:note, noteable: merge_request, project: project) create(:note, noteable: snippet, project: project) - create(:note_on_commit, - author: user, - project: project, - commit_id: ci_pipeline.sha) + create( + :note_on_commit, + author: user, + project: project, + commit_id: ci_pipeline.sha + ) event = create(:event, :created, target: milestone, project: project, author: user, action: 5) create(:push_event_payload, event: event) diff --git a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb index 112b9cbb204..f658cfac0f5 100644 --- a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb +++ b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb @@ -15,8 +15,9 @@ RSpec.shared_examples 'snippets sort order' do context 'when no sort param is provided' do it 'calls SnippetsFinder with updated_at sort option' do - expect(SnippetsFinder).to receive(:new).with(user, - hash_including(sort: 'updated_desc')).and_call_original + expect(SnippetsFinder).to receive(:new) + .with(user, hash_including(sort: 'updated_desc')) + .and_call_original subject end @@ -27,8 +28,9 @@ RSpec.shared_examples 'snippets sort order' do let(:sort_argument) { { sort: order } } it 'calls SnippetsFinder with the given sort param' do - expect(SnippetsFinder).to receive(:new).with(user, - hash_including(sort: order)).and_call_original + expect(SnippetsFinder).to receive(:new) + .with(user, hash_including(sort: order)) + .and_call_original subject end diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index 768b54dc73e..32aa566c27e 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -399,11 +399,10 @@ RSpec.shared_examples 'wiki controller actions' do let(:id_param) { wiki_title } subject(:request) do - patch(:update, - params: routing_params.merge( - id: id_param, - wiki: { title: new_title, content: new_content } - )) + patch(:update, params: routing_params.merge( + id: id_param, + wiki: { title: new_title, content: new_content } + )) end it_behaves_like 'edit action' @@ -439,10 +438,9 @@ RSpec.shared_examples 'wiki controller actions' do let(:new_content) { 'New content' } subject(:request) do - post(:create, - params: routing_params.merge( - wiki: { title: new_title, content: new_content } - )) + post(:create, params: routing_params.merge( + wiki: { title: new_title, content: new_content } + )) end context 'when page is valid' do @@ -476,10 +474,9 @@ RSpec.shared_examples 'wiki controller actions' do let(:delete_user) { user } subject(:request) do - delete(:destroy, - params: routing_params.merge( - id: id_param - )) + delete(:destroy, params: routing_params.merge( + id: id_param + )) end before do diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb index 2bcbd5e5190..14e53dc8655 100644 --- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb @@ -83,9 +83,15 @@ RSpec.shared_examples 'an editable merge request' do fill_in 'merge_request_title', with: 'bug 345' fill_in 'merge_request_description', with: 'bug description' - click_button 'Save changes' - - expect(page).to have_content 'Someone edited the merge request the same time you did' + click_button _('Save changes') + + expect(page).to have_content( + format( + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + model_name: _('merge request'), + link_to_model: _('merge request') + ) + ) end it 'preserves description textarea height', :js do diff --git a/spec/support/shared_examples/features/milestone_editing_shared_examples.rb b/spec/support/shared_examples/features/milestone_editing_shared_examples.rb new file mode 100644 index 00000000000..d21bf62ecfa --- /dev/null +++ b/spec/support/shared_examples/features/milestone_editing_shared_examples.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'milestone handling version conflicts' do + it 'warns about version conflict when milestone has been updated in the background' do + # Update the milestone in the background in order to trigger a version conflict + milestone.update!(title: "New title") + + fill_in _('Title'), with: 'Title for version conflict' + fill_in _('Description'), with: 'Description for version conflict' + + click_button _('Save changes') + + expect(page).to have_content( + format( + _("Someone edited this %{model_name} at the same time you did. Please check out the %{link_to_model} and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength + model_name: _('milestone'), + link_to_model: _('milestone') + ) + ) + end +end diff --git a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb index 4d242d0e719..cbd0ffbab21 100644 --- a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb +++ b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb @@ -48,14 +48,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible issue' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Issue', id: unreadable.id, ability: :read_issue } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Issue', id: unreadable.id, ability: :read_issue } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -95,16 +99,18 @@ RSpec.shared_examples 'a redacted search results' do end let(:unredacted_results) do - ar_relation(Note, - readable_note_on_commit, - readable_diff_note, - readable_note_on_mr, - readable_diff_note_on_mr, - readable_note_on_project_snippet, - unreadable_note_on_commit, - unreadable_diff_note, - unreadable_note_on_mr, - unreadable_note_on_project_snippet) + ar_relation( + Note, + readable_note_on_commit, + readable_diff_note, + readable_note_on_mr, + readable_diff_note_on_mr, + readable_note_on_project_snippet, + unreadable_note_on_commit, + unreadable_diff_note, + unreadable_note_on_mr, + unreadable_note_on_project_snippet + ) end let(:scope) { 'notes' } @@ -112,23 +118,29 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible notes' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Note', id: unreadable_note_on_commit.id, ability: :read_note }, - { class_name: 'DiffNote', id: unreadable_diff_note.id, ability: :read_note }, - { class_name: 'DiscussionNote', id: unreadable_note_on_mr.id, ability: :read_note }, - { class_name: 'Note', id: unreadable_note_on_project_snippet.id, ability: :read_note } - ]))) - - expect(result).to contain_exactly(readable_note_on_commit, - readable_diff_note, - readable_note_on_mr, - readable_diff_note_on_mr, - readable_note_on_project_snippet) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Note', id: unreadable_note_on_commit.id, ability: :read_note }, + { class_name: 'DiffNote', id: unreadable_diff_note.id, ability: :read_note }, + { class_name: 'DiscussionNote', id: unreadable_note_on_mr.id, ability: :read_note }, + { class_name: 'Note', id: unreadable_note_on_project_snippet.id, ability: :read_note } + ] + ) + ) + ) + + expect(result).to contain_exactly( + readable_note_on_commit, + readable_diff_note, + readable_note_on_mr, + readable_diff_note_on_mr, + readable_note_on_project_snippet + ) end end @@ -141,14 +153,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible merge request' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'MergeRequest', id: unreadable.id, ability: :read_merge_request } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'MergeRequest', id: unreadable.id, ability: :read_merge_request } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -169,14 +185,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible blob' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Gitlab::Search::FoundBlob', id: unreadable.id, ability: :read_blob } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Gitlab::Search::FoundBlob', id: unreadable.id, ability: :read_blob } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -191,14 +211,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible blob' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Gitlab::Search::FoundWikiPage', id: unreadable.id, ability: :read_wiki_page } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Gitlab::Search::FoundWikiPage', id: unreadable.id, ability: :read_wiki_page } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -213,14 +237,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible snippet' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'ProjectSnippet', id: unreadable.id, ability: :read_snippet } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'ProjectSnippet', id: unreadable.id, ability: :read_snippet } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -239,14 +267,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible snippet' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'PersonalSnippet', id: unreadable.id, ability: :read_snippet } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'PersonalSnippet', id: unreadable.id, ability: :read_snippet } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end @@ -265,14 +297,18 @@ RSpec.shared_examples 'a redacted search results' do it 'redacts the inaccessible commit' do expect(search_service.send(:logger)) .to receive(:error) - .with(hash_including( - message: "redacted_search_results", - current_user_id: user.id, - query: search, - filtered: array_including( - [ - { class_name: 'Commit', id: unreadable.id, ability: :read_commit } - ]))) + .with( + hash_including( + message: "redacted_search_results", + current_user_id: user.id, + query: search, + filtered: array_including( + [ + { class_name: 'Commit', id: unreadable.id, ability: :read_commit } + ] + ) + ) + ) expect(result).to contain_exactly(readable) end diff --git a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb index a7c32932ba7..767caffd417 100644 --- a/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb @@ -9,9 +9,11 @@ RSpec.shared_examples 'User views a wiki page' do let(:path) { 'image.png' } let(:wiki_page) do - create(:wiki_page, - wiki: wiki, - title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})") + create( + :wiki_page, + wiki: wiki, + title: 'home', content: "Look at this [image](#{path})\n\n ![alt text](#{path})" + ) end before do diff --git a/spec/support/shared_examples/graphql/members_shared_examples.rb b/spec/support/shared_examples/graphql/members_shared_examples.rb index 5cba8baa829..5ab17f5a49d 100644 --- a/spec/support/shared_examples/graphql/members_shared_examples.rb +++ b/spec/support/shared_examples/graphql/members_shared_examples.rb @@ -39,8 +39,10 @@ RSpec.shared_examples 'querying members with a group' do let(:base_args) { { relations: described_class.arguments['relations'].default_value } } subject do - resolve(described_class, obj: resource, args: base_args.merge(args), - ctx: { current_user: user_4 }, arg_style: :internal) + resolve( + described_class, obj: resource, args: base_args.merge(args), + ctx: { current_user: user_4 }, arg_style: :internal + ) end describe '#resolve' do @@ -83,8 +85,10 @@ RSpec.shared_examples 'querying members with a group' do let_it_be(:other_user) { create(:user) } subject do - resolve(described_class, obj: resource, args: base_args.merge(args), - ctx: { current_user: other_user }, arg_style: :internal) + resolve( + described_class, obj: resource, args: base_args.merge(args), + ctx: { current_user: other_user }, arg_style: :internal + ) end it 'generates an error' do diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb index dc590e23ace..808fb097f29 100644 --- a/spec/support/shared_examples/graphql/mutation_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'a mutation that returns top-level errors' do |errors: []| expect(graphql_errors).to be_present - error_messages = graphql_errors.map { |e| e['message'] } + error_messages = graphql_errors.pluck('message') expect(error_messages).to match_errors end @@ -25,7 +25,7 @@ end # the mutation. RSpec.shared_examples 'a mutation that returns a top-level access error' do include_examples 'a mutation that returns top-level errors', - errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] end RSpec.shared_examples 'an invalid argument to the mutation' do |argument_name:| diff --git a/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb index 022e2308517..3b9dadf2e80 100644 --- a/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/set_assignees_shared_examples.rb @@ -16,10 +16,12 @@ RSpec.shared_examples 'an assignable resource' do let(:mode) { described_class.arguments['operationMode'].default_value } subject do - mutation.resolve(project_path: resource.project.full_path, - iid: resource.iid, - operation_mode: mode, - assignee_usernames: assignee_usernames) + mutation.resolve( + project_path: resource.project.full_path, + iid: resource.iid, + operation_mode: mode, + assignee_usernames: assignee_usernames + ) end it 'raises an error if the resource is not accessible to the user' do diff --git a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb index 09239ced73a..99d122e8254 100644 --- a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb +++ b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb @@ -4,9 +4,11 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do include GraphqlHelpers let(:note) do - create(:note, - noteable: noteable, - project: (noteable.project if noteable.respond_to?(:project))) + create( + :note, + noteable: noteable, + project: (noteable.project if noteable.respond_to?(:project)) + ) end let(:user) { note.author } diff --git a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb index f5c41416763..3ff52166990 100644 --- a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb @@ -18,7 +18,7 @@ RSpec.shared_examples 'returns repositories for allowed users' do |user_type, sc subject expect(json_response.length).to eq(2) - expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + expect(json_response.pluck('id')).to contain_exactly( root_repository.id, test_repository.id) expect(response.body).not_to include('tags') expect(response.body).not_to include('tags_count') @@ -47,7 +47,7 @@ RSpec.shared_examples 'returns tags for allowed users' do |user_type, scope| subject expect(json_response.length).to eq(2) - expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + expect(json_response.pluck('id')).to contain_exactly( root_repository.id, test_repository.id) expect(response.body).to include('tags') end diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb index e3ba51addaf..804221b7416 100644 --- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb @@ -14,7 +14,7 @@ RSpec.shared_examples 'custom attributes endpoints' do |attributable_name| get api("/#{attributable_name}", user), params: { custom_attributes: { foo: 'foo', bar: 'bar' } } expect(response).to have_gitlab_http_status(:ok) - expect(json_response.map { |r| r['id'] }).to include(attributable.id, other_attributable.id) + expect(json_response.pluck('id')).to include(attributable.id, other_attributable.id) end end diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb index f5835460a77..5e9dfc826d4 100644 --- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb @@ -279,11 +279,11 @@ RSpec.shared_examples 'group and project packages query' do end def npm_pipeline_ids - graphql_data_npm_package.dig('pipelines', 'nodes').map { |pipeline| pipeline['id'] } + graphql_data_npm_package.dig('pipelines', 'nodes').pluck('id') end def composer_pipeline_ids - graphql_data_composer_package.dig('pipelines', 'nodes').map { |pipeline| pipeline['id'] } + graphql_data_composer_package.dig('pipelines', 'nodes').pluck('id') end def graphql_data_npm_package diff --git a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb index b4019d7c232..161f4a02b8c 100644 --- a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb @@ -38,7 +38,7 @@ RSpec.shared_examples 'a package with files' do context 'with package files pending destruction' do let_it_be(:package_file_pending_destruction) { create(:package_file, :pending_destruction, package: package) } - let(:response_package_file_ids) { package_files_response.map { |pf| pf['id'] } } + let(:response_package_file_ids) { package_files_response.pluck('id') } it 'does not return them' do expect(package.reload.package_files).to include(package_file_pending_destruction) diff --git a/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb index 41d21490343..fba0533251a 100644 --- a/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb @@ -9,6 +9,6 @@ RSpec.shared_examples 'fetches labels' do expect(json_response).to be_an Array expect(json_response).to all(match_schema('public_api/v4/labels/label')) expect(json_response.size).to eq(expected_labels.size) - expect(json_response.map { |r| r['name'] }).to match_array(expected_labels) + expect(json_response.pluck('name')).to match_array(expected_labels) end end diff --git a/spec/support/shared_examples/requests/api/milestones_shared_examples.rb b/spec/support/shared_examples/requests/api/milestones_shared_examples.rb index 1ea11ba3d7c..ee7d0e86771 100644 --- a/spec/support/shared_examples/requests/api/milestones_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/milestones_shared_examples.rb @@ -52,7 +52,7 @@ RSpec.shared_examples 'group and project milestones' do |route_definition| expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Array expect(json_response.length).to eq(2) - expect(json_response.map { |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id]) + expect(json_response.pluck('id')).to match_array([closed_milestone.id, other_milestone.id]) end it 'does not return any milestone if none found' do @@ -293,7 +293,7 @@ RSpec.shared_examples 'group and project milestones' do |route_definition| expect(json_response).to be_an Array # 2 for projects, 3 for group(which has another project with an issue) expect(json_response.size).to be_between(2, 3) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id) + expect(json_response.pluck('id')).to include(issue.id, confidential_issue.id) end it 'does not return confidential issues to team members with guest role' do @@ -306,7 +306,7 @@ RSpec.shared_examples 'group and project milestones' do |route_definition| expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + expect(json_response.pluck('id')).to include(issue.id) end it 'does not return confidential issues to regular users' do @@ -316,7 +316,7 @@ RSpec.shared_examples 'group and project milestones' do |route_definition| expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) - expect(json_response.map { |issue| issue['id'] }).to include(issue.id) + expect(json_response.pluck('id')).to include(issue.id) end it 'returns issues ordered by label priority' do diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb index 0518749452e..b44ff952cdf 100644 --- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb @@ -14,7 +14,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'sorts by created_at in descending order by default' do get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user, admin_mode: user.admin?) - response_dates = json_response.map { |note| note['created_at'] } + response_dates = json_response.pluck('created_at') expect(json_response.length).to eq(4) expect(response_dates).to eq(response_dates.sort.reverse) @@ -42,7 +42,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'page breaks first page correctly' do get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?per_page=4", user, admin_mode: user.admin?) - response_ids = json_response.map { |note| note['id'] } + response_ids = json_response.pluck('id') expect(response_ids).to include(@note2.id) expect(response_ids).not_to include(@first_note.id) @@ -51,7 +51,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'page breaks second page correctly' do get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?per_page=4&page=2", user, admin_mode: user.admin?) - response_ids = json_response.map { |note| note['id'] } + response_ids = json_response.pluck('id') expect(response_ids).not_to include(@note2.id) expect(response_ids).to include(@first_note.id) @@ -62,7 +62,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'sorts by ascending order when requested' do get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?sort=asc", user, admin_mode: user.admin?) - response_dates = json_response.map { |note| note['created_at'] } + response_dates = json_response.pluck('created_at') expect(json_response.length).to eq(4) expect(response_dates).to eq(response_dates.sort) @@ -71,7 +71,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'sorts by updated_at in descending order when requested' do get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at", user, admin_mode: user.admin?) - response_dates = json_response.map { |note| note['updated_at'] } + response_dates = json_response.pluck('updated_at') expect(json_response.length).to eq(4) expect(response_dates).to eq(response_dates.sort.reverse) @@ -80,7 +80,7 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| it 'sorts by updated_at in ascending order when requested' do get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes?order_by=updated_at&sort=asc", user, admin_mode: user.admin?) - response_dates = json_response.map { |note| note['updated_at'] } + response_dates = json_response.pluck('updated_at') expect(json_response.length).to eq(4) expect(response_dates).to eq(response_dates.sort) diff --git a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb index 7df8d6a513d..3913d29e086 100644 --- a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb @@ -70,7 +70,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| get_container_repository_storage_moves - json_ids = json_response.map { |storage_move| storage_move['id'] } + json_ids = json_response.pluck('id') expect(json_ids).to eq([storage_move.id, storage_move_middle.id, storage_move_oldest.id]) end diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index f63693dbf26..7a4d7f81e96 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -76,7 +76,7 @@ RSpec.shared_examples 'returns packages' do |container_type, user_type| subject expect(json_response.length).to eq(2) - expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id) + expect(json_response.pluck('id')).to contain_exactly(package1.id, package2.id) end end end @@ -123,7 +123,7 @@ RSpec.shared_examples 'returns packages with subgroups' do |container_type, user subject expect(json_response.length).to eq(3) - expect(json_response.map { |package| package['id'] }).to contain_exactly(package1.id, package2.id, package3.id) + expect(json_response.pluck('id')).to contain_exactly(package1.id, package2.id, package3.id) end end end @@ -138,7 +138,7 @@ RSpec.shared_examples 'package sorting' do |order_by| it 'returns the sorted packages' do subject - expect(json_response.map { |package| package['id'] }).to eq(packages.map(&:id)) + expect(json_response.pluck('id')).to eq(packages.map(&:id)) end end @@ -148,7 +148,7 @@ RSpec.shared_examples 'package sorting' do |order_by| it 'returns the sorted packages' do subject - expect(json_response.map { |package| package['id'] }).to eq(packages.reverse.map(&:id)) + expect(json_response.pluck('id')).to eq(packages.reverse.map(&:id)) end end end @@ -225,7 +225,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false| subject expect(json_response.length).to eq(1) - expect(json_response.map { |package| package['package_type'] }).to contain_exactly(package_type) + expect(json_response.pluck('package_type')).to contain_exactly(package_type) end end end @@ -253,7 +253,7 @@ RSpec.shared_examples 'with versionless packages' do it 'does not return the package' do subject - expect(json_response.map { |package| package['id'] }).not_to include(versionless_package.id) + expect(json_response.pluck('id')).not_to include(versionless_package.id) end end @@ -268,7 +268,7 @@ RSpec.shared_examples 'with versionless packages' do it 'returns the package' do subject - expect(json_response.map { |package| package['id'] }).to include(versionless_package.id) + expect(json_response.pluck('id')).to include(versionless_package.id) end end end @@ -295,7 +295,7 @@ RSpec.shared_examples 'with status param' do it 'does not return the package' do subject - expect(json_response.map { |package| package['id'] }).not_to include(hidden_package.id) + expect(json_response.pluck('id')).not_to include(hidden_package.id) end end @@ -309,7 +309,7 @@ RSpec.shared_examples 'with status param' do it 'returns the package' do subject - expect(json_response.map { |package| package['id'] }).to include(hidden_package.id) + expect(json_response.pluck('id')).to include(hidden_package.id) end end end diff --git a/spec/workers/container_registry/cleanup_worker_spec.rb b/spec/workers/container_registry/cleanup_worker_spec.rb index 72e1243ccb5..955d2175085 100644 --- a/spec/workers/container_registry/cleanup_worker_spec.rb +++ b/spec/workers/container_registry/cleanup_worker_spec.rb @@ -46,6 +46,77 @@ RSpec.describe ContainerRegistry::CleanupWorker, :aggregate_failures, feature_ca end end + context 'with stale ongoing repair details' do + let_it_be(:stale_updated_at) { (described_class::STALE_REPAIR_DETAIL_THRESHOLD + 5.minutes).ago } + let_it_be(:recent_updated_at) { (described_class::STALE_REPAIR_DETAIL_THRESHOLD - 5.minutes).ago } + let_it_be(:old_repair_detail) { create(:container_registry_data_repair_detail, updated_at: stale_updated_at) } + let_it_be(:new_repair_detail) { create(:container_registry_data_repair_detail, updated_at: recent_updated_at) } + + it 'deletes them' do + expect { perform }.to change { ContainerRegistry::DataRepairDetail.count }.from(2).to(1) + expect(ContainerRegistry::DataRepairDetail.all).to contain_exactly(new_repair_detail) + end + end + + shared_examples 'does not enqueue record repair detail jobs' do + it 'does not enqueue record repair detail jobs' do + expect(ContainerRegistry::RecordDataRepairDetailWorker).not_to receive(:perform_with_capacity) + + perform + end + end + + context 'when on gitlab.com', :saas do + context 'when the gitlab api is supported' do + let(:relation) { instance_double(ActiveRecord::Relation) } + + before do + allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(true) + allow(Project).to receive(:pending_data_repair_analysis).and_return(relation) + end + + context 'when there are pending projects to analyze' do + before do + allow(relation).to receive(:exists?).and_return(true) + end + + it "enqueues record repair detail jobs" do + expect(ContainerRegistry::RecordDataRepairDetailWorker).to receive(:perform_with_capacity) + + perform + end + end + + context 'when there are no pending projects to analyze' do + before do + allow(relation).to receive(:exists?).and_return(false) + end + + it_behaves_like 'does not enqueue record repair detail jobs' + end + end + + context 'when the Gitlab API is not supported' do + before do + allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(false) + end + + it_behaves_like 'does not enqueue record repair detail jobs' + end + end + + context 'when not on Gitlab.com' do + it_behaves_like 'does not enqueue record repair detail jobs' + end + + context 'when registry_data_repair_worker feature is disabled' do + before do + stub_feature_flags(registry_data_repair_worker: false) + end + + it_behaves_like 'does not enqueue record repair detail jobs' + end + context 'for counts logging' do let_it_be(:delete_started_at) { (described_class::STALE_DELETE_THRESHOLD + 5.minutes).ago } let_it_be(:stale_delete_container_repository) do diff --git a/spec/workers/container_registry/record_data_repair_detail_worker_spec.rb b/spec/workers/container_registry/record_data_repair_detail_worker_spec.rb new file mode 100644 index 00000000000..f107144d397 --- /dev/null +++ b/spec/workers/container_registry/record_data_repair_detail_worker_spec.rb @@ -0,0 +1,191 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ContainerRegistry::RecordDataRepairDetailWorker, :aggregate_failures, :clean_gitlab_redis_shared_state, + feature_category: :container_registry do + include ExclusiveLeaseHelpers + + let(:worker) { described_class.new } + + describe '#perform_work' do + subject(:perform_work) { worker.perform_work } + + context 'with no work to do - no projects pending analysis' do + it 'will not try to get an exclusive lease and connect to the endpoint' do + allow(Project).to receive(:pending_data_repair_analysis).and_return([]) + expect(::Gitlab::ExclusiveLease).not_to receive(:new) + + expect(::ContainerRegistry::GitlabApiClient).not_to receive(:each_sub_repositories_with_tag_page) + + perform_work + end + end + + context 'with work to do' do + let_it_be(:path) { 'build/cng/docker-alpine' } + let_it_be(:group) { create(:group, path: 'build') } + let_it_be(:project) { create(:project, name: 'cng', namespace: group) } + + let_it_be(:container_repository) { create(:container_repository, project: project, name: "docker-alpine") } + let_it_be(:lease_key) { "container_registry_data_repair_detail_worker:#{project.id}" } + + before do + allow(ContainerRegistry::GitlabApiClient).to receive(:each_sub_repositories_with_tag_page) + allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(true) + end + + context 'when on Gitlab.com', :saas do + it 'obtains exclusive lease on the project' do + expect(Project).to receive(:pending_data_repair_analysis).and_call_original + expect_to_obtain_exclusive_lease("container_registry_data_repair_detail_worker:#{project.id}", + timeout: described_class::LEASE_TIMEOUT) + expect_to_cancel_exclusive_lease("container_registry_data_repair_detail_worker:#{project.id}", 'uuid') + + perform_work + end + + it 'queries how many are existing repositories and counts the missing ones' do + stub_exclusive_lease("container_registry_data_repair_detail_worker:#{project.id}", + timeout: described_class::LEASE_TIMEOUT) + allow(ContainerRegistry::GitlabApiClient).to receive(:each_sub_repositories_with_tag_page) + .with(path: project.full_path, page_size: 50).and_yield( + [ + { "path" => container_repository.path }, + { "path" => 'missing1/repository' }, + { "path" => 'missing2/repository' } + ] + ) + + expect(worker).not_to receive(:log_extra_metadata_on_done) + expect { perform_work }.to change { ContainerRegistry::DataRepairDetail.count }.from(0).to(1) + expect(ContainerRegistry::DataRepairDetail.first).to have_attributes(project: project, missing_count: 2) + end + + it 'logs invalid paths' do + stub_exclusive_lease("container_registry_data_repair_detail_worker:#{project.id}", + timeout: described_class::LEASE_TIMEOUT) + valid_path = ContainerRegistry::Path.new('valid/path') + invalid_path = ContainerRegistry::Path.new('invalid/path') + allow(valid_path).to receive(:valid?).and_return(true) + allow(invalid_path).to receive(:valid?).and_return(false) + + allow(ContainerRegistry::GitlabApiClient).to receive(:each_sub_repositories_with_tag_page) + .with(path: project.full_path, page_size: 50).and_yield( + [ + { "path" => valid_path.to_s }, + { "path" => invalid_path.to_s } + ] + ) + + allow(ContainerRegistry::Path).to receive(:new).with(valid_path.to_s).and_return(valid_path) + allow(ContainerRegistry::Path).to receive(:new).with(invalid_path.to_s).and_return(invalid_path) + + expect(worker).to receive(:log_extra_metadata_on_done).with( + :invalid_paths_parsed_in_container_repository_repair, + "invalid/path" + ) + perform_work + end + + it_behaves_like 'an idempotent worker' do + it 'creates a data repair detail' do + expect { perform_work }.to change { ContainerRegistry::DataRepairDetail.count }.from(0).to(1) + expect(project.container_registry_data_repair_detail).to be_present + end + end + + context 'when the lease cannot be obtained' do + before do + stub_exclusive_lease_taken(lease_key, timeout: described_class::LEASE_TIMEOUT) + end + + it 'logs an error and does not proceed' do + expect(worker).to receive(:log_lease_taken) + expect(ContainerRegistry::GitlabApiClient).not_to receive(:each_sub_repositories_with_tag_page) + + perform_work + end + + it 'does not create the data repair detail' do + perform_work + + expect(project.reload.container_registry_data_repair_detail).to be_nil + end + end + + context 'when an error occurs' do + before do + stub_exclusive_lease("container_registry_data_repair_detail_worker:#{project.id}", + timeout: described_class::LEASE_TIMEOUT) + allow(ContainerRegistry::GitlabApiClient).to receive(:each_sub_repositories_with_tag_page) + .with(path: project.full_path, page_size: 50).and_raise(RuntimeError) + end + + it 'logs the error' do + expect(::Gitlab::ErrorTracking).to receive(:log_exception) + .with(instance_of(RuntimeError), class: described_class.name) + + perform_work + end + + it 'sets the status of the repair detail to failed' do + expect { perform_work }.to change { ContainerRegistry::DataRepairDetail.failed.count }.from(0).to(1) + expect(project.reload.container_registry_data_repair_detail.failed?).to eq(true) + end + end + end + + context 'when not on Gitlab.com' do + it 'will not do anything' do + expect(::Gitlab::ExclusiveLease).not_to receive(:new) + expect(::ContainerRegistry::GitlabApiClient).not_to receive(:each_sub_repositories_with_tag_page) + + perform_work + end + end + end + end + + describe '#max_running_jobs' do + subject { worker.max_running_jobs } + + it { is_expected.to eq(described_class::MAX_CAPACITY) } + end + + describe '#remaining_work_count' do + let_it_be(:pending_projects) do + create_list(:project, described_class::MAX_CAPACITY + 2) + end + + subject { worker.remaining_work_count } + + context 'when on Gitlab.com', :saas do + before do + allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(true) + end + + it { is_expected.to eq(described_class::MAX_CAPACITY + 1) } + + context 'when the Gitlab API is not supported' do + before do + allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(false) + end + + it { is_expected.to eq(0) } + end + end + + context 'when not on Gitlab.com' do + it { is_expected.to eq(0) } + end + + context 'when registry_data_repair_worker feature is disabled' do + before do + stub_feature_flags(registry_data_repair_worker: false) + end + + it { is_expected.to eq(0) } + end + end +end diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index 4309ec24a9e..2c2cd7e9960 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -191,6 +191,7 @@ RSpec.describe 'Every Sidekiq worker', feature_category: :shared do 'Clusters::Cleanup::ServiceAccountWorker' => 3, 'ContainerExpirationPolicies::CleanupContainerRepositoryWorker' => 0, 'ContainerRegistry::DeleteContainerRepositoryWorker' => 0, + 'ContainerRegistry::RecordDataRepairDetailWorker' => 0, 'CreateCommitSignatureWorker' => 3, 'CreateGithubWebhookWorker' => 3, 'CreateNoteDiffFileWorker' => 3, |