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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-09-14 15:09:48 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-09-14 15:09:48 +0300
commit7f73b108d44ebb58d2eddcbc98808bafc94d1b11 (patch)
treec44c1d67aefe3a48ce24b1b83ce495edbf57ff25 /spec
parent8f55aaede8e1ba5ca9ffae53313fa860ad05bffa (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb1
-rw-r--r--spec/features/explore/navbar_spec.rb1
-rw-r--r--spec/features/explore/user_explores_projects_spec.rb4
-rw-r--r--spec/features/nav/pinned_nav_items_spec.rb18
-rw-r--r--spec/features/search/user_searches_for_projects_spec.rb2
-rw-r--r--spec/features/signed_commits_spec.rb8
-rw-r--r--spec/features/snippets/show_spec.rb4
-rw-r--r--spec/features/unsubscribe_links_spec.rb4
-rw-r--r--spec/features/users/rss_spec.rb2
-rw-r--r--spec/features/users/show_spec.rb4
-rw-r--r--spec/features/users/snippets_spec.rb2
-rw-r--r--spec/features/users/user_browses_projects_on_user_page_spec.rb2
-rw-r--r--spec/features/whats_new_spec.rb4
-rw-r--r--spec/frontend/merge_requests/components/compare_app_spec.js54
-rw-r--r--spec/frontend/projects/settings_service_desk/components/custom_email_form_spec.js13
-rw-r--r--spec/frontend/projects/settings_service_desk/components/custom_email_wrapper_spec.js21
-rw-r--r--spec/frontend/super_sidebar/components/nav_item_spec.js4
-rw-r--r--spec/lib/bitbucket/representation/pull_request_spec.rb5
-rw-r--r--spec/lib/click_house/record_sync_context_spec.rb32
-rw-r--r--spec/lib/click_house/sync_cursor_spec.rb35
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb22
-rw-r--r--spec/lib/gitlab/database_spec.rb37
-rw-r--r--spec/models/analytics/cycle_analytics/runtime_limiter_spec.rb55
-rw-r--r--spec/models/review_spec.rb19
-rw-r--r--spec/requests/api/commit_statuses_spec.rb740
-rw-r--r--spec/requests/api/internal/base_spec.rb15
-rw-r--r--spec/spec_helper.rb5
-rw-r--r--spec/workers/bulk_imports/pipeline_batch_worker_spec.rb10
-rw-r--r--spec/workers/click_house/events_sync_worker_spec.rb145
29 files changed, 829 insertions, 439 deletions
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: '<b>Lorem</b> <i>ipsum</i> dolor sit <a href="https://example.com">amet</a>') }
+ 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