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-08-10 15:09:48 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-08-10 15:09:48 +0300
commit1c00bf77814669d7d35c8aede82553c7e8883e18 (patch)
tree34f35f73a4631c81c18b07b05a4cf5886c5c2cec /spec
parentf605b80ff70b395afa345bad11c7f8aa0506a9bf (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/protected_branches_spec.rb16
-rw-r--r--spec/frontend/super_sidebar/components/global_search/components/global_search_default_issuables_spec.js139
-rw-r--r--spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js131
-rw-r--r--spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js78
-rw-r--r--spec/frontend/super_sidebar/mock_data.js1
-rw-r--r--spec/frontend/vue_compat_test_setup.js43
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb11
-rw-r--r--spec/models/customer_relations/contact_spec.rb23
-rw-r--r--spec/models/issue_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb17
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb8
-rw-r--r--spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb4
-rw-r--r--spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb22
-rw-r--r--spec/support/protected_branch_helpers.rb4
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb3
16 files changed, 382 insertions, 124 deletions
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 9244cafbc0b..ee5d92b7cdb 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -40,6 +40,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
it 'allows to create a protected branch with name containing HTML tags' do
visit project_protected_branches_path(project)
+
+ show_add_form
set_defaults
set_protected_branch_name('foo<b>bar<\b>')
click_on "Protect"
@@ -89,6 +91,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
describe "explicit protected branches" do
it "allows creating explicit protected branches" do
visit project_protected_branches_path(project)
+
+ show_add_form
set_defaults
set_protected_branch_name('some->branch')
click_on "Protect"
@@ -100,6 +104,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
it "shows success alert once protected branch is created" do
visit project_protected_branches_path(project)
+
+ show_add_form
set_defaults
set_protected_branch_name('some->branch')
click_on "Protect"
@@ -112,6 +118,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
project.repository.add_branch(admin, 'some-branch', commit.id)
visit project_protected_branches_path(project)
+
+ show_add_form
set_defaults
set_protected_branch_name('some-branch')
click_on "Protect"
@@ -124,6 +132,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
it "displays an error message if the named branch does not exist" do
visit project_protected_branches_path(project)
+
+ show_add_form
set_defaults
set_protected_branch_name('some-branch')
click_on "Protect"
@@ -135,6 +145,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
describe "wildcard protected branches" do
it "allows creating protected branches with a wildcard" do
visit project_protected_branches_path(project)
+
+ show_add_form
set_defaults
set_protected_branch_name('*-stable')
click_on "Protect"
@@ -149,6 +161,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
project.repository.add_branch(admin, 'staging-stable', 'master')
visit project_protected_branches_path(project)
+
+ show_add_form
set_defaults
set_protected_branch_name('*-stable')
click_on "Protect"
@@ -164,6 +178,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem
project.repository.add_branch(admin, 'development', 'master')
visit project_protected_branches_path(project)
+
+ show_add_form
set_protected_branch_name('*-stable')
set_defaults
click_on "Protect"
diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_issuables_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_issuables_spec.js
new file mode 100644
index 00000000000..934cb1678df
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_issuables_spec.js
@@ -0,0 +1,139 @@
+import { GlDisclosureDropdownGroup, GlDisclosureDropdownItem } from '@gitlab/ui';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { shallowMount } from '@vue/test-utils';
+import GlobalSearchDefaultIssuables from '~/super_sidebar/components/global_search/components/global_search_default_issuables.vue';
+import {
+ MOCK_SEARCH_CONTEXT,
+ MOCK_PROJECT_SEARCH_CONTEXT,
+ MOCK_GROUP_SEARCH_CONTEXT,
+ MOCK_DEFAULT_SEARCH_OPTIONS,
+} from '../mock_data';
+
+Vue.use(Vuex);
+
+describe('GlobalSearchDefaultPlaces', () => {
+ let wrapper;
+
+ const createComponent = ({
+ searchContext = null,
+ mockDefaultSearchOptions = [],
+ ...options
+ } = {}) => {
+ const store = new Vuex.Store({
+ state: {
+ searchContext,
+ },
+ getters: {
+ defaultSearchOptions: () => mockDefaultSearchOptions,
+ },
+ });
+
+ wrapper = shallowMount(GlobalSearchDefaultIssuables, {
+ store,
+ stubs: {
+ GlDisclosureDropdownGroup,
+ },
+ ...options,
+ });
+ };
+
+ const findGroup = () => wrapper.findComponent(GlDisclosureDropdownGroup);
+ const findItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem);
+
+ describe('given no contextSwitcherLinks', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders nothing', () => {
+ expect(wrapper.html()).toBe('');
+ });
+
+ it('emits a nothing-to-render event', () => {
+ expect(wrapper.emitted('nothing-to-render')).toEqual([[]]);
+ });
+ });
+
+ describe('given some contextSwitcherLinks', () => {
+ beforeEach(() => {
+ createComponent({
+ searchContext: MOCK_SEARCH_CONTEXT,
+ mockDefaultSearchOptions: MOCK_DEFAULT_SEARCH_OPTIONS,
+ attrs: {
+ bordered: true,
+ class: 'test-class',
+ },
+ });
+ });
+
+ it('renders a disclosure dropdown group', () => {
+ expect(findGroup().exists()).toBe(true);
+ });
+
+ it('renders the expected header', () => {
+ expect(wrapper.text()).toContain('All GitLab');
+ });
+
+ it('passes attrs down', () => {
+ const group = findGroup();
+ expect(group.props('bordered')).toBe(true);
+ expect(group.classes()).toContain('test-class');
+ });
+
+ it('renders the links', () => {
+ const itemProps = findItems().wrappers.map((item) => item.props('item'));
+
+ expect(itemProps).toEqual([
+ {
+ text: 'Issues assigned to me',
+ href: '/dashboard/issues/?assignee_username=anyone',
+ },
+ {
+ text: "Issues I've created",
+ href: '/dashboard/issues/?author_username=anyone',
+ },
+ {
+ text: 'Merge requests assigned to me',
+ href: '/dashboard/merge_requests/?assignee_username=anyone',
+ },
+ {
+ text: "Merge requests that I'm a reviewer",
+ href: '/dashboard/merge_requests/?reviewer_username=anyone',
+ },
+ {
+ text: "Merge requests I've created",
+ href: '/dashboard/merge_requests/?author_username=anyone',
+ },
+ ]);
+ });
+ });
+
+ describe('group name', () => {
+ describe('in a project context', () => {
+ beforeEach(() => {
+ createComponent({
+ searchContext: MOCK_PROJECT_SEARCH_CONTEXT,
+ mockDefaultSearchOptions: MOCK_DEFAULT_SEARCH_OPTIONS,
+ });
+ });
+
+ it('renders the expected header', () => {
+ expect(wrapper.text()).toContain('MockProject');
+ });
+ });
+
+ describe('in a group context', () => {
+ beforeEach(() => {
+ createComponent({
+ searchContext: MOCK_GROUP_SEARCH_CONTEXT,
+ mockDefaultSearchOptions: MOCK_DEFAULT_SEARCH_OPTIONS,
+ });
+ });
+
+ it('renders the expected header', () => {
+ expect(wrapper.text()).toContain('MockGroup');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js
index 0fb6585e8ca..18a6e4aa35a 100644
--- a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js
+++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js
@@ -1,110 +1,59 @@
-import { GlDisclosureDropdownGroup, GlDisclosureDropdownItem } from '@gitlab/ui';
-import Vue from 'vue';
-import Vuex from 'vuex';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { shallowMount } from '@vue/test-utils';
import GlobalSearchDefaultItems from '~/super_sidebar/components/global_search/components/global_search_default_items.vue';
-import { MOCK_SEARCH_CONTEXT, MOCK_DEFAULT_SEARCH_OPTIONS } from '../mock_data';
-import { contextSwitcherLinks } from '../../../mock_data';
-
-Vue.use(Vuex);
+import GlobalSearchDefaultPlaces from '~/super_sidebar/components/global_search/components/global_search_default_places.vue';
+import GlobalSearchDefaultIssuables from '~/super_sidebar/components/global_search/components/global_search_default_issuables.vue';
describe('GlobalSearchDefaultItems', () => {
let wrapper;
- const createComponent = ({
- storeState,
- mockDefaultSearchOptions = MOCK_DEFAULT_SEARCH_OPTIONS,
- ...options
- } = {}) => {
- const store = new Vuex.Store({
- state: {
- searchContext: MOCK_SEARCH_CONTEXT,
- ...storeState,
- },
- getters: {
- defaultSearchOptions: () => mockDefaultSearchOptions,
- },
- });
-
- wrapper = shallowMountExtended(GlobalSearchDefaultItems, {
- store,
- provide: {
- contextSwitcherLinks,
- },
- stubs: {
- GlDisclosureDropdownGroup,
- },
- ...options,
- });
+ const createComponent = () => {
+ wrapper = shallowMount(GlobalSearchDefaultItems);
};
- const findGroups = () => wrapper.findAllComponents(GlDisclosureDropdownGroup);
- const findItems = (root = wrapper) => root.findAllComponents(GlDisclosureDropdownItem);
-
- describe('template', () => {
- describe('Dropdown items', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders two groups', () => {
- const groups = findGroups();
-
- expect(groups).toHaveLength(2);
-
- const actualNames = groups.wrappers.map((group) => group.props('group').name);
- expect(actualNames).toEqual(['Places', 'All GitLab']);
- });
-
- it('renders context switcher links in first group', () => {
- const group = findGroups().at(0);
- expect(group.props('group').name).toBe('Places');
+ const findPlaces = () => wrapper.findComponent(GlobalSearchDefaultPlaces);
+ const findIssuables = () => wrapper.findComponent(GlobalSearchDefaultIssuables);
+ const receivedAttrs = (wrapperInstance) => ({
+ // See https://github.com/vuejs/test-utils/issues/2151.
+ ...wrapperInstance.vm.$attrs,
+ });
- const items = findItems(group);
- expect(items).toHaveLength(contextSwitcherLinks.length);
- });
+ beforeEach(() => {
+ createComponent();
+ });
- it('renders default search options in second group', () => {
- const group = findGroups().at(1);
- expect(group.props('group').name).toBe('All GitLab');
+ describe('all child components can render', () => {
+ it('renders the components', () => {
+ expect(findPlaces().exists()).toBe(true);
+ expect(findIssuables().exists()).toBe(true);
+ });
- const items = findItems(group);
- expect(items).toHaveLength(MOCK_DEFAULT_SEARCH_OPTIONS.length);
- });
+ it('sets the expected props on first component', () => {
+ const places = findPlaces();
+ expect(receivedAttrs(places)).toEqual({});
+ expect(places.classes()).toEqual([]);
});
- describe('Empty groups', () => {
- beforeEach(() => {
- createComponent({ mockDefaultSearchOptions: [], provide: { contextSwitcherLinks: [] } });
- });
+ it('sets the expected props on second component', () => {
+ const issuables = findIssuables();
+ expect(receivedAttrs(issuables)).toEqual({ bordered: true });
+ expect(issuables.classes()).toEqual(['gl-mt-3']);
+ });
+ });
- it('does not render groups with no items', () => {
- expect(findGroups()).toHaveLength(0);
- });
+ describe('when a child component emits nothing-to-render', () => {
+ beforeEach(() => {
+ findPlaces().vm.$emit('nothing-to-render');
});
- describe.each`
- group | project | groupHeader
- ${null} | ${null} | ${'All GitLab'}
- ${{ name: 'Test Group' }} | ${null} | ${'Test Group'}
- ${{ name: 'Test Group' }} | ${{ name: 'Test Project' }} | ${'Test Project'}
- `('Current context header', ({ group, project, groupHeader }) => {
- describe(`when group is ${group?.name} and project is ${project?.name}`, () => {
- beforeEach(() => {
- createComponent({
- storeState: {
- searchContext: {
- group,
- project,
- },
- },
- });
- });
+ it('does not render the component', () => {
+ expect(findPlaces().exists()).toBe(false);
+ expect(findIssuables().exists()).toBe(true);
+ });
- it(`should render as ${groupHeader}`, () => {
- expect(wrapper.text()).toContain(groupHeader);
- });
- });
+ it('sets the expected props on first component', () => {
+ const issuables = findIssuables();
+ expect(receivedAttrs(issuables)).toEqual({});
+ expect(issuables.classes()).toEqual([]);
});
});
});
diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js
new file mode 100644
index 00000000000..c6126a348f5
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js
@@ -0,0 +1,78 @@
+import { GlDisclosureDropdownGroup, GlDisclosureDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import GlobalSearchDefaultPlaces from '~/super_sidebar/components/global_search/components/global_search_default_places.vue';
+import { contextSwitcherLinks } from '../../../mock_data';
+
+describe('GlobalSearchDefaultPlaces', () => {
+ let wrapper;
+
+ const createComponent = ({ links = [], attrs } = {}) => {
+ wrapper = shallowMount(GlobalSearchDefaultPlaces, {
+ provide: {
+ contextSwitcherLinks: links,
+ },
+ attrs,
+ stubs: {
+ GlDisclosureDropdownGroup,
+ },
+ });
+ };
+
+ const findGroup = () => wrapper.findComponent(GlDisclosureDropdownGroup);
+ const findItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem);
+
+ describe('given no contextSwitcherLinks', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders nothing', () => {
+ expect(wrapper.html()).toBe('');
+ });
+
+ it('emits a nothing-to-render event', () => {
+ expect(wrapper.emitted('nothing-to-render')).toEqual([[]]);
+ });
+ });
+
+ describe('given some contextSwitcherLinks', () => {
+ beforeEach(() => {
+ createComponent({
+ links: contextSwitcherLinks,
+ attrs: {
+ bordered: true,
+ class: 'test-class',
+ },
+ });
+ });
+
+ it('renders a disclosure dropdown group', () => {
+ expect(findGroup().exists()).toBe(true);
+ });
+
+ it('renders the expected header', () => {
+ expect(wrapper.text()).toContain('Places');
+ });
+
+ it('passes attrs down', () => {
+ const group = findGroup();
+ expect(group.props('bordered')).toBe(true);
+ expect(group.classes()).toContain('test-class');
+ });
+
+ it('renders the links', () => {
+ const itemProps = findItems().wrappers.map((item) => item.props('item'));
+
+ expect(itemProps).toEqual([
+ {
+ text: 'Explore',
+ href: '/explore',
+ },
+ {
+ text: 'Admin area',
+ href: '/admin',
+ },
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js
index 0d34329c60d..6fb9715824f 100644
--- a/spec/frontend/super_sidebar/mock_data.js
+++ b/spec/frontend/super_sidebar/mock_data.js
@@ -73,6 +73,7 @@ export const mergeRequestMenuGroup = [
export const contextSwitcherLinks = [
{ title: 'Explore', link: '/explore', icon: 'compass', link_classes: 'persistent-link-class' },
+ { title: 'Admin area', link: '/admin', icon: 'admin' },
];
export const sidebarData = {
diff --git a/spec/frontend/vue_compat_test_setup.js b/spec/frontend/vue_compat_test_setup.js
index fe43f8f2617..ad1230f2ca9 100644
--- a/spec/frontend/vue_compat_test_setup.js
+++ b/spec/frontend/vue_compat_test_setup.js
@@ -21,6 +21,13 @@ function isLegacyExtendedComponent(component) {
function unwrapLegacyVueExtendComponent(selector) {
return isLegacyExtendedComponent(selector) ? selector.options : selector;
}
+function getStubProps(component) {
+ const stubProps = { ...component.props };
+ component.mixins?.forEach((mixin) => {
+ Object.assign(stubProps, unwrapLegacyVueExtendComponent(mixin).props);
+ });
+ return stubProps;
+}
if (global.document) {
const compatConfig = {
@@ -148,33 +155,27 @@ if (global.document) {
return true;
};
- VTU.config.plugins.createStubs = ({ name, component: rawComponent, registerStub }) => {
+ VTU.config.plugins.createStubs = ({ name, component: rawComponent, registerStub, stubs }) => {
const component = unwrapLegacyVueExtendComponent(rawComponent);
const hyphenatedName = name.replace(/\B([A-Z])/g, '-$1').toLowerCase();
+ const stubTag = stubs?.[name] ? name : hyphenatedName;
const stub = Vue.defineComponent({
name: getComponentName(component),
- props: component.props,
- model: component.model,
+ props: getStubProps(component),
+ model: component.model ?? component.mixins?.find((m) => m.model),
methods: Object.fromEntries(
Object.entries(component.methods ?? {}).map(([key]) => [key, noop]),
),
render() {
- const {
- $slots: slots = {},
- $scopedSlots: scopedSlots = {},
- $parent: parent,
- $vnode: vnode,
- } = this;
-
- const hasStaticDefaultSlot = 'default' in slots && !('default' in scopedSlots);
- const isTheOnlyChild = parent?.$.subTree === vnode;
- // this condition should be altered when https://github.com/vuejs/vue-test-utils/pull/2068 is merged
- // and our codebase will be updated to include it (@vue/test-utils@1.3.6 I assume)
- const shouldRenderAllSlots = !hasStaticDefaultSlot && isTheOnlyChild;
+ const { $scopedSlots: scopedSlots = {} } = this;
+
+ // eslint-disable-next-line no-underscore-dangle
+ const hasDefaultSlot = 'default' in scopedSlots && scopedSlots.default._ns;
+ const shouldRenderAllSlots = !component.functional && !hasDefaultSlot;
const renderSlotByName = (slotName) => {
- const slot = scopedSlots[slotName] || slots[slotName];
+ const slot = scopedSlots[slotName];
let result;
if (typeof slot === 'function') {
try {
@@ -189,16 +190,16 @@ if (global.document) {
};
const slotContents = shouldRenderAllSlots
- ? [...new Set([...Object.keys(slots), ...Object.keys(scopedSlots)])]
- .map(renderSlotByName)
- .filter(Boolean)
+ ? Object.keys(scopedSlots).map(renderSlotByName).filter(Boolean)
: renderSlotByName('default');
const props = Object.fromEntries(
- Object.entries(this.$props).filter(([prop]) => isPropertyValidOnDomNode(prop)),
+ Object.entries(this.$props)
+ .filter(([prop]) => isPropertyValidOnDomNode(prop))
+ .map(([key, value]) => [key, typeof value === 'function' ? '[Function]' : value]),
);
- return Vue.h(`${hyphenatedName || 'anonymous'}-stub`, props, slotContents);
+ return Vue.h(`${stubTag || 'anonymous'}-stub`, props, slotContents);
},
});
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index b9490306410..9f7aaa21f5b 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -2,9 +2,13 @@
require 'spec_helper'
-RSpec.describe Gitlab::HookData::IssueBuilder do
- let_it_be(:label) { create(:label) }
- let_it_be(:issue) { create(:labeled_issue, labels: [label], project: label.project) }
+RSpec.describe Gitlab::HookData::IssueBuilder, feature_category: :webhooks do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:label) { create(:label, project: project) }
+ let_it_be(:issue) { create(:labeled_issue, labels: [label], project: project) }
+ let_it_be(:contact) { create(:contact, group: project.group) }
+ let_it_be(:issue_contact) { create(:issue_customer_relations_contact, issue: issue, contact: contact) }
let(:builder) { described_class.new(issue) }
@@ -50,6 +54,7 @@ RSpec.describe Gitlab::HookData::IssueBuilder do
expect(data).to include(:state)
expect(data).to include(:severity)
expect(data).to include('labels' => [label.hook_attrs])
+ expect(data).to include('customer_relations_contacts' => [contact.reload.hook_attrs])
end
context 'when the issue has an image in the description' do
diff --git a/spec/models/customer_relations/contact_spec.rb b/spec/models/customer_relations/contact_spec.rb
index 3d78a9089ca..6f124662b8e 100644
--- a/spec/models/customer_relations/contact_spec.rb
+++ b/spec/models/customer_relations/contact_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe CustomerRelations::Contact, type: :model do
+RSpec.describe CustomerRelations::Contact, type: :model, feature_category: :team_planning do
let_it_be(:group) { create(:group) }
describe 'associations' do
@@ -280,4 +280,25 @@ RSpec.describe CustomerRelations::Contact, type: :model do
end
end
end
+
+ describe '#hook_attrs' do
+ let_it_be(:contact) { create(:contact, group: group) }
+
+ it 'includes the expected attributes' do
+ expect(contact.hook_attrs).to match a_hash_including(
+ {
+ 'created_at' => contact.created_at,
+ 'description' => contact.description,
+ 'first_name' => contact.first_name,
+ 'group_id' => group.id,
+ 'id' => contact.id,
+ 'last_name' => contact.last_name,
+ 'organization_id' => contact.organization_id,
+ 'state' => contact.state,
+ 'updated_at' => contact.updated_at
+ }
+ )
+ expect(contact.hook_attrs.keys).to match_array(described_class::SAFE_ATTRIBUTES)
+ end
+ end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index a84445f397f..a26ab2501ec 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -188,7 +188,7 @@ RSpec.describe Issue, feature_category: :team_planning do
expect(issue).not_to be_valid
expect(issue.errors[:base])
- .to include(_('A confidential issue cannot have a parent that already has non-confidential children.'))
+ .to include(_('A confidential issue must have only confidential children. Make any child items confidential and try again.'))
end
it 'allows to make child confidential' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index b463199a85b..da3f691b63a 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -3266,6 +3266,15 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
end
end
end
+
+ context 'with check_mergeability_retry_lease option' do
+ it 'call check_mergeability with sync_retry_lease' do
+ allow(subject).to receive(:mergeable_state?) { true }
+ expect(subject).to receive(:check_mergeability).with(sync_retry_lease: true)
+
+ subject.mergeable?(check_mergeability_retry_lease: true)
+ end
+ end
end
describe '#skipped_mergeable_checks' do
@@ -3300,6 +3309,14 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
subject.check_mergeability
end
+ context 'when sync_retry_lease is true' do
+ it 'executes MergeabilityCheckService' do
+ expect(mergeability_service).to receive(:execute).with(retry_lease: true)
+
+ subject.check_mergeability(sync_retry_lease: true)
+ end
+ end
+
context 'when async is true' do
it 'executes MergeabilityCheckService asynchronously' do
expect(mergeability_service).to receive(:async_execute)
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index c77cf288f56..c32b7b16f63 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -577,6 +577,14 @@ RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workf
end
end
end
+
+ context 'when passing `check_mergeability_retry_lease: true` as `options` parameter' do
+ it 'call mergeable? with check_mergeability_retry_lease' do
+ expect(merge_request).to receive(:mergeable?).with(hash_including(check_mergeability_retry_lease: true)).and_call_original
+
+ service.execute(merge_request, check_mergeability_retry_lease: true)
+ end
+ end
end
end
diff --git a/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb b/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb
index 8a1581d513a..a764e751bf5 100644
--- a/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb
+++ b/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb
@@ -4,10 +4,6 @@ RSpec.configure do |config|
config.include ::Ci::PartitioningTesting::PartitionIdentifiers
config.around(:each, :ci_partitionable) do |example|
- unless ::Ci::Build.table_name.to_s.starts_with?('p_')
- skip 'Skipping partitioning tests until `ci_builds` is partitioned'
- end
-
::Ci::PartitioningTesting::SchemaHelpers.with_routing_tables do
example.run
end
diff --git a/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb b/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb
index f31dbb111ee..df653c853b9 100644
--- a/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb
+++ b/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb
@@ -6,16 +6,22 @@ module Ci
module_function
def with_routing_tables
- # model.table_name = :routing_table
+ previous_table_name = CommitStatus.table_name
+ CommitStatus.table_name = :p_ci_builds
+ CommitStatus.descendants.each(&:reset_table_name)
+
yield
- # ensure
- # model.table_name = :regular_table
+
+ ensure
+ CommitStatus.table_name = previous_table_name
+ CommitStatus.descendants.each(&:reset_table_name)
end
def setup(connection: Ci::ApplicationRecord.connection)
each_partitionable_table do |table_name|
create_test_partition("p_#{table_name}", connection: connection)
end
+ ensure_builds_id_uniquness(connection: connection)
end
def teardown(connection: Ci::ApplicationRecord.connection)
@@ -57,6 +63,16 @@ module Ci
SQL
end
+ # This can be removed after https://gitlab.com/gitlab-org/gitlab/-/issues/421173
+ # is implemented
+ def ensure_builds_id_uniquness(connection:)
+ connection.execute(<<~SQL.squish)
+ CREATE TRIGGER assign_p_ci_builds_id_trigger
+ BEFORE INSERT ON #{full_partition_name('ci_builds')}
+ FOR EACH ROW EXECUTE FUNCTION assign_p_ci_builds_id_value();
+ SQL
+ end
+
def table_available?(table_name, connection:)
connection.table_exists?(table_name) &&
connection.column_exists?(table_name, :partition_id)
diff --git a/spec/support/protected_branch_helpers.rb b/spec/support/protected_branch_helpers.rb
index d983d03fd2e..576275e9d1d 100644
--- a/spec/support/protected_branch_helpers.rb
+++ b/spec/support/protected_branch_helpers.rb
@@ -9,6 +9,10 @@ module ProtectedBranchHelpers
end
end
+ def show_add_form
+ click_button 'Add protected branch'
+ end
+
def set_protected_branch_name(branch_name)
find('.js-protected-branch-select').click
find('.dropdown-input-field').set(branch_name)
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
index 2d3f1949716..fb882ef8a23 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb
@@ -7,6 +7,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
it "allows creating protected branches that #{access_type_name} can push to" do
visit project_protected_branches_path(project)
+ show_add_form
set_protected_branch_name('master')
set_allowed_to('merge', no_one)
set_allowed_to('push', access_type_name)
@@ -19,6 +20,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
it "allows creating protected branches that #{access_type_name} can merge to" do
visit project_protected_branches_path(project)
+ show_add_form
set_protected_branch_name('master')
set_allowed_to('merge', access_type_name)
set_allowed_to('push', no_one)
@@ -31,6 +33,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
it "allows updating protected branches so that #{access_type_name} can push to them" do
visit project_protected_branches_path(project)
+ show_add_form
set_protected_branch_name('master')
set_allowed_to('merge', no_one)
set_allowed_to('push', no_one)
@@ -52,6 +55,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
it "allows updating protected branches so that #{access_type_name} can merge to them" do
visit project_protected_branches_path(project)
+ show_add_form
set_protected_branch_name('master')
set_allowed_to('merge', no_one)
set_allowed_to('push', no_one)
diff --git a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb
index 90b0e600228..a15ee47de34 100644
--- a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb
+++ b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb
@@ -20,6 +20,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do
it "shows all dropdown sections in the 'Allowed to push' main dropdown, with only one deploy key" do
visit project_protected_branches_path(project)
+ click_button 'Add protected branch'
find(".js-allowed-to-push").click
wait_for_requests
@@ -35,6 +36,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do
it "shows all sections but not deploy keys in the 'Allowed to merge' main dropdown" do
visit project_protected_branches_path(project)
+ click_button 'Add protected branch'
find(".js-allowed-to-merge").click
wait_for_requests
@@ -65,6 +67,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do
it "just shows all sections but not deploy keys in the 'Allowed to push' dropdown" do
visit project_protected_branches_path(project)
+ click_button 'Add protected branch'
find(".js-allowed-to-push").click
wait_for_requests