diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-10 21:18:16 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-10 21:18:16 +0300 |
commit | e0277d5393d958865fdec470176ac5874edded06 (patch) | |
tree | f867094e393909ef822e354b1c72997ec5102f6f /spec | |
parent | 74d9798736a89f07e047698e5e32964829bf8859 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
18 files changed, 434 insertions, 87 deletions
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 88e60a5352a..2390687c3ea 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -2078,6 +2078,20 @@ RSpec.describe Projects::MergeRequestsController do end end + context 'when source branch is protected from force push' do + before do + create(:protected_branch, project: project, name: merge_request.source_branch, allow_force_push: false) + end + + it 'returns 404' do + expect_rebase_worker_for(user).never + + post_rebase + + expect(response).to have_gitlab_http_status(:not_found) + end + end + context 'with a forked project' do let(:forked_project) { fork_project(project, fork_owner, repository: true) } let(:fork_owner) { create(:user) } diff --git a/spec/frontend/pages/dashboard/todos/index/todos_spec.js b/spec/frontend/pages/dashboard/todos/index/todos_spec.js index 6a7ce80ec5a..ef295e7d1ba 100644 --- a/spec/frontend/pages/dashboard/todos/index/todos_spec.js +++ b/spec/frontend/pages/dashboard/todos/index/todos_spec.js @@ -1,5 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import $ from 'jquery'; +import waitForPromises from 'helpers/wait_for_promises'; import '~/lib/utils/common_utils'; import axios from '~/lib/utils/axios_utils'; import { addDelimiter } from '~/lib/utils/text_utility'; @@ -71,7 +72,7 @@ describe('Todos', () => { describe('on done todo click', () => { let onToggleSpy; - beforeEach((done) => { + beforeEach(() => { const el = document.querySelector('.js-done-todo'); const path = el.dataset.href; @@ -86,7 +87,7 @@ describe('Todos', () => { el.click(); // Wait for axios and HTML to udpate - setImmediate(done); + return waitForPromises(); }); it('dispatches todo:toggle', () => { diff --git a/spec/frontend/projects/project_find_file_spec.js b/spec/frontend/projects/project_find_file_spec.js index 9c1000039b1..eec54dd04bc 100644 --- a/spec/frontend/projects/project_find_file_spec.js +++ b/spec/frontend/projects/project_find_file_spec.js @@ -1,6 +1,7 @@ import MockAdapter from 'axios-mock-adapter'; import $ from 'jquery'; import { TEST_HOST } from 'helpers/test_constants'; +import waitForPromises from 'helpers/wait_for_promises'; import { sanitize } from '~/lib/dompurify'; import axios from '~/lib/utils/axios_utils'; import ProjectFindFile from '~/projects/project_find_file'; @@ -53,7 +54,7 @@ describe('ProjectFindFile', () => { { path: 'folde?rC/fil#F.txt', escaped: 'folde%3FrC/fil%23F.txt' }, ]; - beforeEach((done) => { + beforeEach(() => { // Create a mock adapter for stubbing axios API requests mock = new MockAdapter(axios); @@ -64,7 +65,7 @@ describe('ProjectFindFile', () => { ); getProjectFindFileInstance(); // This triggers a load / axios call + subsequent render in the constructor - setImmediate(done); + return waitForPromises(); }); afterEach(() => { diff --git a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js index a703dc0a66f..ee74e28ba23 100644 --- a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js +++ b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js @@ -1,4 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; import PANEL_STATE from '~/prometheus_metrics/constants'; import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics'; @@ -132,7 +133,7 @@ describe('PrometheusMetrics', () => { mock.restore(); }); - it('should show loader animation while response is being loaded and hide it when request is complete', (done) => { + it('should show loader animation while response is being loaded and hide it when request is complete', async () => { mockSuccess(); prometheusMetrics.loadActiveMetrics(); @@ -140,34 +141,31 @@ describe('PrometheusMetrics', () => { expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeFalsy(); expect(axios.get).toHaveBeenCalledWith(prometheusMetrics.activeMetricsEndpoint); - setImmediate(() => { - expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy(); - done(); - }); + await waitForPromises(); + + expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy(); }); - it('should show empty state if response failed to load', (done) => { + it('should show empty state if response failed to load', async () => { mockError(); prometheusMetrics.loadActiveMetrics(); - setImmediate(() => { - expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy(); - expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy(); - done(); - }); + await waitForPromises(); + + expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy(); + expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy(); }); - it('should populate metrics list once response is loaded', (done) => { + it('should populate metrics list once response is loaded', async () => { jest.spyOn(prometheusMetrics, 'populateActiveMetrics').mockImplementation(); mockSuccess(); prometheusMetrics.loadActiveMetrics(); - setImmediate(() => { - expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics); - done(); - }); + await waitForPromises(); + + expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics); }); }); }); diff --git a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js index 6116bc68927..5d80a221d8e 100644 --- a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js +++ b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js @@ -97,14 +97,14 @@ describe('SidebarSeverity', () => { }); }); - it('shows error alert when severity update fails ', () => { + it('shows error alert when severity update fails ', async () => { const errorMsg = 'Something went wrong'; jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValueOnce(errorMsg); findCriticalSeverityDropdownItem().vm.$emit('click'); - setImmediate(() => { - expect(createFlash).toHaveBeenCalled(); - }); + await waitForPromises(); + + expect(createFlash).toHaveBeenCalled(); }); it('shows loading icon while updating', async () => { diff --git a/spec/frontend/sidebar/sidebar_move_issue_spec.js b/spec/frontend/sidebar/sidebar_move_issue_spec.js index d9972ae75c3..7bb7b18adf8 100644 --- a/spec/frontend/sidebar/sidebar_move_issue_spec.js +++ b/spec/frontend/sidebar/sidebar_move_issue_spec.js @@ -1,5 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import $ from 'jquery'; +import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue'; @@ -77,15 +78,14 @@ describe('SidebarMoveIssue', () => { expect(test.sidebarMoveIssue.$dropdownToggle.data('deprecatedJQueryDropdown')).toBeTruthy(); }); - it('escapes html from project name', (done) => { + it('escapes html from project name', async () => { test.$toggleButton.dropdown('toggle'); - setImmediate(() => { - expect(test.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual( - '<img src=x onerror=alert(document.domain)> foo / bar', - ); - done(); - }); + await waitForPromises(); + + expect(test.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual( + '<img src=x onerror=alert(document.domain)> foo / bar', + ); }); }); @@ -101,20 +101,20 @@ describe('SidebarMoveIssue', () => { expect(test.$confirmButton.hasClass('is-loading')).toBe(true); }); - it('should remove loading state from confirm button on failure', (done) => { + it('should remove loading state from confirm button on failure', async () => { jest.spyOn(test.mediator, 'moveIssue').mockReturnValue(Promise.reject()); test.mediator.setMoveToProjectId(7); test.sidebarMoveIssue.onConfirmClicked(); expect(test.mediator.moveIssue).toHaveBeenCalled(); + // Wait for the move issue request to fail - setImmediate(() => { - expect(createFlash).toHaveBeenCalled(); - expect(test.$confirmButton.prop('disabled')).toBeFalsy(); - expect(test.$confirmButton.hasClass('is-loading')).toBe(false); - done(); - }); + await waitForPromises(); + + expect(createFlash).toHaveBeenCalled(); + expect(test.$confirmButton.prop('disabled')).toBeFalsy(); + expect(test.$confirmButton.hasClass('is-loading')).toBe(false); }); it('should not move the issue with id=0', () => { @@ -127,35 +127,33 @@ describe('SidebarMoveIssue', () => { }); }); - it('should set moveToProjectId on dropdown item "No project" click', (done) => { + it('should set moveToProjectId on dropdown item "No project" click', async () => { jest.spyOn(test.mediator, 'setMoveToProjectId').mockImplementation(() => {}); // Open the dropdown test.$toggleButton.dropdown('toggle'); // Wait for the autocomplete request to finish - setImmediate(() => { - test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click'); + await waitForPromises(); - expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0); - expect(test.$confirmButton.prop('disabled')).toBeTruthy(); - done(); - }); + test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click'); + + expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0); + expect(test.$confirmButton.prop('disabled')).toBeTruthy(); }); - it('should set moveToProjectId on dropdown item click', (done) => { + it('should set moveToProjectId on dropdown item click', async () => { jest.spyOn(test.mediator, 'setMoveToProjectId').mockImplementation(() => {}); // Open the dropdown test.$toggleButton.dropdown('toggle'); // Wait for the autocomplete request to finish - setImmediate(() => { - test.$content.find('.js-move-issue-dropdown-item').eq(1).trigger('click'); + await waitForPromises(); - expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(20); - expect(test.$confirmButton.attr('disabled')).toBe(undefined); - done(); - }); + test.$content.find('.js-move-issue-dropdown-item').eq(1).trigger('click'); + + expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(20); + expect(test.$confirmButton.attr('disabled')).toBe(undefined); }); }); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js index 45e9335abbe..c0a30a5093d 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js @@ -1,6 +1,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import Vue, { nextTick } from 'vue'; +import waitForPromises from 'helpers/wait_for_promises'; import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; @@ -152,23 +153,18 @@ describe('MemoryUsage', () => { }); describe('loadMetrics', () => { - const returnServicePromise = () => - new Promise((resolve) => { - resolve({ - data: metricsMockData, - }); + it('should load metrics data using MRWidgetService', async () => { + jest.spyOn(MRWidgetService, 'fetchMetrics').mockResolvedValue({ + data: metricsMockData, }); - - it('should load metrics data using MRWidgetService', (done) => { - jest.spyOn(MRWidgetService, 'fetchMetrics').mockReturnValue(returnServicePromise(true)); jest.spyOn(vm, 'computeGraphData').mockImplementation(() => {}); vm.loadMetrics(); - setImmediate(() => { - expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url); - expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time); - done(); - }); + + await waitForPromises(); + + expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url); + expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time); }); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js index 52a56af454f..7387ed2d5e9 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js @@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import { trimText } from 'helpers/text_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue'; import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; import eventHub from '~/vue_merge_request_widget/event_hub'; @@ -185,7 +186,7 @@ describe('MRWidgetAutoMergeEnabled', () => { describe('methods', () => { describe('cancelAutomaticMerge', () => { - it('should set flag and call service then tell main component to update the widget with data', (done) => { + it('should set flag and call service then tell main component to update the widget with data', async () => { factory({ ...defaultMrProps(), }); @@ -201,20 +202,20 @@ describe('MRWidgetAutoMergeEnabled', () => { ); wrapper.vm.cancelAutomaticMerge(); - setImmediate(() => { - expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy(); - if (mergeRequestWidgetGraphql) { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - } else { - expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - } - done(); - }); + + await waitForPromises(); + + expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy(); + if (mergeRequestWidgetGraphql) { + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + } else { + expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); + } }); }); describe('removeSourceBranch', () => { - it('should set flag and call service then request main component to update the widget', (done) => { + it('should set flag and call service then request main component to update the widget', async () => { factory({ ...defaultMrProps(), }); @@ -227,14 +228,14 @@ describe('MRWidgetAutoMergeEnabled', () => { ); wrapper.vm.removeSourceBranch(); - setImmediate(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); - expect(wrapper.vm.service.merge).toHaveBeenCalledWith({ - sha, - auto_merge_strategy: MWPS_MERGE_STRATEGY, - should_remove_source_branch: true, - }); - done(); + + await waitForPromises(); + + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + expect(wrapper.vm.service.merge).toHaveBeenCalledWith({ + sha, + auto_merge_strategy: MWPS_MERGE_STRATEGY, + should_remove_source_branch: true, }); }); }); diff --git a/spec/lib/gitlab/background_migration/populate_topics_non_private_projects_count_spec.rb b/spec/lib/gitlab/background_migration/populate_topics_non_private_projects_count_spec.rb new file mode 100644 index 00000000000..e72e3392210 --- /dev/null +++ b/spec/lib/gitlab/background_migration/populate_topics_non_private_projects_count_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::PopulateTopicsNonPrivateProjectsCount, schema: 20220125122640 do + it 'correctly populates the non private projects counters' do + namespaces = table(:namespaces) + projects = table(:projects) + topics = table(:topics) + project_topics = table(:project_topics) + + group = namespaces.create!(name: 'group', path: 'group') + project_public = projects.create!(namespace_id: group.id, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project_internal = projects.create!(namespace_id: group.id, visibility_level: Gitlab::VisibilityLevel::INTERNAL) + project_private = projects.create!(namespace_id: group.id, visibility_level: Gitlab::VisibilityLevel::PRIVATE) + topic_1 = topics.create!(name: 'Topic1') + topic_2 = topics.create!(name: 'Topic2') + topic_3 = topics.create!(name: 'Topic3') + topic_4 = topics.create!(name: 'Topic4') + topic_5 = topics.create!(name: 'Topic5') + topic_6 = topics.create!(name: 'Topic6') + topic_7 = topics.create!(name: 'Topic7') + topic_8 = topics.create!(name: 'Topic8') + + project_topics.create!(topic_id: topic_1.id, project_id: project_public.id) + project_topics.create!(topic_id: topic_2.id, project_id: project_internal.id) + project_topics.create!(topic_id: topic_3.id, project_id: project_private.id) + project_topics.create!(topic_id: topic_4.id, project_id: project_public.id) + project_topics.create!(topic_id: topic_4.id, project_id: project_internal.id) + project_topics.create!(topic_id: topic_5.id, project_id: project_public.id) + project_topics.create!(topic_id: topic_5.id, project_id: project_private.id) + project_topics.create!(topic_id: topic_6.id, project_id: project_internal.id) + project_topics.create!(topic_id: topic_6.id, project_id: project_private.id) + project_topics.create!(topic_id: topic_7.id, project_id: project_public.id) + project_topics.create!(topic_id: topic_7.id, project_id: project_internal.id) + project_topics.create!(topic_id: topic_7.id, project_id: project_private.id) + project_topics.create!(topic_id: topic_8.id, project_id: project_public.id) + + subject.perform(topic_1.id, topic_7.id) + + expect(topic_1.reload.non_private_projects_count).to eq(1) + expect(topic_2.reload.non_private_projects_count).to eq(1) + expect(topic_3.reload.non_private_projects_count).to eq(0) + expect(topic_4.reload.non_private_projects_count).to eq(2) + expect(topic_5.reload.non_private_projects_count).to eq(1) + expect(topic_6.reload.non_private_projects_count).to eq(1) + expect(topic_7.reload.non_private_projects_count).to eq(2) + expect(topic_8.reload.non_private_projects_count).to eq(0) + end +end diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb index 067b8b09516..a70ff0bd82d 100644 --- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb @@ -164,7 +164,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsImporter do expect(project.repository) .to receive(:fetch_remote) - .with(url, forced: false, refmap: Gitlab::GithubImport.refmap) + .with(url, forced: true, refmap: Gitlab::GithubImport.refmap) freeze_time do importer.update_repository diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index e1f3b36a2ba..e9a38755692 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1538,6 +1538,42 @@ RSpec.describe MergeRequest, factory_default: :keep do end end + describe '#permits_force_push?' do + let_it_be(:merge_request) { build_stubbed(:merge_request) } + + subject { merge_request.permits_force_push? } + + context 'when source branch is not protected' do + before do + allow(ProtectedBranch).to receive(:protected?).and_return(false) + end + + it { is_expected.to be_truthy } + end + + context 'when source branch is protected' do + before do + allow(ProtectedBranch).to receive(:protected?).and_return(true) + end + + context 'when force push is not allowed' do + before do + allow(ProtectedBranch).to receive(:allow_force_push?) { false } + end + + it { is_expected.to be_falsey } + end + + context 'when force push is allowed' do + before do + allow(ProtectedBranch).to receive(:allow_force_push?) { true } + end + + it { is_expected.to be_truthy } + end + end + end + describe '#can_remove_source_branch?' do let_it_be(:user) { create(:user) } let_it_be(:merge_request, reload: true) { create(:merge_request, :simple) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 35d0566d9b4..f343e164d9b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -7418,6 +7418,67 @@ RSpec.describe Project, factory_default: :keep do expect(project.reload.topics.map(&:name)).to eq(%w[topic1 topic2 topic3]) end end + + context 'public topics counter' do + let_it_be(:topic_1) { create(:topic, name: 't1') } + let_it_be(:topic_2) { create(:topic, name: 't2') } + let_it_be(:topic_3) { create(:topic, name: 't3') } + + let(:private) { Gitlab::VisibilityLevel::PRIVATE } + let(:internal) { Gitlab::VisibilityLevel::INTERNAL } + let(:public) { Gitlab::VisibilityLevel::PUBLIC } + + subject do + project_updates = { + visibility_level: new_visibility, + topic_list: new_topic_list + }.compact + + project.update!(project_updates) + end + + using RSpec::Parameterized::TableSyntax + + # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + where(:initial_visibility, :new_visibility, :new_topic_list, :expected_count_changes) do + ref(:private) | nil | 't2, t3' | [0, 0, 0] + ref(:internal) | nil | 't2, t3' | [-1, 0, 1] + ref(:public) | nil | 't2, t3' | [-1, 0, 1] + ref(:private) | ref(:public) | nil | [1, 1, 0] + ref(:private) | ref(:internal) | nil | [1, 1, 0] + ref(:private) | ref(:private) | nil | [0, 0, 0] + ref(:internal) | ref(:public) | nil | [0, 0, 0] + ref(:internal) | ref(:internal) | nil | [0, 0, 0] + ref(:internal) | ref(:private) | nil | [-1, -1, 0] + ref(:public) | ref(:public) | nil | [0, 0, 0] + ref(:public) | ref(:internal) | nil | [0, 0, 0] + ref(:public) | ref(:private) | nil | [-1, -1, 0] + ref(:private) | ref(:public) | 't2, t3' | [0, 1, 1] + ref(:private) | ref(:internal) | 't2, t3' | [0, 1, 1] + ref(:private) | ref(:private) | 't2, t3' | [0, 0, 0] + ref(:internal) | ref(:public) | 't2, t3' | [-1, 0, 1] + ref(:internal) | ref(:internal) | 't2, t3' | [-1, 0, 1] + ref(:internal) | ref(:private) | 't2, t3' | [-1, -1, 0] + ref(:public) | ref(:public) | 't2, t3' | [-1, 0, 1] + ref(:public) | ref(:internal) | 't2, t3' | [-1, 0, 1] + ref(:public) | ref(:private) | 't2, t3' | [-1, -1, 0] + end + # rubocop:enable Lint/BinaryOperatorWithIdenticalOperands + + with_them do + it 'increments or decrements counters of topics' do + project.reload.update!( + visibility_level: initial_visibility, + topic_list: [topic_1.name, topic_2.name] + ) + + expect { subject } + .to change { topic_1.reload.non_private_projects_count }.by(expected_count_changes[0]) + .and change { topic_2.reload.non_private_projects_count }.by(expected_count_changes[1]) + .and change { topic_3.reload.non_private_projects_count }.by(expected_count_changes[2]) + end + end + end end shared_examples 'all_runners' do diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 2a266ff5d19..dcf5c0151eb 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -3344,6 +3344,18 @@ RSpec.describe API::MergeRequests do end end + context 'when merge request branch does not allow force push' do + before do + create(:protected_branch, project: project, name: merge_request.source_branch, allow_force_push: false) + end + + it 'returns 403' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/rebase", user) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + it 'returns 403 if the user cannot push to the branch' do guest = create(:user) project.add_guest(guest) diff --git a/spec/services/merge_requests/bulk_remove_attention_requested_service_spec.rb b/spec/services/merge_requests/bulk_remove_attention_requested_service_spec.rb index fe4ce0dab5e..ae8846974ce 100644 --- a/spec/services/merge_requests/bulk_remove_attention_requested_service_spec.rb +++ b/spec/services/merge_requests/bulk_remove_attention_requested_service_spec.rb @@ -10,7 +10,7 @@ RSpec.describe MergeRequests::BulkRemoveAttentionRequestedService do let(:reviewer) { merge_request.find_reviewer(user) } let(:assignee) { merge_request.find_assignee(assignee_user) } let(:project) { merge_request.project } - let(:service) { described_class.new(project: project, current_user: current_user, merge_request: merge_request) } + let(:service) { described_class.new(project: project, current_user: current_user, merge_request: merge_request, users: [user, assignee_user]) } let(:result) { service.execute } before do @@ -20,7 +20,7 @@ RSpec.describe MergeRequests::BulkRemoveAttentionRequestedService do describe '#execute' do context 'invalid permissions' do - let(:service) { described_class.new(project: project, current_user: create(:user), merge_request: merge_request) } + let(:service) { described_class.new(project: project, current_user: create(:user), merge_request: merge_request, users: [user]) } it 'returns an error' do expect(result[:status]).to eq :error diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index e3a6a60393d..bd0896bf0a8 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -701,6 +701,27 @@ RSpec.describe QuickActions::InterpretService do end end + shared_examples 'attention command' do + it 'updates reviewers attention status' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Requested attention from #{developer.to_reference}.") + + reviewer.reload + + expect(reviewer).to be_attention_requested + end + end + + shared_examples 'remove attention command' do + it 'updates reviewers attention status' do + _, _, message = service.execute(content, issuable) + + expect(message).to eq("Removed attention from #{developer.to_reference}.") + expect(reviewer).not_to be_attention_requested + end + end + it_behaves_like 'reopen command' do let(:content) { '/reopen' } let(:issuable) { issue } @@ -2283,6 +2304,82 @@ RSpec.describe QuickActions::InterpretService do expect(message).to eq('One or more contacts were successfully removed.') end end + + describe 'attention command' do + let(:issuable) { create(:merge_request, reviewers: [developer], source_project: project) } + let(:reviewer) { issuable.merge_request_reviewers.find_by(user_id: developer.id) } + let(:content) { "/attention @#{developer.username}" } + + context 'with one user' do + before do + reviewer.update!(state: :reviewed) + end + + it_behaves_like 'attention command' + end + + context 'with no user' do + let(:content) { "/attention" } + + it_behaves_like 'failed command', 'Failed to request attention because no user was found.' + end + + context 'with incorrect permissions' do + let(:service) { described_class.new(project, create(:user)) } + + it_behaves_like 'failed command', 'Could not apply attention command.' + end + + context 'with feature flag disabled' do + before do + stub_feature_flags(mr_attention_requests: false) + end + + it_behaves_like 'failed command', 'Could not apply attention command.' + end + + context 'with an issue instead of a merge request' do + let(:issuable) { issue } + + it_behaves_like 'failed command', 'Could not apply attention command.' + end + end + + describe 'remove attention command' do + let(:issuable) { create(:merge_request, reviewers: [developer], source_project: project) } + let(:reviewer) { issuable.merge_request_reviewers.find_by(user_id: developer.id) } + let(:content) { "/remove_attention @#{developer.username}" } + + context 'with one user' do + it_behaves_like 'remove attention command' + end + + context 'with no user' do + let(:content) { "/remove_attention" } + + it_behaves_like 'failed command', 'Failed to remove attention because no user was found.' + end + + context 'with incorrect permissions' do + let(:service) { described_class.new(project, create(:user)) } + + it_behaves_like 'failed command', 'Could not apply remove_attention command.' + end + + context 'with feature flag disabled' do + before do + stub_feature_flags(mr_attention_requests: false) + end + + it_behaves_like 'failed command', 'Could not apply remove_attention command.' + end + + context 'with an issue instead of a merge request' do + let(:issuable) { issue } + + it_behaves_like 'failed command', 'Could not apply remove_attention command.' + end + end end describe '#explain' do diff --git a/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb index 28decb4011d..2258bdd2c79 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb @@ -73,6 +73,16 @@ RSpec.shared_examples 'rebase quick action' do expect(page).to have_content 'This merge request cannot be rebased while there are conflicts.' end end + + context 'when the merge request branch is protected from force push' do + let!(:protected_branch) { create(:protected_branch, project: project, name: merge_request.source_branch, allow_force_push: false) } + + it 'does not rebase the MR' do + add_note("/rebase") + + expect(page).to have_content 'This merge request branch is protected from force push.' + end + end end context 'when the current user cannot rebase the MR' do diff --git a/spec/workers/container_registry/migration/observer_worker_spec.rb b/spec/workers/container_registry/migration/observer_worker_spec.rb new file mode 100644 index 00000000000..fec6640d7ec --- /dev/null +++ b/spec/workers/container_registry/migration/observer_worker_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ContainerRegistry::Migration::ObserverWorker, :aggregate_failures do + let(:worker) { described_class.new } + + describe '#perform' do + subject { worker.perform } + + context 'when the migration feature flag is disabled' do + before do + stub_feature_flags(container_registry_migration_phase2_enabled: false) + end + + it 'does nothing' do + expect(worker).not_to receive(:log_extra_metadata_on_done) + + subject + end + end + + context 'when the migration is enabled' do + before do + create_list(:container_repository, 3) + create(:container_repository, :pre_importing) + create(:container_repository, :pre_import_done) + create_list(:container_repository, 2, :importing) + create(:container_repository, :import_aborted) + # batch_count is not allowed within a transaction but + # all rspec tests run inside of a transaction. + # This mocks the false positive. + allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false) # rubocop:disable Database/MultipleDatabases + end + + it 'logs all the counts' do + expect(worker).to receive(:log_extra_metadata_on_done).with(:default_count, 3) + expect(worker).to receive(:log_extra_metadata_on_done).with(:pre_importing_count, 1) + expect(worker).to receive(:log_extra_metadata_on_done).with(:pre_import_done_count, 1) + expect(worker).to receive(:log_extra_metadata_on_done).with(:importing_count, 2) + expect(worker).to receive(:log_extra_metadata_on_done).with(:import_done_count, 0) + expect(worker).to receive(:log_extra_metadata_on_done).with(:import_aborted_count, 1) + expect(worker).to receive(:log_extra_metadata_on_done).with(:import_skipped_count, 0) + + subject + end + + context 'with load balancing enabled', :db_load_balancing do + it 'uses the replica' do + expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_replicas_for_read_queries).and_call_original + + subject + end + end + end + end +end diff --git a/spec/workers/projects/git_garbage_collect_worker_spec.rb b/spec/workers/projects/git_garbage_collect_worker_spec.rb index 7b54d7df4b2..ae567107443 100644 --- a/spec/workers/projects/git_garbage_collect_worker_spec.rb +++ b/spec/workers/projects/git_garbage_collect_worker_spec.rb @@ -32,6 +32,21 @@ RSpec.describe Projects::GitGarbageCollectWorker do subject.perform(*params) end + + context 'when deduplication service runs into a GRPC internal error' do + before do + allow_next_instance_of(::Projects::GitDeduplicationService) do |instance| + expect(instance).to receive(:execute).and_raise(GRPC::Internal) + end + end + + it_behaves_like 'can collect git garbage' do + let(:resource) { project } + let(:statistics_service_klass) { Projects::UpdateStatisticsService } + let(:statistics_keys) { [:repository_size, :lfs_objects_size] } + let(:expected_default_lease) { "projects:#{resource.id}" } + end + end end context 'LFS object garbage collection' do |