From 7f73b108d44ebb58d2eddcbc98808bafc94d1b11 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 14 Sep 2023 12:09:48 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/features/dashboard/shortcuts_spec.rb | 1 + spec/features/explore/navbar_spec.rb | 1 + .../explore/user_explores_projects_spec.rb | 4 + spec/features/nav/pinned_nav_items_spec.rb | 18 +- .../search/user_searches_for_projects_spec.rb | 2 +- spec/features/signed_commits_spec.rb | 8 +- spec/features/snippets/show_spec.rb | 4 + spec/features/unsubscribe_links_spec.rb | 4 + spec/features/users/rss_spec.rb | 2 + spec/features/users/show_spec.rb | 4 + spec/features/users/snippets_spec.rb | 2 +- .../user_browses_projects_on_user_page_spec.rb | 2 +- spec/features/whats_new_spec.rb | 4 + .../merge_requests/components/compare_app_spec.js | 54 +- .../components/custom_email_form_spec.js | 13 + .../components/custom_email_wrapper_spec.js | 21 +- .../super_sidebar/components/nav_item_spec.js | 4 + .../bitbucket/representation/pull_request_spec.rb | 5 + spec/lib/click_house/record_sync_context_spec.rb | 32 + spec/lib/click_house/sync_cursor_spec.rb | 35 + spec/lib/gitlab/bitbucket_import/importer_spec.rb | 22 +- spec/lib/gitlab/database_spec.rb | 37 ++ .../cycle_analytics/runtime_limiter_spec.rb | 55 ++ spec/models/review_spec.rb | 19 + spec/requests/api/commit_statuses_spec.rb | 740 ++++++++++----------- spec/requests/api/internal/base_spec.rb | 15 +- spec/spec_helper.rb | 5 - .../bulk_imports/pipeline_batch_worker_spec.rb | 10 + .../workers/click_house/events_sync_worker_spec.rb | 145 +++- 29 files changed, 829 insertions(+), 439 deletions(-) create mode 100644 spec/lib/click_house/record_sync_context_spec.rb create mode 100644 spec/lib/click_house/sync_cursor_spec.rb create mode 100644 spec/models/analytics/cycle_analytics/runtime_limiter_spec.rb (limited to 'spec') diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index 976dcc5a027..c8013d364e3 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -50,6 +50,7 @@ RSpec.describe 'Dashboard shortcuts', :js, feature_category: :shared do context 'logged out' do before do + stub_feature_flags(super_sidebar_logged_out: false) visit explore_root_path end diff --git a/spec/features/explore/navbar_spec.rb b/spec/features/explore/navbar_spec.rb index 8f281abe6a7..853d66ed4d1 100644 --- a/spec/features/explore/navbar_spec.rb +++ b/spec/features/explore/navbar_spec.rb @@ -7,6 +7,7 @@ RSpec.describe '"Explore" navbar', feature_category: :navigation do it_behaves_like 'verified navigation bar' do before do + stub_feature_flags(super_sidebar_logged_out: false) visit explore_projects_path end end diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb index f259ba6a167..43d464e0c9f 100644 --- a/spec/features/explore/user_explores_projects_spec.rb +++ b/spec/features/explore/user_explores_projects_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' RSpec.describe 'User explores projects', feature_category: :user_profile do + before do + stub_feature_flags(super_sidebar_logged_out: false) + end + describe '"All" tab' do it_behaves_like 'an "Explore" page with sidebar and breadcrumbs', :explore_projects_path, :projects end diff --git a/spec/features/nav/pinned_nav_items_spec.rb b/spec/features/nav/pinned_nav_items_spec.rb index cf53e0a322a..1a3ac973ed4 100644 --- a/spec/features/nav/pinned_nav_items_spec.rb +++ b/spec/features/nav/pinned_nav_items_spec.rb @@ -168,17 +168,19 @@ RSpec.describe 'Navigation menu item pinning', :js, feature_category: :navigatio private - def add_pin(menu_item_title) - menu_item = find("[data-testid=\"nav-item-link\"]", text: menu_item_title) - menu_item.hover - menu_item.find("[data-testid=\"thumbtack-icon\"]").click + def add_pin(nav_item_title) + nav_item = find("[data-testid=\"nav-item\"]", text: nav_item_title) + nav_item.hover + pin_button = nav_item.find("[data-testid=\"nav-item-pin\"]") + pin_button.click wait_for_requests end - def remove_pin(menu_item_title) - menu_item = find("[data-testid=\"nav-item-link\"]", text: menu_item_title) - menu_item.hover - menu_item.find("[data-testid=\"thumbtack-solid-icon\"]").click + def remove_pin(nav_item_title) + nav_item = find("[data-testid=\"nav-item\"]", text: nav_item_title) + nav_item.hover + unpin_button = nav_item.find("[data-testid=\"nav-item-unpin\"]") + unpin_button.click wait_for_requests end diff --git a/spec/features/search/user_searches_for_projects_spec.rb b/spec/features/search/user_searches_for_projects_spec.rb index 48a94161927..51e5ad85e2b 100644 --- a/spec/features/search/user_searches_for_projects_spec.rb +++ b/spec/features/search/user_searches_for_projects_spec.rb @@ -8,7 +8,7 @@ RSpec.describe 'User searches for projects', :js, :disable_rate_limiter, feature context 'when signed out' do context 'when block_anonymous_global_searches is disabled' do before do - stub_feature_flags(block_anonymous_global_searches: false) + stub_feature_flags(block_anonymous_global_searches: false, super_sidebar_logged_out: false) end include_examples 'top right search form' diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb index 0268c8ad0d4..08d2d0575eb 100644 --- a/spec/features/signed_commits_spec.rb +++ b/spec/features/signed_commits_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe 'GPG signed commits', feature_category: :source_code_management do - let(:project) { create(:project, :public, :repository) } +RSpec.describe 'GPG signed commits', :js, feature_category: :source_code_management do + let_it_be(:project) { create(:project, :public, :repository) } it 'changes from unverified to verified when the user changes their email to match the gpg key', :sidekiq_might_not_need_inline do ref = GpgHelpers::SIGNED_AND_AUTHORED_SHA @@ -47,7 +47,7 @@ RSpec.describe 'GPG signed commits', feature_category: :source_code_management d expect(page).to have_selector('.gl-badge', text: 'Verified') end - context 'shows popover badges', :js do + context 'shows popover badges' do let(:user_1) do create :user, email: GpgHelpers::User1.emails.first, username: 'nannie.bernhard', name: 'Nannie Bernhard' end @@ -163,7 +163,7 @@ RSpec.describe 'GPG signed commits', feature_category: :source_code_management d end end - context 'view signed commit on the tree view', :js do + context 'view signed commit on the tree view' do shared_examples 'a commit with a signature' do before do visit project_tree_path(project, 'signed-commits') diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb index a354499fa0c..bbb120edb80 100644 --- a/spec/features/snippets/show_spec.rb +++ b/spec/features/snippets/show_spec.rb @@ -6,6 +6,10 @@ RSpec.describe 'Snippet', :js, feature_category: :source_code_management do let_it_be(:user) { create(:user, :no_super_sidebar) } let_it_be(:snippet) { create(:personal_snippet, :public, :repository, author: user) } + before do + stub_feature_flags(super_sidebar_logged_out: false) + end + it_behaves_like 'show and render proper snippet blob' do let(:anchor) { nil } diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb index 01da9063f52..b78efa65888 100644 --- a/spec/features/unsubscribe_links_spec.rb +++ b/spec/features/unsubscribe_links_spec.rb @@ -22,6 +22,10 @@ RSpec.describe 'Unsubscribe links', :sidekiq_inline, feature_category: :shared d end context 'when logged out' do + before do + stub_feature_flags(super_sidebar_logged_out: false) + end + context 'when visiting the link from the body' do it 'shows the unsubscribe confirmation page and redirects to root path when confirming' do visit body_link diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb index 712e5056138..99451ac472d 100644 --- a/spec/features/users/rss_spec.rb +++ b/spec/features/users/rss_spec.rb @@ -22,6 +22,7 @@ RSpec.describe 'User RSS', feature_category: :user_profile do context 'when signed out' do before do + stub_feature_flags(super_sidebar_logged_out: false) visit path end @@ -45,6 +46,7 @@ RSpec.describe 'User RSS', feature_category: :user_profile do context 'when signed out' do before do + stub_feature_flags(super_sidebar_logged_out: false) visit path end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index f8653b22377..522eb12f507 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -7,6 +7,10 @@ RSpec.describe 'User page', feature_category: :user_profile do let_it_be(:user) { create(:user, bio: 'Lorem ipsum dolor sit amet') } + before do + stub_feature_flags(super_sidebar_logged_out: false) + end + subject(:visit_profile) { visit(user_path(user)) } context 'with "user_profile_overflow_menu_vue" feature flag enabled', :js do diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb index 6473753470b..98ac9fa5f92 100644 --- a/spec/features/users/snippets_spec.rb +++ b/spec/features/users/snippets_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'Snippets tab on a user profile', :js, feature_category: :source_ let(:user) { create(:user, :no_super_sidebar) } before do - stub_feature_flags(profile_tabs_vue: false) + stub_feature_flags(profile_tabs_vue: false, super_sidebar_logged_out: false) end context 'pagination' do diff --git a/spec/features/users/user_browses_projects_on_user_page_spec.rb b/spec/features/users/user_browses_projects_on_user_page_spec.rb index fb2a80c61e0..5e047192e7b 100644 --- a/spec/features/users/user_browses_projects_on_user_page_spec.rb +++ b/spec/features/users/user_browses_projects_on_user_page_spec.rb @@ -29,7 +29,7 @@ RSpec.describe 'Users > User browses projects on user page', :js, feature_catego end before do - stub_feature_flags(profile_tabs_vue: false) + stub_feature_flags(profile_tabs_vue: false, super_sidebar_logged_out: false) end it 'hides loading spinner after load', :js do diff --git a/spec/features/whats_new_spec.rb b/spec/features/whats_new_spec.rb index 16a3deae7f3..c8bcf5f6ef0 100644 --- a/spec/features/whats_new_spec.rb +++ b/spec/features/whats_new_spec.rb @@ -6,6 +6,10 @@ RSpec.describe "renders a `whats new` dropdown item", feature_category: :onboard let_it_be(:user) { create(:user, :no_super_sidebar) } context 'when not logged in' do + before do + stub_feature_flags(super_sidebar_logged_out: false) + end + it 'and on SaaS it renders', :saas do visit user_path(user) diff --git a/spec/frontend/merge_requests/components/compare_app_spec.js b/spec/frontend/merge_requests/components/compare_app_spec.js index ba129363ffd..887f79f9fad 100644 --- a/spec/frontend/merge_requests/components/compare_app_spec.js +++ b/spec/frontend/merge_requests/components/compare_app_spec.js @@ -1,10 +1,14 @@ -import { shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import axios from '~/lib/utils/axios_utils'; import CompareApp from '~/merge_requests/components/compare_app.vue'; let wrapper; +let mock; function factory(provideData = {}) { - wrapper = shallowMount(CompareApp, { + wrapper = shallowMountExtended(CompareApp, { provide: { inputs: { project: { @@ -16,6 +20,7 @@ function factory(provideData = {}) { name: 'branch', }, }, + branchCommitPath: '/commit', toggleClass: { project: 'project', branch: 'branch', @@ -29,7 +34,18 @@ function factory(provideData = {}) { }); } +const findCommitBox = () => wrapper.findByTestId('commit-box'); + describe('Merge requests compare app component', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onGet('/commit').reply(200, 'commit content'); + }); + + afterEach(() => { + mock.restore(); + }); + it('shows commit box when selected branch is empty', () => { factory({ currentBranch: { @@ -38,9 +54,41 @@ describe('Merge requests compare app component', () => { }, }); - const commitBox = wrapper.find('[data-testid="commit-box"]'); + const commitBox = findCommitBox(); expect(commitBox.exists()).toBe(true); expect(commitBox.text()).toBe('Select a branch to compare'); }); + + it('emits select-branch on selected event', () => { + factory({ + currentBranch: { + text: '', + value: '', + }, + }); + + wrapper.findByTestId('compare-dropdown').vm.$emit('selected', { value: 'main' }); + + expect(wrapper.emitted('select-branch')).toEqual([['main']]); + }); + + describe('currentBranch watcher', () => { + it('changes selected value', async () => { + factory({ + currentBranch: { + text: '', + value: '', + }, + }); + + expect(findCommitBox().text()).toBe('Select a branch to compare'); + + wrapper.setProps({ currentBranch: { text: 'main', value: 'main ' } }); + + await waitForPromises(); + + expect(findCommitBox().text()).toBe('commit content'); + }); + }); }); diff --git a/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js b/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js index ded8b181c4e..9b012995ea4 100644 --- a/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js +++ b/spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js @@ -1,6 +1,8 @@ import { mount } from '@vue/test-utils'; +import { GlLink } from '@gitlab/ui'; import { nextTick } from 'vue'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { helpPagePath } from '~/helpers/help_page_helper'; import CustomEmailForm from '~/projects/settings_service_desk/components/custom_email_form.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import { I18N_FORM_FORWARDING_CLIPBOARD_BUTTON_TITLE } from '~/projects/settings_service_desk/custom_email_constants'; @@ -15,6 +17,7 @@ describe('CustomEmailForm', () => { const findForm = () => wrapper.find('form'); const findClipboardButton = () => wrapper.findComponent(ClipboardButton); + const findLink = () => wrapper.findComponent(GlLink); const findInputByTestId = (testId) => wrapper.findByTestId(testId).find('input'); const findCustomEmailInput = () => findInputByTestId('form-custom-email'); const findSmtpAddressInput = () => findInputByTestId('form-smtp-address'); @@ -35,6 +38,16 @@ describe('CustomEmailForm', () => { wrapper = extendedWrapper(mount(CustomEmailForm, { propsData: { ...defaultProps, ...props } })); }; + it('displays help page link', () => { + createWrapper(); + + expect(findLink().attributes('href')).toBe( + helpPagePath('user/project/service_desk/configure.html', { + anchor: 'custom-email-address', + }), + ); + }); + it('renders a copy to clipboard button', () => { createWrapper(); diff --git a/spec/frontend/projects/settings_service_desk/components/custom_email_wrapper_spec.js b/spec/frontend/projects/settings_service_desk/components/custom_email_wrapper_spec.js index e54d09cf82f..174e05ceeee 100644 --- a/spec/frontend/projects/settings_service_desk/components/custom_email_wrapper_spec.js +++ b/spec/frontend/projects/settings_service_desk/components/custom_email_wrapper_spec.js @@ -1,7 +1,8 @@ import { nextTick } from 'vue'; -import { GlLink, GlLoadingIcon, GlAlert } from '@gitlab/ui'; +import { GlLoadingIcon, GlAlert } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import axios from '~/lib/utils/axios_utils'; import waitForPromises from 'helpers/wait_for_promises'; import { HTTP_STATUS_OK, HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status'; @@ -40,19 +41,21 @@ describe('CustomEmailWrapper', () => { const showToast = jest.fn(); const createWrapper = (props = {}) => { - wrapper = mount(CustomEmailWrapper, { - propsData: { ...defaultProps, ...props }, - mocks: { - $toast: { - show: showToast, + wrapper = extendedWrapper( + mount(CustomEmailWrapper, { + propsData: { ...defaultProps, ...props }, + mocks: { + $toast: { + show: showToast, + }, }, - }, - }); + }), + ); }; const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findAlert = () => wrapper.findComponent(GlAlert); - const findFeedbackLink = () => wrapper.findComponent(GlLink); + const findFeedbackLink = () => wrapper.findByTestId('feedback-link'); const findCustomEmailForm = () => wrapper.findComponent(CustomEmailForm); const findCustomEmail = () => wrapper.findComponent(CustomEmail); const findCustomEmailConfirmModal = () => wrapper.findComponent(CustomEmailConfirmModal); diff --git a/spec/frontend/super_sidebar/components/nav_item_spec.js b/spec/frontend/super_sidebar/components/nav_item_spec.js index 25c7082bbf3..89d774c4b43 100644 --- a/spec/frontend/super_sidebar/components/nav_item_spec.js +++ b/spec/frontend/super_sidebar/components/nav_item_spec.js @@ -90,6 +90,10 @@ describe('NavItem component', () => { expect(findPinButton().exists()).toBe(true); }); + it('contains an aria-label', () => { + expect(findPinButton().attributes('aria-label')).toBe('Pin Foo'); + }); + it('toggles pointer events on after CSS fade-in', async () => { const pinButton = findPinButton(); diff --git a/spec/lib/bitbucket/representation/pull_request_spec.rb b/spec/lib/bitbucket/representation/pull_request_spec.rb index f39222805d0..544dd498aea 100644 --- a/spec/lib/bitbucket/representation/pull_request_spec.rb +++ b/spec/lib/bitbucket/representation/pull_request_spec.rb @@ -56,4 +56,9 @@ RSpec.describe Bitbucket::Representation::PullRequest, feature_category: :import describe '#updated_at' do it { expect(described_class.new('updated_on' => '2023-01-01').updated_at).to eq('2023-01-01') } end + + describe '#merge_commit_sha' do + it { expect(described_class.new('merge_commit' => { 'hash' => 'SHA' }).merge_commit_sha).to eq('SHA') } + it { expect(described_class.new({}).merge_commit_sha).to be_nil } + end end diff --git a/spec/lib/click_house/record_sync_context_spec.rb b/spec/lib/click_house/record_sync_context_spec.rb new file mode 100644 index 00000000000..7873796cd9c --- /dev/null +++ b/spec/lib/click_house/record_sync_context_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ClickHouse::RecordSyncContext, feature_category: :value_stream_management do + let(:records) { [Issue.new(id: 1), Issue.new(id: 2), Issue.new(id: 3), Issue.new(id: 4)] } + + subject(:sync_context) { described_class.new(last_record_id: 0, max_records_per_batch: 3) } + + it 'allows processing 3 records per batch' do + records.take(3).each do |record| + sync_context.last_processed_id = record.id + end + + expect(sync_context).to be_record_limit_reached + expect(sync_context.last_processed_id).to eq(3) + + expect { sync_context.new_batch! }.to change { sync_context.record_count_in_current_batch }.from(3).to(0) + + expect(sync_context).not_to be_record_limit_reached + + records.take(3).each do |record| + sync_context.last_processed_id = record.id + end + + expect(sync_context).to be_record_limit_reached + end + + it 'sets the no more records flag' do + expect { sync_context.no_more_records! }.to change { sync_context.no_more_records? }.from(false).to(true) + end +end diff --git a/spec/lib/click_house/sync_cursor_spec.rb b/spec/lib/click_house/sync_cursor_spec.rb new file mode 100644 index 00000000000..43ffaa76e1d --- /dev/null +++ b/spec/lib/click_house/sync_cursor_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ClickHouse::SyncCursor, feature_category: :value_stream_management, click_house: {} do + def value + ClickHouse::SyncCursor.cursor_for(:my_table) + end + + context 'when cursor is empty' do + it 'returns the default value: 0' do + expect(value).to eq(0) + end + end + + context 'when cursor is present' do + it 'updates and returns the current cursor value' do + described_class.update_cursor_for(:my_table, 1111) + + expect(value).to eq(1111) + + described_class.update_cursor_for(:my_table, 2222) + + expect(value).to eq(2222) + end + end + + context 'when updating a different cursor' do + it 'does not affect the other cursors' do + described_class.update_cursor_for(:other_table, 1111) + + expect(value).to eq(0) + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 4c94ecfe745..9786e7a364e 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -92,6 +92,7 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea describe '#import_pull_requests' do let(:source_branch_sha) { sample.commits.last } + let(:merge_commit_sha) { sample.commits.second } let(:target_branch_sha) { sample.commits.first } let(:pull_request) do instance_double( @@ -101,6 +102,7 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea source_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.source_branch, target_branch_sha: target_branch_sha, target_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.target_branch, + merge_commit_sha: merge_commit_sha, title: 'This is a title', description: 'This is a test pull request', state: 'merged', @@ -217,17 +219,29 @@ RSpec.describe Gitlab::BitbucketImport::Importer, :clean_gitlab_redis_cache, fea end end - context "when branches' sha is not found in the repository" do + context 'when source SHA is not found in the repository' do let(:source_branch_sha) { 'a' * Commit::MIN_SHA_LENGTH } - let(:target_branch_sha) { 'b' * Commit::MIN_SHA_LENGTH } + let(:target_branch_sha) { 'c' * Commit::MIN_SHA_LENGTH } - it 'uses the pull request sha references' do + it 'uses merge commit SHA for source' do expect { subject.execute }.to change { MergeRequest.count }.by(1) merge_request_diff = MergeRequest.first.merge_request_diff - expect(merge_request_diff.head_commit_sha).to eq source_branch_sha + expect(merge_request_diff.head_commit_sha).to eq merge_commit_sha expect(merge_request_diff.start_commit_sha).to eq target_branch_sha end + + context 'when the merge commit SHA is also not found' do + let(:merge_commit_sha) { 'b' * Commit::MIN_SHA_LENGTH } + + it 'uses the pull request sha references' do + expect { subject.execute }.to change { MergeRequest.count }.by(1) + + merge_request_diff = MergeRequest.first.merge_request_diff + expect(merge_request_diff.head_commit_sha).to eq source_branch_sha + expect(merge_request_diff.start_commit_sha).to eq target_branch_sha + end + end end context "when target_branch_sha is blank" do diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 0d8fa4dad6d..5af8f8c7bdd 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -251,6 +251,43 @@ RSpec.describe Gitlab::Database, feature_category: :database do end end + describe '.check_single_connection_and_print_warning' do + subject { described_class.check_single_connection_and_print_warning } + + it 'prints a warning if single connection' do + allow(described_class).to receive(:database_mode).and_return(::Gitlab::Database::MODE_SINGLE_DATABASE) + + expect(Kernel).to receive(:warn).with(/Your database has a single connection/) + + subject + end + + it 'does not print a warning if single ci connection' do + allow(described_class).to receive(:database_mode).and_return(::Gitlab::Database::MODE_SINGLE_DATABASE_CI_CONNECTION) + + expect(Kernel).not_to receive(:warn) + + subject + end + + it 'does not print a warning if multiple connection' do + allow(described_class).to receive(:database_mode).and_return(::Gitlab::Database::MODE_MULTIPLE_DATABASES) + + expect(Kernel).not_to receive(:warn) + + subject + end + + it 'does not print a warning in Rails runner environment' do + allow(described_class).to receive(:database_mode).and_return(::Gitlab::Database::MODE_SINGLE_DATABASE) + allow(Gitlab::Runtime).to receive(:rails_runner?).and_return(true) + + expect(Kernel).not_to receive(:warn) + + subject + end + end + describe '.db_config_for_connection' do context 'when the regular connection is used' do it 'returns db_config' do diff --git a/spec/models/analytics/cycle_analytics/runtime_limiter_spec.rb b/spec/models/analytics/cycle_analytics/runtime_limiter_spec.rb new file mode 100644 index 00000000000..1a5200719c8 --- /dev/null +++ b/spec/models/analytics/cycle_analytics/runtime_limiter_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Analytics::CycleAnalytics::RuntimeLimiter, feature_category: :value_stream_management do + let(:max_runtime) { 321 } + let(:runtime_limiter) { described_class.new(max_runtime) } + + describe '#elapsed_time' do + it 'reports monotonic elapsed time since instantiation' do + elapsed = 123 + first_monotonic_time = 100 + second_monotonic_time = first_monotonic_time + elapsed + + expect(Gitlab::Metrics::System).to receive(:monotonic_time) + .and_return(first_monotonic_time, second_monotonic_time) + + expect(runtime_limiter.elapsed_time).to eq(elapsed) + end + end + + describe '#over_time?' do + it 'returns true if over time' do + start_time = 100 + allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(start_time, start_time + max_runtime - 1) + + expect(runtime_limiter.over_time?).to be(false) + + allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(start_time + max_runtime) + expect(runtime_limiter.over_time?).to be(true) + + allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(start_time + max_runtime + 1) + expect(runtime_limiter.over_time?).to be(true) + end + end + + describe '#was_over_time?' do + it 'returns true if over_time? returned true at an earlier step' do + first_monotonic_time = 10 + second_monotonic_time = first_monotonic_time + 50 + third_monotonic_time = second_monotonic_time + 50 # over time: 110 > 100 + + expect(Gitlab::Metrics::System).to receive(:monotonic_time) + .and_return(first_monotonic_time, second_monotonic_time, third_monotonic_time) + + runtime_limiter = described_class.new(100) + + expect(runtime_limiter.over_time?).to be(false) # uses the second_monotonic_time + expect(runtime_limiter.was_over_time?).to be(false) + + expect(runtime_limiter.over_time?).to be(true) # uses the third_monotonic_time + expect(runtime_limiter.was_over_time?).to be(true) + end + end +end diff --git a/spec/models/review_spec.rb b/spec/models/review_spec.rb index 2683dc93a4b..75cdea5bca7 100644 --- a/spec/models/review_spec.rb +++ b/spec/models/review_spec.rb @@ -41,4 +41,23 @@ RSpec.describe Review do expect(review.participants).to include(review.author) end end + + describe '#from_merge_request_author?' do + let(:merge_request) { build_stubbed(:merge_request) } + let(:review) { build_stubbed(:review, merge_request: merge_request, author: author) } + + subject(:from_merge_request_author?) { review.from_merge_request_author? } + + context 'when review author is the merge request author' do + let(:author) { merge_request.author } + + it { is_expected.to eq(true) } + end + + context 'when review author is not the merge request author' do + let(:author) { build_stubbed(:user) } + + it { is_expected.to eq(false) } + end + end end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index b7377a45dc9..9247d9366b2 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -135,471 +135,463 @@ RSpec.describe API::CommitStatuses, :clean_gitlab_redis_cache, feature_category: end end - [true, false].each do |flag| - context "when feature flag ci_commit_statuses_api_exclusive_lock is set to #{flag}" do - before do - stub_feature_flags(ci_commit_statuses_api_exclusive_lock: flag) - end - - describe 'POST /projects/:id/statuses/:sha' do - let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" } - - context 'developer user' do - context 'uses only required parameters' do - valid_statues = %w[pending running success failed canceled] - valid_statues.each do |status| - context "for #{status}" do - context 'when pipeline for sha does not exists' do - it 'creates commit status and sets pipeline iid' do - post api(post_url, developer), params: { state: status } - - expect(response).to have_gitlab_http_status(:created) - expect(json_response['sha']).to eq(commit.id) - expect(json_response['status']).to eq(status) - expect(json_response['name']).to eq('default') - expect(json_response['ref']).not_to be_empty - expect(json_response['target_url']).to be_nil - expect(json_response['description']).to be_nil - expect(json_response['pipeline_id']).not_to be_nil - - if status == 'failed' - expect(CommitStatus.find(json_response['id'])).to be_api_failure - end - - expect(::Ci::Pipeline.last.iid).not_to be_nil - end - end - end - end - - context 'when pipeline already exists for the specified sha' do - let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: 'ref') } - let(:params) { { state: 'pending' } } - - shared_examples_for 'creates a commit status for the existing pipeline with an external stage' do - it do - expect do - post api(post_url, developer), params: params - end.not_to change { Ci::Pipeline.count } - - job = pipeline.statuses.find_by_name(json_response['name']) - - expect(response).to have_gitlab_http_status(:created) - expect(job.ci_stage.name).to eq('external') - expect(job.ci_stage.position).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) - expect(job.ci_stage.pipeline).to eq(pipeline) - expect(job.status).to eq('pending') - expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) - end - end - - shared_examples_for 'updates the commit status with an external stage' do - before do - post api(post_url, developer), params: { state: 'pending' } - end - - it 'updates the commit status with the external stage' do - post api(post_url, developer), params: { state: 'running' } - job = pipeline.statuses.find_by_name(json_response['name']) + describe 'POST /projects/:id/statuses/:sha' do + let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" } + + context 'developer user' do + context 'uses only required parameters' do + valid_statues = %w[pending running success failed canceled] + valid_statues.each do |status| + context "for #{status}" do + context 'when pipeline for sha does not exists' do + it 'creates commit status and sets pipeline iid' do + post api(post_url, developer), params: { state: status } - expect(job.ci_stage.name).to eq('external') - expect(job.ci_stage.position).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) - expect(job.ci_stage.pipeline).to eq(pipeline) - expect(job.status).to eq('running') - expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) - end - end - - context 'with pipeline for merge request' do - let!(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project) } - let!(:pipeline) { merge_request.all_pipelines.last } - let(:sha) { pipeline.sha } - - it_behaves_like 'creates a commit status for the existing pipeline with an external stage' - end - - context 'when an external stage does not exist' do - context 'when the commit status does not exist' do - it_behaves_like 'creates a commit status for the existing pipeline with an external stage' + expect(response).to have_gitlab_http_status(:created) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq(status) + expect(json_response['name']).to eq('default') + expect(json_response['ref']).not_to be_empty + expect(json_response['target_url']).to be_nil + expect(json_response['description']).to be_nil + expect(json_response['pipeline_id']).not_to be_nil + + if status == 'failed' + expect(CommitStatus.find(json_response['id'])).to be_api_failure end - context 'when the commit status exists' do - it_behaves_like 'updates the commit status with an external stage' - end + expect(::Ci::Pipeline.last.iid).not_to be_nil end + end + end + end - context 'when an external stage already exists' do - let(:stage) { create(:ci_stage, name: 'external', pipeline: pipeline, position: 1_000_000) } + context 'when pipeline already exists for the specified sha' do + let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: 'ref') } + let(:params) { { state: 'pending' } } - context 'when the commit status exists' do - it_behaves_like 'updates the commit status with an external stage' - end + shared_examples_for 'creates a commit status for the existing pipeline with an external stage' do + it do + expect do + post api(post_url, developer), params: params + end.not_to change { Ci::Pipeline.count } - context 'when the commit status does not exist' do - it_behaves_like 'creates a commit status for the existing pipeline with an external stage' - end - end - end + job = pipeline.statuses.find_by_name(json_response['name']) - context 'when the pipeline does not exist' do - it 'creates a commit status and a stage' do - expect do - post api(post_url, developer), params: { state: 'pending' } - end.to change { Ci::Pipeline.count }.by(1) - job = Ci::Pipeline.last.statuses.find_by_name(json_response['name']) - - expect(job.ci_stage.name).to eq('external') - expect(job.ci_stage.position).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) - expect(job.status).to eq('pending') - expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) - end + expect(response).to have_gitlab_http_status(:created) + expect(job.ci_stage.name).to eq('external') + expect(job.ci_stage.position).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) + expect(job.ci_stage.pipeline).to eq(pipeline) + expect(job.status).to eq('pending') + expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) end end - context 'when status transitions from pending' do + shared_examples_for 'updates the commit status with an external stage' do before do post api(post_url, developer), params: { state: 'pending' } end - valid_statues = %w[running success failed canceled] - valid_statues.each do |status| - it "to #{status}" do - expect { post api(post_url, developer), params: { state: status } }.not_to change { CommitStatus.count } + it 'updates the commit status with the external stage' do + post api(post_url, developer), params: { state: 'running' } + job = pipeline.statuses.find_by_name(json_response['name']) - expect(response).to have_gitlab_http_status(:created) - expect(json_response['status']).to eq(status) - end + expect(job.ci_stage.name).to eq('external') + expect(job.ci_stage.position).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) + expect(job.ci_stage.pipeline).to eq(pipeline) + expect(job.status).to eq('running') + expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) end end - context 'with all optional parameters' do - context 'when creating a commit status' do - subject do - post api(post_url, developer), params: { - state: 'success', - context: 'coverage', - ref: 'master', - description: 'test', - coverage: 80.0, - target_url: 'http://gitlab.com/status' - } - end + context 'with pipeline for merge request' do + let!(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project) } + let!(:pipeline) { merge_request.all_pipelines.last } + let(:sha) { pipeline.sha } - it 'creates commit status' do - subject + it_behaves_like 'creates a commit status for the existing pipeline with an external stage' + end - expect(response).to have_gitlab_http_status(:created) - expect(json_response['sha']).to eq(commit.id) - expect(json_response['status']).to eq('success') - expect(json_response['name']).to eq('coverage') - expect(json_response['ref']).to eq('master') - expect(json_response['coverage']).to eq(80.0) - expect(json_response['description']).to eq('test') - expect(json_response['target_url']).to eq('http://gitlab.com/status') - end + context 'when an external stage does not exist' do + context 'when the commit status does not exist' do + it_behaves_like 'creates a commit status for the existing pipeline with an external stage' + end - context 'when merge request exists for given branch' do - let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'develop') } + context 'when the commit status exists' do + it_behaves_like 'updates the commit status with an external stage' + end + end - it 'sets head pipeline' do - subject + context 'when an external stage already exists' do + let(:stage) { create(:ci_stage, name: 'external', pipeline: pipeline, position: 1_000_000) } - expect(response).to have_gitlab_http_status(:created) - expect(merge_request.reload.head_pipeline).not_to be_nil - end - end + context 'when the commit status exists' do + it_behaves_like 'updates the commit status with an external stage' end - context 'when updating a commit status' do - let(:parameters) do - { - state: 'success', - name: 'coverage', - ref: 'master' - } - end - - let(:updatable_optional_attributes) do - { - description: 'new description', - coverage: 90.0 - } - end + context 'when the commit status does not exist' do + it_behaves_like 'creates a commit status for the existing pipeline with an external stage' + end + end + end - # creating the initial commit status - before do - post api(post_url, developer), params: { - state: 'running', - context: 'coverage', - ref: 'master', - description: 'coverage test', - coverage: 10.0, - target_url: 'http://gitlab.com/status' - } - end + context 'when the pipeline does not exist' do + it 'creates a commit status and a stage' do + expect do + post api(post_url, developer), params: { state: 'pending' } + end.to change { Ci::Pipeline.count }.by(1) + job = Ci::Pipeline.last.statuses.find_by_name(json_response['name']) - subject(:send_request) do - post api(post_url, developer), params: { - **parameters, - **updatable_optional_attributes - } - end + expect(job.ci_stage.name).to eq('external') + expect(job.ci_stage.position).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) + expect(job.status).to eq('pending') + expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) + end + end + end - it 'updates a commit status' do - send_request + context 'when status transitions from pending' do + before do + post api(post_url, developer), params: { state: 'pending' } + end - expect(response).to have_gitlab_http_status(:created) - expect(json_response['sha']).to eq(commit.id) - expect(json_response['status']).to eq('success') - expect(json_response['name']).to eq('coverage') - expect(json_response['ref']).to eq('master') - expect(json_response['coverage']).to eq(90.0) - expect(json_response['description']).to eq('new description') - expect(json_response['target_url']).to eq('http://gitlab.com/status') - end + valid_statues = %w[running success failed canceled] + valid_statues.each do |status| + it "to #{status}" do + expect { post api(post_url, developer), params: { state: status } }.not_to change { CommitStatus.count } - it 'does not create a new commit status' do - expect { send_request }.not_to change { CommitStatus.count } - end + expect(response).to have_gitlab_http_status(:created) + expect(json_response['status']).to eq(status) + end + end + end - context 'when the `state` parameter is sent the same' do - let(:parameters) do - { - state: 'running', - name: 'coverage', - ref: 'master' - } - end + context 'with all optional parameters' do + context 'when creating a commit status' do + subject do + post api(post_url, developer), params: { + state: 'success', + context: 'coverage', + ref: 'master', + description: 'test', + coverage: 80.0, + target_url: 'http://gitlab.com/status' + } + end - it 'does not update the commit status' do - send_request + it 'creates commit status' do + subject + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq('success') + expect(json_response['name']).to eq('coverage') + expect(json_response['ref']).to eq('master') + expect(json_response['coverage']).to eq(80.0) + expect(json_response['description']).to eq('test') + expect(json_response['target_url']).to eq('http://gitlab.com/status') + end - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq("Cannot transition status via :run from :running (Reason(s): Status cannot transition via \"run\")") + context 'when merge request exists for given branch' do + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'develop') } - commit_status = project.commit_statuses.find_by!(name: 'coverage') + it 'sets head pipeline' do + subject - expect(commit_status.description).to eq('coverage test') - expect(commit_status.coverage).to eq(10.0) - end - end + expect(response).to have_gitlab_http_status(:created) + expect(merge_request.reload.head_pipeline).not_to be_nil end + end + end - context 'when a pipeline id is specified' do - let!(:first_pipeline) do - project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'master', status: 'created').tap do |p| - p.ensure_project_iid! # Necessary to avoid cross-database modification error - p.save! - end - end + context 'when updating a commit status' do + let(:parameters) do + { + state: 'success', + name: 'coverage', + ref: 'master' + } + end - let!(:other_pipeline) do - project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'master', status: 'created').tap do |p| - p.ensure_project_iid! # Necessary to avoid cross-database modification error - p.save! - end - end + let(:updatable_optional_attributes) do + { + description: 'new description', + coverage: 90.0 + } + end - subject do - post api(post_url, developer), params: { - pipeline_id: other_pipeline.id, - state: 'success', - ref: 'master' - } - end + # creating the initial commit status + before do + post api(post_url, developer), params: { + state: 'running', + context: 'coverage', + ref: 'master', + description: 'coverage test', + coverage: 10.0, + target_url: 'http://gitlab.com/status' + } + end - it 'update the correct pipeline', :sidekiq_might_not_need_inline do - subject + subject(:send_request) do + post api(post_url, developer), params: { + **parameters, + **updatable_optional_attributes + } + end - expect(first_pipeline.reload.status).to eq('created') - expect(other_pipeline.reload.status).to eq('success') - end - end + it 'updates a commit status' do + send_request + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq('success') + expect(json_response['name']).to eq('coverage') + expect(json_response['ref']).to eq('master') + expect(json_response['coverage']).to eq(90.0) + expect(json_response['description']).to eq('new description') + expect(json_response['target_url']).to eq('http://gitlab.com/status') end - context 'when retrying a commit status' do - subject(:post_request) do - post api(post_url, developer), - params: { state: 'failed', name: 'test', ref: 'master' } + it 'does not create a new commit status' do + expect { send_request }.not_to change { CommitStatus.count } + end - post api(post_url, developer), - params: { state: 'success', name: 'test', ref: 'master' } + context 'when the `state` parameter is sent the same' do + let(:parameters) do + { + state: 'running', + name: 'coverage', + ref: 'master' + } end - it 'correctly posts a new commit status' do - post_request + it 'does not update the commit status' do + send_request - expect(response).to have_gitlab_http_status(:created) - expect(json_response['sha']).to eq(commit.id) - expect(json_response['status']).to eq('success') - end + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq("Cannot transition status via :run from :running (Reason(s): Status cannot transition via \"run\")") - it 'retries the commit status', :sidekiq_might_not_need_inline do - post_request + commit_status = project.commit_statuses.find_by!(name: 'coverage') - expect(CommitStatus.count).to eq 2 - expect(CommitStatus.first).to be_retried - expect(CommitStatus.last.pipeline).to be_success + expect(commit_status.description).to eq('coverage test') + expect(commit_status.coverage).to eq(10.0) end end + end - context 'when status is invalid' do - before do - post api(post_url, developer), params: { state: 'invalid' } + context 'when a pipeline id is specified' do + let!(:first_pipeline) do + project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'master', status: 'created').tap do |p| + p.ensure_project_iid! # Necessary to avoid cross-database modification error + p.save! end + end - it 'does not create commit status' do - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq(nil) + let!(:other_pipeline) do + project.ci_pipelines.build(source: :push, sha: commit.id, ref: 'master', status: 'created').tap do |p| + p.ensure_project_iid! # Necessary to avoid cross-database modification error + p.save! end end - context 'when request without a state made' do - before do - post api(post_url, developer) - end + subject do + post api(post_url, developer), params: { + pipeline_id: other_pipeline.id, + state: 'success', + ref: 'master' + } + end - it 'does not create commit status' do - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq(nil) - end + it 'update the correct pipeline', :sidekiq_might_not_need_inline do + subject + + expect(first_pipeline.reload.status).to eq('created') + expect(other_pipeline.reload.status).to eq('success') end + end + end - context 'when updating a protected ref' do - before do - create(:protected_branch, project: project, name: 'master') - post api(post_url, user), params: { state: 'running', ref: 'master' } - end + context 'when retrying a commit status' do + subject(:post_request) do + post api(post_url, developer), + params: { state: 'failed', name: 'test', ref: 'master' } - context 'with user as developer' do - let(:user) { developer } + post api(post_url, developer), + params: { state: 'success', name: 'test', ref: 'master' } + end - it 'does not create commit status' do - expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('403 Forbidden') - end - end + it 'correctly posts a new commit status' do + post_request - context 'with user as maintainer' do - let(:user) { create_user(:maintainer) } + expect(response).to have_gitlab_http_status(:created) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq('success') + end - it 'creates commit status' do - expect(response).to have_gitlab_http_status(:created) - end - end - end + it 'retries the commit status', :sidekiq_might_not_need_inline do + post_request - context 'when commit SHA is invalid' do - let(:sha) { 'invalid_sha' } + expect(CommitStatus.count).to eq 2 + expect(CommitStatus.first).to be_retried + expect(CommitStatus.last.pipeline).to be_success + end + end - before do - post api(post_url, developer), params: { state: 'running' } - end + context 'when status is invalid' do + before do + post api(post_url, developer), params: { state: 'invalid' } + end - it 'returns not found error' do - expect(response).to have_gitlab_http_status(:not_found) - expect(json_response['message']).to eq('404 Commit Not Found') - end - end + it 'does not create commit status' do + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq(nil) + end + end - context 'when target URL is an invalid address' do - before do - post api(post_url, developer), params: { - state: 'pending', - target_url: 'invalid url' - } - end + context 'when request without a state made' do + before do + post api(post_url, developer) + end - it 'responds with bad request status and validation errors' do - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['target_url']) - .to include 'is blocked: Only allowed schemes are http, https' - end - end + it 'does not create commit status' do + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq(nil) + end + end - context 'when target URL is an unsupported scheme' do - before do - post api(post_url, developer), params: { - state: 'pending', - target_url: 'git://example.com' - } - end + context 'when updating a protected ref' do + before do + create(:protected_branch, project: project, name: 'master') + post api(post_url, user), params: { state: 'running', ref: 'master' } + end - it 'responds with bad request status and validation errors' do - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['target_url']) - .to include 'is blocked: Only allowed schemes are http, https' - end - end + context 'with user as developer' do + let(:user) { developer } - context 'when trying to update a status of a different type' do - let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: 'ref') } - let!(:ci_build) { create(:ci_build, pipeline: pipeline, name: 'test-job') } - let(:params) { { state: 'pending', name: 'test-job' } } + it 'does not create commit status' do + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('403 Forbidden') + end + end - before do - post api(post_url, developer), params: params - end + context 'with user as maintainer' do + let(:user) { create_user(:maintainer) } - it 'responds with bad request status and validation errors' do - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['name']) - .to include 'has already been taken' - end + it 'creates commit status' do + expect(response).to have_gitlab_http_status(:created) end + end + end - context 'with partitions', :ci_partitionable do - let(:current_partition_id) { ci_testing_partition_id } + context 'when commit SHA is invalid' do + let(:sha) { 'invalid_sha' } - before do - allow(Ci::Pipeline) - .to receive(:current_partition_value) { current_partition_id } - end + before do + post api(post_url, developer), params: { state: 'running' } + end - it 'creates records in the current partition' do - expect { post api(post_url, developer), params: { state: 'running' } } - .to change(CommitStatus, :count).by(1) - .and change(Ci::Pipeline, :count).by(1) + it 'returns not found error' do + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 Commit Not Found') + end + end - status = CommitStatus.find(json_response['id']) + context 'when target URL is an invalid address' do + before do + post api(post_url, developer), params: { + state: 'pending', + target_url: 'invalid url' + } + end - expect(status.partition_id).to eq(current_partition_id) - expect(status.pipeline.partition_id).to eq(current_partition_id) - end - end + it 'responds with bad request status and validation errors' do + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']['target_url']) + .to include 'is blocked: Only allowed schemes are http, https' end + end - context 'reporter user' do - before do - post api(post_url, reporter), params: { state: 'running' } - end + context 'when target URL is an unsupported scheme' do + before do + post api(post_url, developer), params: { + state: 'pending', + target_url: 'git://example.com' + } + end - it 'does not create commit status' do - expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('403 Forbidden') - end + it 'responds with bad request status and validation errors' do + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']['target_url']) + .to include 'is blocked: Only allowed schemes are http, https' end + end - context 'guest user' do - before do - post api(post_url, guest), params: { state: 'running' } - end + context 'when trying to update a status of a different type' do + let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: 'ref') } + let!(:ci_build) { create(:ci_build, pipeline: pipeline, name: 'test-job') } + let(:params) { { state: 'pending', name: 'test-job' } } - it 'does not create commit status' do - expect(response).to have_gitlab_http_status(:forbidden) - expect(json_response['message']).to eq('403 Forbidden') - end + before do + post api(post_url, developer), params: params end - context 'unauthorized user' do - before do - post api(post_url) - end + it 'responds with bad request status and validation errors' do + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']['name']) + .to include 'has already been taken' + end + end - it 'does not create commit status' do - expect(response).to have_gitlab_http_status(:unauthorized) - end + context 'with partitions', :ci_partitionable do + let(:current_partition_id) { ci_testing_partition_id } + + before do + allow(Ci::Pipeline) + .to receive(:current_partition_value) { current_partition_id } end + + it 'creates records in the current partition' do + expect { post api(post_url, developer), params: { state: 'running' } } + .to change(CommitStatus, :count).by(1) + .and change(Ci::Pipeline, :count).by(1) + + status = CommitStatus.find(json_response['id']) + + expect(status.partition_id).to eq(current_partition_id) + expect(status.pipeline.partition_id).to eq(current_partition_id) + end + end + end + + context 'reporter user' do + before do + post api(post_url, reporter), params: { state: 'running' } + end + + it 'does not create commit status' do + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('403 Forbidden') + end + end + + context 'guest user' do + before do + post api(post_url, guest), params: { state: 'running' } + end + + it 'does not create commit status' do + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('403 Forbidden') + end + end + + context 'unauthorized user' do + before do + post api(post_url) + end + + it 'does not create commit status' do + expect(response).to have_gitlab_http_status(:unauthorized) end end end diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index fa35e367420..cf0cd9a2e85 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -881,7 +881,6 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do end context "custom action" do - let(:access_checker) { double(Gitlab::GitAccess) } let(:payload) do { 'action' => 'geo_proxy_to_primary', @@ -898,7 +897,8 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do before do project.add_guest(user) - expect(Gitlab::GitAccess).to receive(:new).with( + + expect_next_instance_of(Gitlab::GitAccess, key, project, 'ssh', @@ -907,11 +907,12 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do repository_path: "#{project.full_path}.git", redirected_path: nil } - ).and_return(access_checker) - expect(access_checker).to receive(:check).with( - 'git-receive-pack', - 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master' - ).and_return(custom_action_result) + ) do |access_checker| + expect(access_checker).to receive(:check).with( + 'git-receive-pack', + 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master' + ).and_return(custom_action_result) + end end context "git push" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a4957bff000..6c4589b7a5f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -297,11 +297,6 @@ RSpec.configure do |config| stub_feature_flags(ci_queueing_disaster_recovery_disable_fair_scheduling: false) stub_feature_flags(ci_queueing_disaster_recovery_disable_quota: false) - # The anonymous super-sidebar is under heavy development and enabling the flag - # globally leads to a lot of errors. This issue is for fixing all test to work with the - # new nav: https://gitlab.com/gitlab-org/gitlab/-/issues/420119 - stub_feature_flags(super_sidebar_logged_out: false) - # It's disabled in specs because we don't support certain features which # cause spec failures. stub_feature_flags(gitlab_error_tracking: false) diff --git a/spec/workers/bulk_imports/pipeline_batch_worker_spec.rb b/spec/workers/bulk_imports/pipeline_batch_worker_spec.rb index c10e1b486ab..3c33910b62c 100644 --- a/spec/workers/bulk_imports/pipeline_batch_worker_spec.rb +++ b/spec/workers/bulk_imports/pipeline_batch_worker_spec.rb @@ -73,6 +73,16 @@ RSpec.describe BulkImports::PipelineBatchWorker, feature_category: :importers do end end + context 'when batch status is started' do + let(:batch) { create(:bulk_import_batch_tracker, :started, tracker: tracker) } + + it 'runs the given pipeline batch successfully' do + subject.perform(batch.id) + + expect(batch.reload).to be_finished + end + end + context 'when exclusive lease cannot be obtained' do it 'does not run the pipeline' do expect(subject).to receive(:try_obtain_lease).and_return(false) diff --git a/spec/workers/click_house/events_sync_worker_spec.rb b/spec/workers/click_house/events_sync_worker_spec.rb index 8f328839cfd..01267db36a7 100644 --- a/spec/workers/click_house/events_sync_worker_spec.rb +++ b/spec/workers/click_house/events_sync_worker_spec.rb @@ -3,48 +3,125 @@ require 'spec_helper' RSpec.describe ClickHouse::EventsSyncWorker, feature_category: :value_stream_management do - let(:databases) { { main: :some_db } } let(:worker) { described_class.new } - before do - allow(ClickHouse::Client.configuration).to receive(:databases).and_return(databases) - end - - include_examples 'an idempotent worker' do - context 'when the event_sync_worker_for_click_house feature flag is on' do + it_behaves_like 'an idempotent worker' do + context 'when the event_sync_worker_for_click_house feature flag is on', :click_house do before do stub_feature_flags(event_sync_worker_for_click_house: true) end - it 'returns true' do - expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :processed }) + context 'when there is nothing to sync' do + it 'adds metadata for the worker' do + expect(worker).to receive(:log_extra_metadata_on_done).with(:result, + { status: :processed, records_inserted: 0, reached_end_of_table: true }) - worker.perform + worker.perform + + events = ClickHouse::Client.select('SELECT * FROM events', :main) + expect(events).to be_empty + end end - context 'when no ClickHouse databases are configured' do - let(:databases) { {} } + context 'when syncing records' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:project_event2) { create(:event, :closed, project: project, target: issue) } + let_it_be(:event_without_parent) { create(:event, :joined, project: nil, group: nil) } + let_it_be(:group_event) { create(:event, :created, group: group, project: nil) } + let_it_be(:project_event1) { create(:event, :created, project: project, target: issue) } + # looks invalid but we have some records like this on PRD - it 'skips execution' do - expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :disabled }) + it 'inserts all records' do + expect(worker).to receive(:log_extra_metadata_on_done).with(:result, + { status: :processed, records_inserted: 4, reached_end_of_table: true }) worker.perform + + expected_records = [ + hash_including('id' => project_event2.id, 'path' => "#{group.id}/#{project.project_namespace.id}/", + 'target_type' => 'Issue'), + hash_including('id' => event_without_parent.id, 'path' => '', 'target_type' => ''), + hash_including('id' => group_event.id, 'path' => "#{group.id}/", 'target_type' => ''), + hash_including('id' => project_event1.id, 'path' => "#{group.id}/#{project.project_namespace.id}/", + 'target_type' => 'Issue') + ] + + events = ClickHouse::Client.select('SELECT * FROM events ORDER BY id', :main) + + expect(events).to match(expected_records) + + last_processed_id = ClickHouse::SyncCursor.cursor_for(:events) + expect(last_processed_id).to eq(project_event1.id) end - end - context 'when exclusive lease error happens' do - it 'skips execution' do - expect(worker).to receive(:in_lock).and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError) - expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :skipped }) + context 'when multiple batches are needed' do + before do + stub_const("#{described_class}::BATCH_SIZE", 1) + stub_const("#{described_class}::INSERT_BATCH_SIZE", 1) + end - worker.perform + it 'inserts all records' do + worker.perform + + events = ClickHouse::Client.select('SELECT * FROM events', :main) + expect(events.size).to eq(4) + end + end + + context 'when time limit is reached' do + before do + stub_const("#{described_class}::BATCH_SIZE", 1) + end + + it 'stops the processing' do + allow_next_instance_of(Analytics::CycleAnalytics::RuntimeLimiter) do |runtime_limiter| + allow(runtime_limiter).to receive(:over_time?).and_return(false, true) + end + + expect(worker).to receive(:log_extra_metadata_on_done).with(:result, + { status: :processed, records_inserted: 2, reached_end_of_table: false }) + + worker.perform + + last_processed_id = ClickHouse::SyncCursor.cursor_for(:events) + expect(last_processed_id).to eq(event_without_parent.id) + end + end + + context 'when syncing from a certain point' do + before do + ClickHouse::SyncCursor.update_cursor_for(:events, project_event2.id) + end + + it 'syncs records after the cursor' do + worker.perform + + events = ClickHouse::Client.select('SELECT id FROM events ORDER BY id', :main) + expect(events).to eq([{ 'id' => event_without_parent.id }, { 'id' => group_event.id }, + { 'id' => project_event1.id }]) + end + + context 'when there is nothing to sync' do + it 'does nothing' do + expect(worker).to receive(:log_extra_metadata_on_done).with(:result, + { status: :processed, records_inserted: 0, reached_end_of_table: true }) + + ClickHouse::SyncCursor.update_cursor_for(:events, project_event1.id) + worker.perform + + events = ClickHouse::Client.select('SELECT id FROM events ORDER BY id', :main) + expect(events).to be_empty + end + end end end end - context 'when the event_sync_worker_for_click_house feature flag is off' do + context 'when clickhouse is not configured' do before do - stub_feature_flags(event_sync_worker_for_click_house: false) + allow(ClickHouse::Client.configuration).to receive(:databases).and_return({}) end it 'skips execution' do @@ -54,4 +131,28 @@ RSpec.describe ClickHouse::EventsSyncWorker, feature_category: :value_stream_man end end end + + context 'when exclusive lease error happens' do + it 'skips execution' do + stub_feature_flags(event_sync_worker_for_click_house: true) + allow(ClickHouse::Client.configuration).to receive(:databases).and_return({ main: :some_db }) + + expect(worker).to receive(:in_lock).and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError) + expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :skipped }) + + worker.perform + end + end + + context 'when the event_sync_worker_for_click_house feature flag is off' do + before do + stub_feature_flags(event_sync_worker_for_click_house: false) + end + + it 'skips execution' do + expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :disabled }) + + worker.perform + end + end end -- cgit v1.2.3