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>2021-05-04 15:10:04 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-04 15:10:04 +0300
commit998adcc422d4161515bf2960ef4dce71258f69a3 (patch)
tree34ffbf53b8587d3367457d4175fc8dfd71477dca /spec
parente2c471038ab2b4f09487c1feb74520f74f834986 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/oauth/authorizations_controller_spec.rb69
-rw-r--r--spec/factories/projects.rb2
-rw-r--r--spec/features/admin/admin_dev_ops_report_spec.rb6
-rw-r--r--spec/features/groups_spec.rb29
-rw-r--r--spec/features/issues/user_toggles_subscription_spec.rb4
-rw-r--r--spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js91
-rw-r--r--spec/frontend/admin/analytics/devops_score/mock_data.js42
-rw-r--r--spec/frontend/boards/components/board_content_sidebar_spec.js4
-rw-r--r--spec/frontend/issuable/components/status_box_spec.js (renamed from spec/frontend/merge_request/components/status_box_spec.js)18
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js4
-rw-r--r--spec/frontend/projects/compare/components/app_spec.js77
-rw-r--r--spec/frontend/projects/compare/components/mock_data.js37
-rw-r--r--spec/frontend/projects/compare/components/repo_dropdown_spec.js56
-rw-r--r--spec/frontend/projects/compare/components/revision_card_spec.js8
-rw-r--r--spec/frontend/projects/compare/components/revision_dropdown_spec.js22
-rw-r--r--spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js131
-rw-r--r--spec/frontend/sidebar/mock_data.js14
-rw-r--r--spec/frontend/sidebar/sidebar_subscriptions_spec.js36
-rw-r--r--spec/graphql/types/ci/job_type_spec.rb2
-rw-r--r--spec/graphql/types/ci/runner_type_spec.rb16
-rw-r--r--spec/graphql/types/permission_types/ci/job_spec.rb13
-rw-r--r--spec/graphql/types/query_type_spec.rb7
-rw-r--r--spec/helpers/auth_helper_spec.rb33
-rw-r--r--spec/helpers/dev_ops_report_helper_spec.rb35
-rw-r--r--spec/helpers/users_helper_spec.rb2
-rw-r--r--spec/lib/api/entities/release_spec.rb39
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb241
-rw-r--r--spec/lib/gitlab/import_export/base/relation_factory_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/config_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/group/relation_factory_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb58
-rw-r--r--spec/lib/gitlab/import_export/project/relation_factory_spec.rb72
-rw-r--r--spec/lib/gitlab/import_export/project/sample/relation_factory_spec.rb1
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb7
-rw-r--r--spec/models/note_spec.rb10
-rw-r--r--spec/models/project_spec.rb31
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb144
-rw-r--r--spec/spec_helper.rb9
-rw-r--r--spec/support/shared_examples/features/sidebar_shared_examples.rb20
-rw-r--r--spec/views/layouts/header/_new_dropdown.haml_spec.rb1
41 files changed, 984 insertions, 413 deletions
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb
index 21124299b25..5fc5cdfc9b9 100644
--- a/spec/controllers/oauth/authorizations_controller_spec.rb
+++ b/spec/controllers/oauth/authorizations_controller_spec.rb
@@ -73,39 +73,74 @@ RSpec.describe Oauth::AuthorizationsController do
include_examples 'OAuth Authorizations require confirmed user'
include_examples "Implicit grant can't be used in confidential application"
- context 'when the user is confirmed' do
- let(:confirmed_at) { 1.hour.ago }
+ context 'rendering of views based on the ownership of the application' do
+ shared_examples 'render views' do
+ render_views
- context 'without valid params' do
- it 'returns 200 code and renders error view' do
- get :new
+ it 'returns 200 and renders view with correct info', :aggregate_failures do
+ subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template('doorkeeper/authorizations/error')
+ expect(response.body).to include(application.owner.name)
+ expect(response).to render_template('doorkeeper/authorizations/new')
end
end
- context 'with valid params' do
- render_views
+ subject { get :new, params: params }
- it 'returns 200 code and renders view' do
- subject
+ context 'when auth app owner is a user' do
+ context 'with valid params' do
+ it_behaves_like 'render views'
+ end
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template('doorkeeper/authorizations/new')
+ context 'when auth app owner is a group' do
+ let(:group) { create(:group) }
+
+ context 'when auth app owner is a root group' do
+ let(:application) { create(:oauth_application, owner_id: group.id, owner_type: 'Namespace') }
+
+ it_behaves_like 'render views'
+ end
+
+ context 'when auth app owner is a subgroup' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:application) { create(:oauth_application, owner_id: subgroup.id, owner_type: 'Namespace') }
+
+ it_behaves_like 'render views'
end
+ end
- it 'deletes session.user_return_to and redirects when skip authorization' do
- application.update!(trusted: true)
- request.session['user_return_to'] = 'http://example.com'
+ context 'when there is no owner associated' do
+ let(:application) { create(:oauth_application, owner_id: nil, owner_type: nil) }
+ it 'renders view' do
subject
- expect(request.session['user_return_to']).to be_nil
- expect(response).to have_gitlab_http_status(:found)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('doorkeeper/authorizations/new')
end
end
end
+
+ context 'without valid params' do
+ it 'returns 200 code and renders error view' do
+ get :new
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('doorkeeper/authorizations/error')
+ end
+ end
+
+ it 'deletes session.user_return_to and redirects when skip authorization' do
+ application.update!(trusted: true)
+ request.session['user_return_to'] = 'http://example.com'
+
+ subject
+
+ expect(request.session['user_return_to']).to be_nil
+ expect(response).to have_gitlab_http_status(:found)
+ end
end
describe 'POST #create' do
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 80392a2fece..f4f1e1bcbda 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -194,7 +194,7 @@ FactoryBot.define do
filename,
content,
message: "Automatically created file #{filename}",
- branch_name: project.default_branch_or_master
+ branch_name: project.default_branch || 'master'
)
end
end
diff --git a/spec/features/admin/admin_dev_ops_report_spec.rb b/spec/features/admin/admin_dev_ops_report_spec.rb
index a05fa0640d8..33f984af807 100644
--- a/spec/features/admin/admin_dev_ops_report_spec.rb
+++ b/spec/features/admin/admin_dev_ops_report_spec.rb
@@ -53,15 +53,13 @@ RSpec.describe 'DevOps Report page', :js do
end
context 'when there is data to display' do
- it 'shows numbers for each metric' do
+ it 'shows the DevOps Score app' do
stub_application_setting(usage_ping_enabled: true)
create(:dev_ops_report_metric)
visit admin_dev_ops_report_path
- expect(page).to have_content(
- 'Issues created per active user 1.2 You 9.3 Lead 13.3%'
- )
+ expect(page).to have_selector('[data-testid="devops-score-app"]')
end
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 27b75d15d3b..a43946925bf 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -439,6 +439,35 @@ RSpec.describe 'Group' do
end
end
+ describe 'new_repo experiment' do
+ let_it_be(:group) { create_default(:group) }
+
+ it 'when in candidate renders "project/repository"' do
+ stub_experiments(new_repo: :candidate)
+
+ visit group_path(group)
+
+ find('li.header-new.dropdown').click
+
+ page.within('li.header-new.dropdown') do
+ expect(page).to have_selector('a', text: 'New project/repository')
+ end
+ end
+
+ it 'when in control renders "project/repository"' do
+ stub_experiments(new_repo: :control)
+
+ visit group_path(group)
+
+ find('li.header-new.dropdown').click
+
+ page.within('li.header-new.dropdown') do
+ expect(page).to have_selector('a', text: 'New project')
+ expect(page).to have_no_selector('a', text: 'New project/repository')
+ end
+ end
+ end
+
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb
index d91c187c840..35f4b415463 100644
--- a/spec/features/issues/user_toggles_subscription_spec.rb
+++ b/spec/features/issues/user_toggles_subscription_spec.rb
@@ -32,8 +32,8 @@ RSpec.describe "User toggles subscription", :js do
let(:project) { create(:project_empty_repo, :public, emails_disabled: true) }
it 'is disabled' do
- expect(page).to have_content('Notifications have been disabled by the project or group owner')
- expect(page).not_to have_selector('[data-testid="subscription-toggle"]')
+ expect(page).to have_content('Disabled by project owner')
+ expect(page).to have_button('Notifications', class: 'is-disabled')
end
end
end
diff --git a/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js b/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js
new file mode 100644
index 00000000000..09979694e07
--- /dev/null
+++ b/spec/frontend/admin/analytics/devops_score/components/devops_score_spec.js
@@ -0,0 +1,91 @@
+import { GlTable, GlBadge } from '@gitlab/ui';
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { mount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import DevopsScore from '~/analytics/devops_report/components/devops_score.vue';
+import { createdAt, cards, averageScore, devopsScoreTableHeaders } from '../mock_data';
+
+describe('DevopsScore', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = extendedWrapper(
+ mount(DevopsScore, {
+ provide: {
+ devopsScoreMetrics: {
+ createdAt,
+ cards,
+ averageScore,
+ },
+ },
+ }),
+ );
+ };
+
+ const findTable = () => wrapper.find(GlTable);
+ const findCol = (testId) => findTable().find(`[data-testid="${testId}"]`);
+ const findUsageCol = () => findCol('usageCol');
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('displays the title note', () => {
+ expect(wrapper.findByTestId('devops-score-note-text').text()).toBe(
+ 'DevOps score metrics are based on usage over the last 30 days. Last updated: 2020-06-29 08:16.',
+ );
+ });
+
+ it('displays the single stat section', () => {
+ const component = wrapper.find(GlSingleStat);
+
+ expect(component.exists()).toBe(true);
+ expect(component.props('value')).toBe(averageScore.value);
+ });
+
+ describe('devops score table', () => {
+ it('displays the table', () => {
+ expect(findTable().exists()).toBe(true);
+ });
+
+ describe('table headings', () => {
+ let headers;
+
+ beforeEach(() => {
+ headers = findTable().findAll("[data-testid='header']");
+ });
+
+ it('displays the correct number of headings', () => {
+ expect(headers).toHaveLength(devopsScoreTableHeaders.length);
+ });
+
+ describe.each(devopsScoreTableHeaders)('header fields', ({ label, index }) => {
+ let headerWrapper;
+
+ beforeEach(() => {
+ headerWrapper = headers.at(index);
+ });
+
+ it(`displays the correct table heading text for "${label}"`, () => {
+ expect(headerWrapper.text()).toContain(label);
+ });
+ });
+ });
+
+ describe('table columns', () => {
+ describe('Your usage', () => {
+ it('displays the corrrect value', () => {
+ expect(findUsageCol().text()).toContain('3.2');
+ });
+
+ it('displays the corrrect badge', () => {
+ const badge = findUsageCol().find(GlBadge);
+
+ expect(badge.exists()).toBe(true);
+ expect(badge.props('variant')).toBe('muted');
+ expect(badge.text()).toBe('Low');
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/admin/analytics/devops_score/mock_data.js b/spec/frontend/admin/analytics/devops_score/mock_data.js
new file mode 100644
index 00000000000..358568c6e39
--- /dev/null
+++ b/spec/frontend/admin/analytics/devops_score/mock_data.js
@@ -0,0 +1,42 @@
+export const averageScore = {
+ value: '10',
+ scoreLevel: {
+ label: 'High',
+ icon: 'check-circle',
+ variant: 'success',
+ },
+};
+
+export const cards = [
+ {
+ title: 'Issues created per active user',
+ usage: '3.2',
+ leadInstance: '10.2',
+ score: '0',
+ scoreLevel: {
+ label: 'Low',
+ variant: 'muted',
+ },
+ },
+];
+
+export const createdAt = '2020-06-29 08:16';
+
+export const devopsScoreTableHeaders = [
+ {
+ index: 0,
+ label: '',
+ },
+ {
+ index: 1,
+ label: 'Your usage',
+ },
+ {
+ index: 2,
+ label: 'Leader usage',
+ },
+ {
+ index: 3,
+ label: 'Score',
+ },
+];
diff --git a/spec/frontend/boards/components/board_content_sidebar_spec.js b/spec/frontend/boards/components/board_content_sidebar_spec.js
index 7f949739891..01c99a02db2 100644
--- a/spec/frontend/boards/components/board_content_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_content_sidebar_spec.js
@@ -6,9 +6,9 @@ import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
-import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
+import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
import { mockIssue, mockIssueGroupPath, mockIssueProjectPath } from '../mock_data';
describe('BoardContentSidebar', () => {
@@ -111,7 +111,7 @@ describe('BoardContentSidebar', () => {
});
it('renders BoardSidebarSubscription', () => {
- expect(wrapper.find(BoardSidebarSubscription).exists()).toBe(true);
+ expect(wrapper.find(SidebarSubscriptionsWidget).exists()).toBe(true);
});
it('renders BoardSidebarMilestoneSelect', () => {
diff --git a/spec/frontend/merge_request/components/status_box_spec.js b/spec/frontend/issuable/components/status_box_spec.js
index de0f3574ab2..990fac67f7e 100644
--- a/spec/frontend/merge_request/components/status_box_spec.js
+++ b/spec/frontend/issuable/components/status_box_spec.js
@@ -1,8 +1,6 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import StatusBox from '~/merge_request/components/status_box.vue';
-import mrEventHub from '~/merge_request/eventhub';
+import StatusBox from '~/issuable/components/status_box.vue';
let wrapper;
@@ -70,18 +68,4 @@ describe('Merge request status box component', () => {
});
});
});
-
- it('updates with eventhub event', async () => {
- factory({
- initialState: 'opened',
- });
-
- expect(wrapper.text()).toContain('Open');
-
- mrEventHub.$emit('mr.state.updated', { state: 'closed' });
-
- await nextTick();
-
- expect(wrapper.text()).toContain('Closed');
- });
});
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index b717bab7c3f..b140eea9439 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -437,6 +437,7 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click');
await wrapper.vm.$nextTick;
+ await wrapper.vm.$nextTick;
expect(flash).toHaveBeenCalledWith(
`Something went wrong while closing the ${type}. Please try again later.`,
@@ -472,6 +473,7 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click');
await wrapper.vm.$nextTick;
+ await wrapper.vm.$nextTick;
expect(flash).toHaveBeenCalledWith(
`Something went wrong while reopening the ${type}. Please try again later.`,
@@ -489,6 +491,8 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click');
+ await wrapper.vm.$nextTick();
+
expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/projects/compare/components/app_spec.js b/spec/frontend/projects/compare/components/app_spec.js
index 6de06e4373c..7989a6f3d74 100644
--- a/spec/frontend/projects/compare/components/app_spec.js
+++ b/spec/frontend/projects/compare/components/app_spec.js
@@ -2,26 +2,19 @@ import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import CompareApp from '~/projects/compare/components/app.vue';
import RevisionCard from '~/projects/compare/components/revision_card.vue';
+import { appDefaultProps as defaultProps } from './mock_data';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
-const projectCompareIndexPath = 'some/path';
-const refsProjectPath = 'some/refs/path';
-const paramsFrom = 'master';
-const paramsTo = 'master';
-
describe('CompareApp component', () => {
let wrapper;
+ const findSourceRevisionCard = () => wrapper.find('[data-testid="sourceRevisionCard"]');
+ const findTargetRevisionCard = () => wrapper.find('[data-testid="targetRevisionCard"]');
const createComponent = (props = {}) => {
wrapper = shallowMount(CompareApp, {
propsData: {
- projectCompareIndexPath,
- refsProjectPath,
- paramsFrom,
- paramsTo,
- projectMergeRequestPath: '',
- createMrPath: '',
+ ...defaultProps,
...props,
},
});
@@ -39,16 +32,16 @@ describe('CompareApp component', () => {
it('renders component with prop', () => {
expect(wrapper.props()).toEqual(
expect.objectContaining({
- projectCompareIndexPath,
- refsProjectPath,
- paramsFrom,
- paramsTo,
+ projectCompareIndexPath: defaultProps.projectCompareIndexPath,
+ refsProjectPath: defaultProps.refsProjectPath,
+ paramsFrom: defaultProps.paramsFrom,
+ paramsTo: defaultProps.paramsTo,
}),
);
});
it('contains the correct form attributes', () => {
- expect(wrapper.attributes('action')).toBe(projectCompareIndexPath);
+ expect(wrapper.attributes('action')).toBe(defaultProps.projectCompareIndexPath);
expect(wrapper.attributes('method')).toBe('POST');
});
@@ -87,6 +80,58 @@ describe('CompareApp component', () => {
});
});
+ it('sets the selected project when the "selectProject" event is emitted', async () => {
+ const project = {
+ name: 'some-to-name',
+ id: '1',
+ };
+
+ findTargetRevisionCard().vm.$emit('selectProject', {
+ direction: 'to',
+ project,
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(findTargetRevisionCard().props('selectedProject')).toEqual(
+ expect.objectContaining(project),
+ );
+ });
+
+ it('sets the selected revision when the "selectRevision" event is emitted', async () => {
+ const revision = 'some-revision';
+
+ findTargetRevisionCard().vm.$emit('selectRevision', {
+ direction: 'to',
+ revision,
+ });
+
+ await wrapper.vm.$nextTick();
+
+ expect(findSourceRevisionCard().props('paramsBranch')).toBe(revision);
+ });
+
+ describe('swap revisions button', () => {
+ const findSwapRevisionsButton = () => wrapper.find('[data-testid="swapRevisionsButton"]');
+
+ it('renders the swap revisions button', () => {
+ expect(findSwapRevisionsButton().exists()).toBe(true);
+ });
+
+ it('has the correct text', () => {
+ expect(findSwapRevisionsButton().text()).toBe('Swap revisions');
+ });
+
+ it('swaps revisions when clicked', async () => {
+ findSwapRevisionsButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ expect(findTargetRevisionCard().props('paramsBranch')).toBe(defaultProps.paramsTo);
+ expect(findSourceRevisionCard().props('paramsBranch')).toBe(defaultProps.paramsFrom);
+ });
+ });
+
describe('merge request buttons', () => {
const findProjectMrButton = () => wrapper.find('[data-testid="projectMrButton"]');
const findCreateMrButton = () => wrapper.find('[data-testid="createMrButton"]');
diff --git a/spec/frontend/projects/compare/components/mock_data.js b/spec/frontend/projects/compare/components/mock_data.js
new file mode 100644
index 00000000000..61309928c26
--- /dev/null
+++ b/spec/frontend/projects/compare/components/mock_data.js
@@ -0,0 +1,37 @@
+const refsProjectPath = 'some/refs/path';
+const paramsName = 'to';
+const paramsBranch = 'main';
+const defaultProject = {
+ name: 'some-to-name',
+ id: '1',
+};
+
+export const appDefaultProps = {
+ projectCompareIndexPath: 'some/path',
+ projectMergeRequestPath: '',
+ projects: [defaultProject],
+ paramsFrom: 'main',
+ paramsTo: 'target/branch',
+ createMrPath: '',
+ refsProjectPath,
+ defaultProject,
+};
+
+export const revisionCardDefaultProps = {
+ selectedProject: defaultProject,
+ paramsBranch,
+ revisionText: 'Source',
+ refsProjectPath,
+ paramsName,
+};
+
+export const repoDropdownDefaultProps = {
+ selectedProject: defaultProject,
+ paramsName,
+};
+
+export const revisionDropdownDefaultProps = {
+ refsProjectPath,
+ paramsBranch,
+ paramsName,
+};
diff --git a/spec/frontend/projects/compare/components/repo_dropdown_spec.js b/spec/frontend/projects/compare/components/repo_dropdown_spec.js
index df8fea8fd32..27a7a32ebca 100644
--- a/spec/frontend/projects/compare/components/repo_dropdown_spec.js
+++ b/spec/frontend/projects/compare/components/repo_dropdown_spec.js
@@ -1,37 +1,17 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RepoDropdown from '~/projects/compare/components/repo_dropdown.vue';
-
-const defaultProps = {
- paramsName: 'to',
-};
-
-const projectToId = '1';
-const projectToName = 'some-to-name';
-const projectFromId = '2';
-const projectFromName = 'some-from-name';
-
-const defaultProvide = {
- projectTo: { id: projectToId, name: projectToName },
- projectsFrom: [
- { id: projectFromId, name: projectFromName },
- { id: 3, name: 'some-from-another-name' },
- ],
-};
+import { revisionCardDefaultProps as defaultProps } from './mock_data';
describe('RepoDropdown component', () => {
let wrapper;
- const createComponent = (props = {}, provide = {}) => {
+ const createComponent = (props = {}) => {
wrapper = shallowMount(RepoDropdown, {
propsData: {
...defaultProps,
...props,
},
- provide: {
- ...defaultProvide,
- ...provide,
- },
});
};
@@ -49,11 +29,11 @@ describe('RepoDropdown component', () => {
});
it('set hidden input', () => {
- expect(findHiddenInput().attributes('value')).toBe(projectToId);
+ expect(findHiddenInput().attributes('value')).toBe(defaultProps.selectedProject.id);
});
it('displays the project name in the disabled dropdown', () => {
- expect(findGlDropdown().props('text')).toBe(projectToName);
+ expect(findGlDropdown().props('text')).toBe(defaultProps.selectedProject.name);
expect(findGlDropdown().props('disabled')).toBe(true);
});
@@ -66,31 +46,39 @@ describe('RepoDropdown component', () => {
describe('Target Revision', () => {
beforeEach(() => {
- createComponent({ paramsName: 'from' });
+ const projects = [
+ {
+ name: 'some-to-name',
+ id: '1',
+ },
+ ];
+
+ createComponent({ paramsName: 'from', projects });
});
it('set hidden input of the selected project', () => {
- expect(findHiddenInput().attributes('value')).toBe(projectToId);
+ expect(findHiddenInput().attributes('value')).toBe(defaultProps.selectedProject.id);
});
it('displays matching project name of the source revision initially in the dropdown', () => {
- expect(findGlDropdown().props('text')).toBe(projectToName);
+ expect(findGlDropdown().props('text')).toBe(defaultProps.selectedProject.name);
});
- it('updates the hiddin input value when onClick method is triggered', async () => {
- const repoId = '100';
+ it('updates the hidden input value when onClick method is triggered', async () => {
+ const repoId = '1';
wrapper.vm.onClick({ id: repoId });
await wrapper.vm.$nextTick();
expect(findHiddenInput().attributes('value')).toBe(repoId);
});
- it('emits `changeTargetProject` event when another target project is selected', async () => {
- const index = 1;
- const { projectsFrom } = defaultProvide;
- findGlDropdown().findAll(GlDropdownItem).at(index).vm.$emit('click');
+ it('emits `selectProject` event when another target project is selected', async () => {
+ findGlDropdown().findAll(GlDropdownItem).at(0).vm.$emit('click');
await wrapper.vm.$nextTick();
- expect(wrapper.emitted('changeTargetProject')[0][0]).toEqual(projectsFrom[index].name);
+ expect(wrapper.emitted('selectProject')[0][0]).toEqual({
+ direction: 'from',
+ project: { id: '1', name: 'some-to-name' },
+ });
});
});
});
diff --git a/spec/frontend/projects/compare/components/revision_card_spec.js b/spec/frontend/projects/compare/components/revision_card_spec.js
index 83f858f4454..57906045337 100644
--- a/spec/frontend/projects/compare/components/revision_card_spec.js
+++ b/spec/frontend/projects/compare/components/revision_card_spec.js
@@ -3,13 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import RepoDropdown from '~/projects/compare/components/repo_dropdown.vue';
import RevisionCard from '~/projects/compare/components/revision_card.vue';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue';
-
-const defaultProps = {
- refsProjectPath: 'some/refs/path',
- revisionText: 'Source',
- paramsName: 'to',
- paramsBranch: 'master',
-};
+import { revisionCardDefaultProps as defaultProps } from './mock_data';
describe('RepoDropdown component', () => {
let wrapper;
diff --git a/spec/frontend/projects/compare/components/revision_dropdown_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
index aab9607ceae..118bb68585e 100644
--- a/spec/frontend/projects/compare/components/revision_dropdown_spec.js
+++ b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
@@ -1,15 +1,10 @@
-import { GlDropdown, GlSearchBoxByType } from '@gitlab/ui';
+import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue';
-
-const defaultProps = {
- refsProjectPath: 'some/refs/path',
- paramsName: 'from',
- paramsBranch: 'master',
-};
+import { revisionDropdownDefaultProps as defaultProps } from './mock_data';
jest.mock('~/flash');
@@ -142,4 +137,17 @@ describe('RevisionDropdown component', () => {
expect(findGlDropdown().props('text')).toBe(defaultProps.paramsBranch);
});
});
+
+ it('emits `selectRevision` event when another revision is selected', async () => {
+ createComponent();
+ wrapper.vm.branches = ['some-branch'];
+ await wrapper.vm.$nextTick();
+
+ findGlDropdown().findAll(GlDropdownItem).at(0).vm.$emit('click');
+
+ expect(wrapper.emitted('selectRevision')[0][0]).toEqual({
+ direction: 'to',
+ revision: 'some-branch',
+ });
+ });
});
diff --git a/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js b/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js
new file mode 100644
index 00000000000..549ab99c6af
--- /dev/null
+++ b/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js
@@ -0,0 +1,131 @@
+import { GlIcon, GlToggle } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
+import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
+import SidebarSubscriptionWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
+import issueSubscribedQuery from '~/sidebar/queries/issue_subscribed.query.graphql';
+import { issueSubscriptionsResponse } from '../../mock_data';
+
+jest.mock('~/flash');
+
+Vue.use(VueApollo);
+
+describe('Sidebar Subscriptions Widget', () => {
+ let wrapper;
+ let fakeApollo;
+
+ const findEditableItem = () => wrapper.findComponent(SidebarEditableItem);
+ const findToggle = () => wrapper.findComponent(GlToggle);
+ const findIcon = () => wrapper.findComponent(GlIcon);
+
+ const createComponent = ({
+ subscriptionsQueryHandler = jest.fn().mockResolvedValue(issueSubscriptionsResponse()),
+ } = {}) => {
+ fakeApollo = createMockApollo([[issueSubscribedQuery, subscriptionsQueryHandler]]);
+
+ wrapper = shallowMount(SidebarSubscriptionWidget, {
+ apolloProvider: fakeApollo,
+ provide: {
+ canUpdate: true,
+ },
+ propsData: {
+ fullPath: 'group/project',
+ iid: '1',
+ issuableType: 'issue',
+ },
+ stubs: {
+ SidebarEditableItem,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ fakeApollo = null;
+ });
+
+ it('passes a `loading` prop as true to editable item when query is loading', () => {
+ createComponent();
+
+ expect(findEditableItem().props('loading')).toBe(true);
+ });
+
+ describe('when user is not subscribed to the issue', () => {
+ beforeEach(() => {
+ createComponent();
+ return waitForPromises();
+ });
+
+ it('passes a `loading` prop as false to editable item', () => {
+ expect(findEditableItem().props('loading')).toBe(false);
+ });
+
+ it('toggle is unchecked', () => {
+ expect(findToggle().props('value')).toBe(false);
+ });
+
+ it('emits `subscribedUpdated` event with a `false` payload', () => {
+ expect(wrapper.emitted('subscribedUpdated')).toEqual([[false]]);
+ });
+ });
+
+ describe('when user is subscribed to the issue', () => {
+ beforeEach(() => {
+ createComponent({
+ subscriptionsQueryHandler: jest.fn().mockResolvedValue(issueSubscriptionsResponse(true)),
+ });
+ return waitForPromises();
+ });
+
+ it('passes a `loading` prop as false to editable item', () => {
+ expect(findEditableItem().props('loading')).toBe(false);
+ });
+
+ it('toggle is checked', () => {
+ expect(findToggle().props('value')).toBe(true);
+ });
+
+ it('emits `subscribedUpdated` event with a `true` payload', () => {
+ expect(wrapper.emitted('subscribedUpdated')).toEqual([[true]]);
+ });
+ });
+
+ describe('when emails are disabled', () => {
+ it('toggle is disabled and off when user is subscribed', async () => {
+ createComponent({
+ subscriptionsQueryHandler: jest
+ .fn()
+ .mockResolvedValue(issueSubscriptionsResponse(true, true)),
+ });
+ await waitForPromises();
+
+ expect(findIcon().props('name')).toBe('notifications-off');
+ expect(findToggle().props('disabled')).toBe(true);
+ });
+
+ it('toggle is disabled and off when user is not subscribed', async () => {
+ createComponent({
+ subscriptionsQueryHandler: jest
+ .fn()
+ .mockResolvedValue(issueSubscriptionsResponse(false, true)),
+ });
+ await waitForPromises();
+
+ expect(findIcon().props('name')).toBe('notifications-off');
+ expect(findToggle().props('disabled')).toBe(true);
+ });
+ });
+
+ it('displays a flash message when query is rejected', async () => {
+ createComponent({
+ subscriptionsQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
+ });
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/sidebar/mock_data.js b/spec/frontend/sidebar/mock_data.js
index 38fce53e398..f51d2f3d459 100644
--- a/spec/frontend/sidebar/mock_data.js
+++ b/spec/frontend/sidebar/mock_data.js
@@ -275,6 +275,20 @@ export const issueReferenceResponse = (reference) => ({
},
});
+export const issueSubscriptionsResponse = (subscribed = false, emailsDisabled = false) => ({
+ data: {
+ workspace: {
+ __typename: 'Project',
+ issuable: {
+ __typename: 'Issue',
+ id: 'gid://gitlab/Issue/4',
+ subscribed,
+ emailsDisabled,
+ },
+ },
+ },
+});
+
export const issuableQueryResponse = {
data: {
workspace: {
diff --git a/spec/frontend/sidebar/sidebar_subscriptions_spec.js b/spec/frontend/sidebar/sidebar_subscriptions_spec.js
deleted file mode 100644
index d900fde7e70..00000000000
--- a/spec/frontend/sidebar/sidebar_subscriptions_spec.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import SidebarSubscriptions from '~/sidebar/components/subscriptions/sidebar_subscriptions.vue';
-import SidebarService from '~/sidebar/services/sidebar_service';
-import SidebarMediator from '~/sidebar/sidebar_mediator';
-import SidebarStore from '~/sidebar/stores/sidebar_store';
-import Mock from './mock_data';
-
-describe('Sidebar Subscriptions', () => {
- let wrapper;
- let mediator;
-
- beforeEach(() => {
- mediator = new SidebarMediator(Mock.mediator);
- wrapper = shallowMount(SidebarSubscriptions, {
- propsData: {
- mediator,
- },
- });
- });
-
- afterEach(() => {
- wrapper.destroy();
- SidebarService.singleton = null;
- SidebarStore.singleton = null;
- SidebarMediator.singleton = null;
- });
-
- it('calls the mediator toggleSubscription on event', () => {
- const spy = jest.spyOn(mediator, 'toggleSubscription').mockReturnValue(Promise.resolve());
-
- wrapper.vm.onToggleSubscription();
-
- expect(spy).toHaveBeenCalled();
- spy.mockRestore();
- });
-});
diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb
index a1107bae630..8b092584efe 100644
--- a/spec/graphql/types/ci/job_type_spec.rb
+++ b/spec/graphql/types/ci/job_type_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Types::Ci::JobType do
specify { expect(described_class.graphql_name).to eq('CiJob') }
specify { expect(described_class).to require_graphql_authorizations(:read_commit_status) }
+ specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Ci::Job) }
it 'exposes the expected fields' do
expected_fields = %i[
@@ -38,6 +39,7 @@ RSpec.describe Types::Ci::JobType do
status
tags
triggered
+ userPermissions
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb
new file mode 100644
index 00000000000..dfe4a30c5b7
--- /dev/null
+++ b/spec/graphql/types/ci/runner_type_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::RunnerType do
+ specify { expect(described_class.graphql_name).to eq('CiRunner') }
+
+ it 'contains attributes related to a runner' do
+ expected_fields = %w[
+ id description contacted_at maximum_timeout access_level active status
+ version short_sha revision locked run_untagged ip_address runner_type tag_list
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/permission_types/ci/job_spec.rb b/spec/graphql/types/permission_types/ci/job_spec.rb
new file mode 100644
index 00000000000..e4bc5419070
--- /dev/null
+++ b/spec/graphql/types/permission_types/ci/job_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::PermissionTypes::Ci::Job do
+ it 'has expected permission fields' do
+ expected_permissions = [
+ :read_job_artifacts, :read_build, :update_build
+ ]
+
+ expect(described_class).to have_graphql_fields(expected_permissions).only
+ end
+end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index a877e19c069..f0e80fa8f14 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -24,6 +24,7 @@ RSpec.describe GitlabSchema.types['Query'] do
merge_request
usage_trends_measurements
runner_platforms
+ runner
]
expect(described_class).to have_graphql_fields(*expected_fields).at_least
@@ -84,6 +85,12 @@ RSpec.describe GitlabSchema.types['Query'] do
end
end
+ describe 'runner field' do
+ subject { described_class.fields['runner'] }
+
+ it { is_expected.to have_graphql_type(Types::Ci::RunnerType) }
+ end
+
describe 'runner_platforms field' do
subject { described_class.fields['runnerPlatforms'] }
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index beffa4cf60e..5194285b965 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -313,4 +313,37 @@ RSpec.describe AuthHelper do
it { is_expected.to be_falsey }
end
end
+
+ describe '#auth_app_owner_text' do
+ shared_examples 'generates text with the correct info' do
+ it 'includes the name of the application owner' do
+ auth_app_owner_text = helper.auth_app_owner_text(owner)
+
+ expect(auth_app_owner_text).to include(owner.name)
+ expect(auth_app_owner_text).to include(path_to_owner)
+ end
+ end
+
+ context 'when owner is a user' do
+ let_it_be(:owner) { create(:user) }
+
+ let(:path_to_owner) { user_path(owner) }
+
+ it_behaves_like 'generates text with the correct info'
+ end
+
+ context 'when owner is a group' do
+ let_it_be(:owner) { create(:group) }
+
+ let(:path_to_owner) { user_path(owner) }
+
+ it_behaves_like 'generates text with the correct info'
+ end
+
+ context 'when the user is missing' do
+ it 'returns nil' do
+ expect(helper.auth_app_owner_text(nil)).to be(nil)
+ end
+ end
+ end
end
diff --git a/spec/helpers/dev_ops_report_helper_spec.rb b/spec/helpers/dev_ops_report_helper_spec.rb
new file mode 100644
index 00000000000..769400521f8
--- /dev/null
+++ b/spec/helpers/dev_ops_report_helper_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DevOpsReportHelper do
+ subject { DevOpsReport::MetricPresenter.new(metric) }
+
+ let(:metric) { build(:dev_ops_report_metric, created_at: DateTime.new(2021, 4, 3, 2, 1, 0) ) }
+
+ describe '#devops_score_metrics' do
+ let(:devops_score_metrics) { helper.devops_score_metrics(subject) }
+
+ it { expect(devops_score_metrics[:averageScore]).to eq({ scoreLevel: { icon: "status-alert", label: "Moderate", variant: "warning" }, value: "55.9" } ) }
+
+ it { expect(devops_score_metrics[:cards].first).to eq({ leadInstance: "9.3", score: "13.3", scoreLevel: { label: "Low", variant: "muted" }, title: "Issues created per active user", usage: "1.2" } ) }
+ it { expect(devops_score_metrics[:cards].second).to eq({ leadInstance: "30.3", score: "92.7", scoreLevel: { label: "High", variant: "success" }, title: "Comments created per active user", usage: "28.1" } ) }
+ it { expect(devops_score_metrics[:cards].fourth).to eq({ leadInstance: "5.2", score: "62.4", scoreLevel: { label: "Moderate", variant: "neutral" }, title: "Boards created per active user", usage: "3.3" } ) }
+
+ it { expect(devops_score_metrics[:createdAt]).to eq("2021-04-03 02:01") }
+
+ describe 'with low average score' do
+ let(:low_metric) { double(average_percentage_score: 2, cards: subject.cards, created_at: subject.created_at) }
+ let(:devops_score_metrics) { helper.devops_score_metrics(low_metric) }
+
+ it { expect(devops_score_metrics[:averageScore]).to eq({ scoreLevel: { icon: "status-failed", label: "Low", variant: "danger" }, value: "2.0" } ) }
+ end
+
+ describe 'with high average score' do
+ let(:high_metric) { double(average_percentage_score: 82, cards: subject.cards, created_at: subject.created_at) }
+ let(:devops_score_metrics) { helper.devops_score_metrics(high_metric) }
+
+ it { expect(devops_score_metrics[:averageScore]).to eq({ scoreLevel: { icon: "status_success_solid", label: "High", variant: "success" }, value: "82.0" } ) }
+ end
+ end
+end
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index f0f09408249..46073b37a3b 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -160,7 +160,7 @@ RSpec.describe UsersHelper do
it 'returns the "It\'s You" badge' do
badges = helper.user_badges_in_admin_section(user)
- expect(filter_ee_badges(badges)).to eq([text: "It's you!", variant: nil])
+ expect(filter_ee_badges(badges)).to eq([text: "It's you!", variant: "muted"])
end
end
diff --git a/spec/lib/api/entities/release_spec.rb b/spec/lib/api/entities/release_spec.rb
index 06062634015..4f40830a15c 100644
--- a/spec/lib/api/entities/release_spec.rb
+++ b/spec/lib/api/entities/release_spec.rb
@@ -54,18 +54,41 @@ RSpec.describe API::Entities::Release do
subject(:description_html) { entity.as_json['description_html'] }
- it 'renders special references if current user has access' do
- project.add_reporter(user)
+ it 'is inexistent' do
+ expect(description_html).to be_nil
+ end
+
+ context 'when remove_description_html_in_release_api feature flag is disabled' do
+ before do
+ stub_feature_flags(remove_description_html_in_release_api: false)
+ end
+
+ it 'renders special references if current user has access' do
+ project.add_reporter(user)
+
+ expect(description_html).to include(issue_path)
+ expect(description_html).to include(issue_title)
+ end
- expect(description_html).to include(issue_path)
- expect(description_html).to include(issue_title)
+ it 'does not render special references if current user has no access' do
+ project.add_guest(user)
+
+ expect(description_html).not_to include(issue_path)
+ expect(description_html).not_to include(issue_title)
+ end
end
- it 'does not render special references if current user has no access' do
- project.add_guest(user)
+ context 'when remove_description_html_in_release_api_override feature flag is enabled' do
+ before do
+ stub_feature_flags(remove_description_html_in_release_api_override: project)
+ end
- expect(description_html).not_to include(issue_path)
- expect(description_html).not_to include(issue_title)
+ it 'renders special references if current user has access' do
+ project.add_reporter(user)
+
+ expect(description_html).to include(issue_path)
+ expect(description_html).to include(issue_title)
+ end
end
end
end
diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
deleted file mode 100644
index 1c55b50ea3f..00000000000
--- a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
+++ /dev/null
@@ -1,241 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, schema: 20200204113223 do
- let(:users_table) { table(:users) }
- let(:namespaces_table) { table(:namespaces) }
- let(:projects_table) { table(:projects) }
- let(:project_authorizations_table) { table(:project_authorizations) }
- let(:members_table) { table(:members) }
- let(:group_group_links) { table(:group_group_links) }
- let(:project_group_links) { table(:project_group_links) }
-
- let(:user) { users_table.create!(id: 1, email: 'user@example.com', projects_limit: 10) }
- let(:group) { namespaces_table.create!(type: 'Group', name: 'group', path: 'group') }
-
- subject { described_class.new.perform([user.id]) }
-
- context 'missing authorization' do
- context 'personal project' do
- before do
- user_namespace = namespaces_table.create!(owner_id: user.id, name: 'User', path: 'user')
- projects_table.create!(id: 1,
- name: 'personal-project',
- path: 'personal-project',
- visibility_level: 0,
- namespace_id: user_namespace.id)
- end
-
- it 'creates correct authorization' do
- expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
- expect(project_authorizations_table.all).to(
- match_array([have_attributes(user_id: 1, project_id: 1, access_level: 40)]))
- end
- end
-
- context 'group membership' do
- before do
- projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
- visibility_level: 0, namespace_id: group.id)
- members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
- type: 'GroupMember', access_level: 20, notification_level: 3)
- end
-
- it 'creates correct authorization' do
- expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
- expect(project_authorizations_table.all).to(
- match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
- end
- end
-
- context 'inherited group membership' do
- before do
- sub_group = namespaces_table.create!(type: 'Group', name: 'subgroup',
- path: 'subgroup', parent_id: group.id)
- projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
- visibility_level: 0, namespace_id: sub_group.id)
- members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
- type: 'GroupMember', access_level: 20, notification_level: 3)
- end
-
- it 'creates correct authorization' do
- expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
- expect(project_authorizations_table.all).to(
- match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
- end
- end
-
- context 'project membership' do
- before do
- project = projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
- visibility_level: 0, namespace_id: group.id)
- members_table.create!(user_id: user.id, source_id: project.id, source_type: 'Project',
- type: 'ProjectMember', access_level: 20, notification_level: 3)
- end
-
- it 'creates correct authorization' do
- expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
- expect(project_authorizations_table.all).to(
- match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
- end
- end
-
- context 'shared group' do
- before do
- members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
- type: 'GroupMember', access_level: 30, notification_level: 3)
-
- shared_group = namespaces_table.create!(type: 'Group', name: 'shared group',
- path: 'shared-group')
- projects_table.create!(id: 1, name: 'project', path: 'project', visibility_level: 0,
- namespace_id: shared_group.id)
-
- group_group_links.create!(shared_group_id: shared_group.id, shared_with_group_id: group.id,
- group_access: 20)
- end
-
- it 'creates correct authorization' do
- expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
- expect(project_authorizations_table.all).to(
- match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
- end
- end
-
- context 'shared project' do
- before do
- members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
- type: 'GroupMember', access_level: 30, notification_level: 3)
-
- another_group = namespaces_table.create!(type: 'Group', name: 'another group', path: 'another-group')
- shared_project = projects_table.create!(id: 1, name: 'shared project', path: 'shared-project',
- visibility_level: 0, namespace_id: another_group.id)
-
- project_group_links.create!(project_id: shared_project.id, group_id: group.id, group_access: 20)
- end
-
- it 'creates correct authorization' do
- expect { subject }.to change { project_authorizations_table.count }.from(0).to(1)
- expect(project_authorizations_table.all).to(
- match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)]))
- end
- end
- end
-
- context 'unapproved access requests' do
- context 'group membership' do
- before do
- projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
- visibility_level: 0, namespace_id: group.id)
- members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
- type: 'GroupMember', access_level: 20, requested_at: Time.now, notification_level: 3)
- end
-
- it 'does not create authorization' do
- expect { subject }.not_to change { project_authorizations_table.count }.from(0)
- end
- end
-
- context 'inherited group membership' do
- before do
- sub_group = namespaces_table.create!(type: 'Group', name: 'subgroup', path: 'subgroup',
- parent_id: group.id)
- projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
- visibility_level: 0, namespace_id: sub_group.id)
- members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
- type: 'GroupMember', access_level: 20, requested_at: Time.now, notification_level: 3)
- end
-
- it 'does not create authorization' do
- expect { subject }.not_to change { project_authorizations_table.count }.from(0)
- end
- end
-
- context 'project membership' do
- before do
- project = projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
- visibility_level: 0, namespace_id: group.id)
- members_table.create!(user_id: user.id, source_id: project.id, source_type: 'Project',
- type: 'ProjectMember', access_level: 20, requested_at: Time.now, notification_level: 3)
- end
-
- it 'does not create authorization' do
- expect { subject }.not_to change { project_authorizations_table.count }.from(0)
- end
- end
-
- context 'shared group' do
- before do
- members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
- type: 'GroupMember', access_level: 30, requested_at: Time.now, notification_level: 3)
-
- shared_group = namespaces_table.create!(type: 'Group', name: 'shared group',
- path: 'shared-group')
- projects_table.create!(id: 1, name: 'project', path: 'project', visibility_level: 0,
- namespace_id: shared_group.id)
-
- group_group_links.create!(shared_group_id: shared_group.id, shared_with_group_id: group.id,
- group_access: 20)
- end
-
- it 'does not create authorization' do
- expect { subject }.not_to change { project_authorizations_table.count }.from(0)
- end
- end
-
- context 'shared project' do
- before do
- members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
- type: 'GroupMember', access_level: 30, requested_at: Time.now, notification_level: 3)
-
- another_group = namespaces_table.create!(type: 'Group', name: 'another group', path: 'another-group')
- shared_project = projects_table.create!(id: 1, name: 'shared project', path: 'shared-project',
- visibility_level: 0, namespace_id: another_group.id)
-
- project_group_links.create!(project_id: shared_project.id, group_id: group.id, group_access: 20)
- end
-
- it 'does not create authorization' do
- expect { subject }.not_to change { project_authorizations_table.count }.from(0)
- end
- end
- end
-
- context 'incorrect authorization' do
- before do
- project = projects_table.create!(id: 1, name: 'group-project', path: 'group-project',
- visibility_level: 0, namespace_id: group.id)
- members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace',
- type: 'GroupMember', access_level: 30, notification_level: 3)
-
- project_authorizations_table.create!(user_id: user.id, project_id: project.id,
- access_level: 10)
- end
-
- it 'fixes authorization' do
- expect { subject }.not_to change { project_authorizations_table.count }.from(1)
- expect(project_authorizations_table.all).to(
- match_array([have_attributes(user_id: 1, project_id: 1, access_level: 30)]))
- end
- end
-
- context 'unwanted authorization' do
- before do
- project = projects_table.create!(name: 'group-project', path: 'group-project',
- visibility_level: 0, namespace_id: group.id)
-
- project_authorizations_table.create!(user_id: user.id, project_id: project.id,
- access_level: 10)
- end
-
- it 'deletes authorization' do
- expect { subject }.to change { project_authorizations_table.count }.from(1).to(0)
- end
- end
-
- context 'deleted user' do
- it 'does not fail' do
- expect { described_class.new.perform([non_existing_record_id]) }.not_to raise_error
- end
- end
-end
diff --git a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
index 09e6e5a03bb..df33b4896a4 100644
--- a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe Gitlab::ImportExport::Base::RelationFactory do
subject do
described_class.create(relation_sym: relation_sym,
relation_hash: relation_hash,
+ relation_index: 1,
object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
members_mapper: members_mapper,
user: user,
diff --git a/spec/lib/gitlab/import_export/config_spec.rb b/spec/lib/gitlab/import_export/config_spec.rb
index 40cf75779b6..7ad5d3d846c 100644
--- a/spec/lib/gitlab/import_export/config_spec.rb
+++ b/spec/lib/gitlab/import_export/config_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::ImportExport::Config do
expect { subject }.not_to raise_error
expect(subject).to be_a(Hash)
expect(subject.keys).to contain_exactly(
- :tree, :excluded_attributes, :included_attributes, :methods, :preloads)
+ :tree, :excluded_attributes, :included_attributes, :methods, :preloads, :export_reorders)
end
end
end
diff --git a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
index 6b2f80cc80a..63286fc0719 100644
--- a/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe Gitlab::ImportExport::Group::RelationFactory do
described_class.create(
relation_sym: relation_sym,
relation_hash: relation_hash,
+ relation_index: 1,
members_mapper: members_mapper,
object_builder: Gitlab::ImportExport::Group::ObjectBuilder,
user: importer_user,
diff --git a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
index 762687beedb..a0b2faaecfe 100644
--- a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
@@ -30,12 +30,14 @@ RSpec.describe Gitlab::ImportExport::JSON::StreamingSerializer do
let(:json_writer) { instance_double('Gitlab::ImportExport::JSON::LegacyWriter') }
let(:hash) { { name: exportable.name, description: exportable.description }.stringify_keys }
let(:include) { [] }
+ let(:custom_orderer) { nil }
let(:relations_schema) do
{
only: [:name, :description],
include: include,
- preload: { issues: nil }
+ preload: { issues: nil },
+ export_reorder: custom_orderer
}
end
@@ -57,19 +59,63 @@ RSpec.describe Gitlab::ImportExport::JSON::StreamingSerializer do
[{ issues: { include: [] } }]
end
+ before do
+ create_list(:issue, 3, project: exportable, relative_position: 10000) # ascending ids, same position positive
+ create_list(:issue, 3, project: exportable, relative_position: -5000) # ascending ids, same position negative
+ create_list(:issue, 3, project: exportable, relative_position: 0) # ascending ids, duplicate positions
+ create_list(:issue, 3, project: exportable, relative_position: nil) # no position
+ create_list(:issue, 3, :with_desc_relative_position, project: exportable ) # ascending ids, descending position
+ end
+
it 'calls json_writer.write_relation_array with proper params' do
expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, array_including(issue.to_json))
subject.execute
end
- context 'relation ordering' do
- before do
- create_list(:issue, 5, project: exportable)
+ context 'default relation ordering' do
+ it 'orders exported issues by primary key(:id)' do
+ expected_issues = exportable.issues.reorder(:id).map(&:to_json)
+
+ expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, expected_issues)
+
+ subject.execute
end
+ end
- it 'orders exported issues by primary key' do
- expected_issues = exportable.issues.reorder(:id).map(&:to_json)
+ context 'custom relation ordering ascending' do
+ let(:custom_orderer) do
+ {
+ issues: {
+ column: :relative_position,
+ direction: :asc,
+ nulls_position: :nulls_last
+ }
+ }
+ end
+
+ it 'orders exported issues by custom column(relative_position)' do
+ expected_issues = exportable.issues.reorder(:relative_position, :id).map(&:to_json)
+
+ expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, expected_issues)
+
+ subject.execute
+ end
+ end
+
+ context 'custom relation ordering descending' do
+ let(:custom_orderer) do
+ {
+ issues: {
+ column: :relative_position,
+ direction: :desc,
+ nulls_position: :nulls_first
+ }
+ }
+ end
+
+ it 'orders exported issues by custom column(relative_position)' do
+ expected_issues = exportable.issues.order_relative_position_desc.order(id: :desc).map(&:to_json)
expect(json_writer).to receive(:write_relation_array).with(exportable_path, :issues, expected_issues)
diff --git a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
index 56ba730e893..38e700e8f9e 100644
--- a/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::ImportExport::Project::RelationFactory do
+RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_memory_store_caching do
let(:group) { create(:group) }
let(:project) { create(:project, :repository, group: group) }
let(:members_mapper) { double('members_mapper').as_null_object }
@@ -13,6 +13,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do
described_class.create(
relation_sym: relation_sym,
relation_hash: relation_hash,
+ relation_index: 1,
object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
members_mapper: members_mapper,
user: importer_user,
@@ -171,6 +172,75 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory do
end
end
+ context 'issue object' do
+ let(:relation_sym) { :issues }
+
+ let(:exported_member) do
+ {
+ "id" => 111,
+ "access_level" => 30,
+ "source_id" => 1,
+ "source_type" => "Project",
+ "user_id" => 3,
+ "notification_level" => 3,
+ "created_at" => "2016-11-18T09:29:42.634Z",
+ "updated_at" => "2016-11-18T09:29:42.634Z",
+ "user" => {
+ "id" => admin.id,
+ "email" => admin.email,
+ "username" => admin.username
+ }
+ }
+ end
+
+ let(:members_mapper) do
+ Gitlab::ImportExport::MembersMapper.new(
+ exported_members: [exported_member],
+ user: importer_user,
+ importable: project)
+ end
+
+ let(:relation_hash) do
+ {
+ 'id' => 20,
+ 'target_branch' => "feature",
+ 'source_branch' => "feature_conflict",
+ 'project_id' => project.id,
+ 'author_id' => admin.id,
+ 'assignee_id' => admin.id,
+ 'updated_by_id' => admin.id,
+ 'title' => "Issue 1",
+ 'created_at' => "2016-06-14T15:02:36.568Z",
+ 'updated_at' => "2016-06-14T15:02:56.815Z",
+ 'state' => "opened",
+ 'description' => "Description",
+ "relative_position" => 25111 # just a random position
+ }
+ end
+
+ it 'has preloaded project' do
+ expect(created_object.project).to equal(project)
+ end
+
+ context 'computing relative position' do
+ context 'when max relative position in the hierarchy is not cached' do
+ it 'has computed new relative_position' do
+ expect(created_object.relative_position).to equal(1026) # 513*2 - ideal distance
+ end
+ end
+
+ context 'when max relative position in the hierarchy is cached' do
+ before do
+ Rails.cache.write("import:#{project.model_name.plural}:#{project.id}:hierarchy_max_issues_relative_position", 10000)
+ end
+
+ it 'has computed new relative_position' do
+ expect(created_object.relative_position).to equal(10000 + 1026) # 513*2 - ideal distance
+ end
+ end
+ end
+ end
+
context 'label object' do
let(:relation_sym) { :labels }
let(:relation_hash) do
diff --git a/spec/lib/gitlab/import_export/project/sample/relation_factory_spec.rb b/spec/lib/gitlab/import_export/project/sample/relation_factory_spec.rb
index 86d5f2402f8..9dde09a7602 100644
--- a/spec/lib/gitlab/import_export/project/sample/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/project/sample/relation_factory_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe Gitlab::ImportExport::Project::Sample::RelationFactory do
described_class.create( # rubocop:disable Rails/SaveBang
relation_sym: relation_sym,
relation_hash: relation_hash,
+ relation_index: 1,
object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
members_mapper: members_mapper,
user: importer_user,
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index a86afa9cba5..3021d92244e 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -85,7 +85,7 @@ RSpec.describe Gitlab::TreeSummary do
'long.txt',
'',
message: message,
- branch_name: project.default_branch_or_master
+ branch_name: project.default_branch
)
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 8e0c79b3842..9be0ca71bcb 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -172,6 +172,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
another_project = create(:project, :repository, creator: another_user)
create(:remote_mirror, project: another_project, enabled: false)
create(:snippet, author: user)
+ create(:suggestion, note: create(:note, project: project))
end
expect(described_class.usage_activity_by_stage_create({})).to include(
@@ -181,7 +182,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
projects_with_disable_overriding_approvers_per_merge_request: 2,
projects_without_disable_overriding_approvers_per_merge_request: 6,
remote_mirrors: 2,
- snippets: 2
+ snippets: 2,
+ suggestions: 2
)
expect(described_class.usage_activity_by_stage_create(described_class.last_28_days_time_period)).to include(
deploy_keys: 1,
@@ -190,7 +192,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
projects_with_disable_overriding_approvers_per_merge_request: 1,
projects_without_disable_overriding_approvers_per_merge_request: 3,
remote_mirrors: 1,
- snippets: 1
+ snippets: 1,
+ suggestions: 1
)
end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 992b2246f01..4eabc266b40 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1384,6 +1384,16 @@ RSpec.describe Note do
expect(notes.second.id).to eq(note2.id)
end
end
+
+ describe '.with_suggestions' do
+ it 'returns the correct note' do
+ note_with_suggestion = create(:note, suggestions: [create(:suggestion)])
+ note_without_suggestion = create(:note)
+
+ expect(described_class.with_suggestions).to include(note_with_suggestion)
+ expect(described_class.with_suggestions).not_to include(note_without_suggestion)
+ end
+ end
end
describe '#noteable_assignee_or_author?' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 5e12161f47b..55b13128c17 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -6892,6 +6892,37 @@ RSpec.describe Project, factory_default: :keep do
end
end
+ describe '#default_branch_or_main' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ # Stubbing it as true since the FF disabled for tests globally
+ stub_feature_flags(main_branch_over_master: true)
+ end
+
+ it 'returns default branch' do
+ expect(project.default_branch_or_main).to eq(project.default_branch)
+ end
+
+ context 'when default branch is nil' do
+ let(:project) { create(:project, :empty_repo) }
+
+ it 'returns main' do
+ expect(project.default_branch_or_main).to eq('main')
+ end
+
+ context 'main_branch_over_master is disabled' do
+ before do
+ stub_feature_flags(main_branch_over_master: false)
+ end
+
+ it 'returns master' do
+ expect(project.default_branch_or_main).to eq('master')
+ end
+ end
+ end
+ end
+
def finish_job(export_job)
export_job.start
export_job.finish
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
new file mode 100644
index 00000000000..e1f84d23209
--- /dev/null
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.runner(id)' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create_default(:user, :admin) }
+
+ let_it_be(:active_runner) do
+ create(:ci_runner, :instance, description: 'Runner 1', contacted_at: 2.hours.ago,
+ active: true, version: 'adfe156', revision: 'a', locked: true, ip_address: '127.0.0.1', maximum_timeout: 600,
+ access_level: 0, tag_list: %w[tag1 tag2], run_untagged: true)
+ end
+
+ let_it_be(:inactive_runner) do
+ create(:ci_runner, :instance, description: 'Runner 2', contacted_at: 1.day.ago, active: false,
+ version: 'adfe157', revision: 'b', ip_address: '10.10.10.10', access_level: 1, run_untagged: true)
+ end
+
+ def get_runner(id)
+ case id
+ when :active_runner
+ active_runner
+ when :inactive_runner
+ inactive_runner
+ end
+ end
+
+ shared_examples 'runner details fetch' do |runner_id|
+ let(:query) do
+ wrap_fields(query_graphql_path(query_path, all_graphql_fields_for('CiRunner')))
+ end
+
+ let(:query_path) do
+ [
+ [:runner, { id: get_runner(runner_id).to_global_id.to_s }]
+ ]
+ end
+
+ it 'retrieves expected fields' do
+ post_graphql(query, current_user: user)
+
+ runner_data = graphql_data_at(:runner)
+ expect(runner_data).not_to be_nil
+
+ runner = get_runner(runner_id)
+ expect(runner_data).to match a_hash_including(
+ 'id' => "gid://gitlab/Ci::Runner/#{runner.id}",
+ 'description' => runner.description,
+ 'contactedAt' => runner.contacted_at&.iso8601,
+ 'version' => runner.version,
+ 'shortSha' => runner.short_sha,
+ 'revision' => runner.revision,
+ 'locked' => runner.locked,
+ 'active' => runner.active,
+ 'status' => runner.status.to_s.upcase,
+ 'maximumTimeout' => runner.maximum_timeout,
+ 'accessLevel' => runner.access_level.to_s.upcase,
+ 'runUntagged' => runner.run_untagged,
+ 'ipAddress' => runner.ip_address,
+ 'runnerType' => 'INSTANCE_TYPE'
+ )
+ expect(runner_data['tagList']).to match_array runner.tag_list
+ end
+ end
+
+ shared_examples 'retrieval by unauthorized user' do |runner_id|
+ let(:query) do
+ wrap_fields(query_graphql_path(query_path, all_graphql_fields_for('CiRunner')))
+ end
+
+ let(:query_path) do
+ [
+ [:runner, { id: get_runner(runner_id).to_global_id.to_s }]
+ ]
+ end
+
+ it 'returns null runner' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data_at(:runner)).to be_nil
+ end
+ end
+
+ describe 'for active runner' do
+ it_behaves_like 'runner details fetch', :active_runner
+ end
+
+ describe 'for inactive runner' do
+ it_behaves_like 'runner details fetch', :inactive_runner
+ end
+
+ describe 'by regular user' do
+ let(:user) { create_default(:user) }
+
+ it_behaves_like 'retrieval by unauthorized user', :active_runner
+ end
+
+ describe 'by unauthenticated user' do
+ let(:user) { nil }
+
+ it_behaves_like 'retrieval by unauthorized user', :active_runner
+ end
+
+ describe 'Query limits' do
+ def runner_query(runner)
+ <<~SINGLE
+ runner(id: "#{runner.to_global_id}") {
+ #{all_graphql_fields_for('CiRunner')}
+ }
+ SINGLE
+ end
+
+ let(:single_query) do
+ <<~QUERY
+ {
+ active: #{runner_query(active_runner)}
+ }
+ QUERY
+ end
+
+ let(:double_query) do
+ <<~QUERY
+ {
+ active: #{runner_query(active_runner)}
+ inactive: #{runner_query(inactive_runner)}
+ }
+ QUERY
+ end
+
+ it 'does not execute more queries per runner', :aggregate_failures do
+ # warm-up license cache and so on:
+ post_graphql(single_query, current_user: user)
+
+ control = ActiveRecord::QueryRecorder.new { post_graphql(single_query, current_user: user) }
+
+ expect { post_graphql(double_query, current_user: user) }
+ .not_to exceed_query_limit(control)
+ expect(graphql_data_at(:active)).not_to be_nil
+ expect(graphql_data_at(:inactive)).not_to be_nil
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 4179e6f7e91..4a3227cc9f5 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -275,6 +275,15 @@ RSpec.configure do |config|
# https://gitlab.com/groups/gitlab-org/-/epics/5531
stub_feature_flags(refactor_blob_viewer: false)
+ # Disable `main_branch_over_master` as we migrate
+ # from `master` to `main` accross our codebase.
+ # It's done in order to preserve the concistency in tests
+ # As we're ready to change `master` usages to `main`, let's enable it
+ stub_feature_flags(main_branch_over_master: false)
+
+ # Selectively disable by actor https://docs.gitlab.com/ee/development/feature_flags/#selectively-disable-by-actor
+ stub_feature_flags(remove_description_html_in_release_api_override: false)
+
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
else
unstub_all_feature_flags
diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb
index ca891669e31..c9508818f74 100644
--- a/spec/support/shared_examples/features/sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb
@@ -44,22 +44,24 @@ RSpec.shared_examples 'issue boards sidebar' do
context 'in notifications subscription' do
it 'displays notifications toggle', :aggregate_failures do
page.within('[data-testid="sidebar-notifications"]') do
- expect(page).to have_selector('[data-testid="notification-subscribe-toggle"]')
+ expect(page).to have_selector('[data-testid="subscription-toggle"]')
expect(page).to have_content('Notifications')
- expect(page).not_to have_content('Notifications have been disabled by the project or group owner')
+ expect(page).not_to have_content('Disabled by project owner')
end
end
it 'shows toggle as on then as off as user toggles to subscribe and unsubscribe', :aggregate_failures do
- toggle = find('[data-testid="notification-subscribe-toggle"]')
+ wait_for_requests
- toggle.click
+ click_button 'Notifications'
- expect(toggle).to have_css("button.is-checked")
+ expect(page).to have_button('Notifications', class: 'is-checked')
- toggle.click
+ click_button 'Notifications'
- expect(toggle).not_to have_css("button.is-checked")
+ wait_for_requests
+
+ expect(page).not_to have_button('Notifications', class: 'is-checked')
end
context 'when notifications have been disabled' do
@@ -71,8 +73,8 @@ RSpec.shared_examples 'issue boards sidebar' do
it 'displays a message that notifications have been disabled' do
page.within('[data-testid="sidebar-notifications"]') do
- expect(page).not_to have_selector('[data-testid="notification-subscribe-toggle"]')
- expect(page).to have_content('Notifications have been disabled by the project or group owner')
+ expect(page).to have_button('Notifications', class: 'is-disabled')
+ expect(page).to have_content('Disabled by project owner')
end
end
end
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
index cec095f93ad..bf81ab577f7 100644
--- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -52,6 +52,7 @@ RSpec.describe 'layouts/header/_new_dropdown' do
end
it 'has a "New project" link' do
+ render('layouts/header/new_repo_experiment')
render
expect(rendered).to have_link('New project', href: new_project_path(namespace_id: group.id))