summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-11 15:08:32 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-11 15:08:32 +0000
commitf1ce71c88c407709987dd4a7b40bdb7596b6baa2 (patch)
tree0d20ea80baaf8c11524584f586c2cc763af07cb2 /spec
parent28e90894e1e6f17320f5b1d2fff6fe736bf65dff (diff)
downloadgitlab-ce-f1ce71c88c407709987dd4a7b40bdb7596b6baa2.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/groups/group_settings_spec.rb73
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb2
-rw-r--r--spec/finders/groups/accepting_project_creations_finder_spec.rb119
-rw-r--r--spec/frontend/api/projects_api_spec.js14
-rw-r--r--spec/frontend/groups/settings/components/group_settings_readme_spec.js112
-rw-r--r--spec/frontend/groups/settings/mock_data.js6
-rw-r--r--spec/frontend/lib/utils/web_ide_navigator_spec.js38
-rw-r--r--spec/frontend/repository/components/fork_info_spec.js39
-rw-r--r--spec/frontend/repository/mock_data.js2
-rw-r--r--spec/helpers/projects_helper_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/models/concerns/expirable_spec.rb66
-rw-r--r--spec/models/group_group_link_spec.rb128
-rw-r--r--spec/models/group_spec.rb17
-rw-r--r--spec/views/groups/settings/_general.html.haml_spec.rb39
15 files changed, 572 insertions, 92 deletions
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 88f78b87d08..1a49b0497ba 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Edit group settings', feature_category: :subgroups do
+ include Spec::Support::Helpers::ModalHelpers
+
let(:user) { create(:user) }
let(:group) { create(:group, path: 'foo') }
@@ -244,6 +246,77 @@ RSpec.describe 'Edit group settings', feature_category: :subgroups do
end
end
+ describe 'group README', :js do
+ let_it_be(:group) { create(:group) }
+
+ context 'with gitlab-profile project and README.md' do
+ let_it_be(:project) { create(:project, :readme, namespace: group) }
+
+ it 'renders link to Group README and navigates to it on click' do
+ visit edit_group_path(group)
+ wait_for_requests
+
+ click_link('README')
+ wait_for_requests
+
+ expect(page).to have_current_path(project_blob_path(project, "#{project.default_branch}/README.md"))
+ expect(page).to have_text('README.md')
+ end
+ end
+
+ context 'with gitlab-profile project and no README.md' do
+ let_it_be(:project) { create(:project, name: 'gitlab-profile', namespace: group) }
+
+ it 'renders Add README button and allows user to create a README via the IDE' do
+ visit edit_group_path(group)
+ wait_for_requests
+
+ expect(page).not_to have_selector('.ide')
+
+ click_button('Add README')
+
+ accept_gl_confirm("This will create a README.md for project #{group.readme_project.present.path_with_namespace}.", button_text: 'Add README')
+ wait_for_requests
+
+ expect(page).to have_current_path("/-/ide/project/#{group.readme_project.present.path_with_namespace}/edit/main/-/README.md/")
+
+ page.within('.ide') do
+ expect(page).to have_text('README.md')
+ end
+ end
+ end
+
+ context 'with no gitlab-profile project and no README.md' do
+ it 'renders Add README button and allows user to create both the gitlab-profile project and README via the IDE' do
+ visit edit_group_path(group)
+ wait_for_requests
+
+ expect(page).not_to have_selector('.ide')
+
+ click_button('Add README')
+
+ accept_gl_confirm("This will create a project #{group.full_path}/gitlab-profile and add a README.md.", button_text: 'Create and add README')
+ wait_for_requests
+
+ expect(page).to have_current_path("/-/ide/project/#{group.full_path}/gitlab-profile/edit/main/-/README.md/")
+
+ page.within('.ide') do
+ expect(page).to have_text('README.md')
+ end
+ end
+ end
+
+ describe 'with :show_group_readme FF false' do
+ before do
+ stub_feature_flags(show_group_readme: false)
+ end
+
+ it 'does not render Group README settings' do
+ expect(page).not_to have_text('README')
+ end
+ end
+ end
+
def update_path(new_group_path)
visit edit_group_path(group)
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index d1f8929ee8a..2ae347d4f9e 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -86,7 +86,7 @@ RSpec.describe 'Issue Sidebar', feature_category: :team_planning do
end
within '.js-right-sidebar' do
- find('.block.assignee').click(x: 0, y: 0)
+ find('.block.assignee').click(x: 0, y: 0, offset: 0)
find('.block.assignee .edit-link').click
end
diff --git a/spec/finders/groups/accepting_project_creations_finder_spec.rb b/spec/finders/groups/accepting_project_creations_finder_spec.rb
new file mode 100644
index 00000000000..b1b9403748d
--- /dev/null
+++ b/spec/finders/groups/accepting_project_creations_finder_spec.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::AcceptingProjectCreationsFinder, feature_category: :subgroups do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group_where_direct_owner) { create(:group) }
+ let_it_be(:subgroup_of_group_where_direct_owner) { create(:group, parent: group_where_direct_owner) }
+ let_it_be(:group_where_direct_maintainer) { create(:group) }
+ let_it_be(:group_where_direct_maintainer_but_cant_create_projects) do
+ create(:group, project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS)
+ end
+
+ let_it_be(:group_where_direct_developer_but_developers_cannot_create_projects) { create(:group) }
+ let_it_be(:group_where_direct_developer) do
+ create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS)
+ end
+
+ let_it_be(:shared_with_group_where_direct_owner_as_owner) { create(:group) }
+
+ let_it_be(:shared_with_group_where_direct_owner_as_developer) do
+ create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS)
+ end
+
+ let_it_be(:shared_with_group_where_direct_owner_as_developer_but_developers_cannot_create_projects) do
+ create(:group)
+ end
+
+ let_it_be(:shared_with_group_where_direct_developer_as_maintainer) do
+ create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS)
+ end
+
+ let_it_be(:shared_with_group_where_direct_owner_as_guest) { create(:group) }
+ let_it_be(:shared_with_group_where_direct_owner_as_maintainer) { create(:group) }
+ let_it_be(:shared_with_group_where_direct_developer_as_owner) do
+ create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS)
+ end
+
+ let_it_be(:subgroup_of_shared_with_group_where_direct_owner_as_maintainer) do
+ create(:group, parent: shared_with_group_where_direct_owner_as_maintainer)
+ end
+
+ before do
+ group_where_direct_owner.add_owner(user)
+ group_where_direct_maintainer.add_maintainer(user)
+ group_where_direct_developer_but_developers_cannot_create_projects.add_developer(user)
+ group_where_direct_developer.add_developer(user)
+
+ create(:group_group_link, :owner,
+ shared_with_group: group_where_direct_owner,
+ shared_group: shared_with_group_where_direct_owner_as_owner
+ )
+
+ create(:group_group_link, :developer,
+ shared_with_group: group_where_direct_owner,
+ shared_group: shared_with_group_where_direct_owner_as_developer_but_developers_cannot_create_projects
+ )
+
+ create(:group_group_link, :maintainer,
+ shared_with_group: group_where_direct_developer,
+ shared_group: shared_with_group_where_direct_developer_as_maintainer
+ )
+
+ create(:group_group_link, :developer,
+ shared_with_group: group_where_direct_owner,
+ shared_group: shared_with_group_where_direct_owner_as_developer
+ )
+
+ create(:group_group_link, :guest,
+ shared_with_group: group_where_direct_owner,
+ shared_group: shared_with_group_where_direct_owner_as_guest
+ )
+
+ create(:group_group_link, :maintainer,
+ shared_with_group: group_where_direct_owner,
+ shared_group: shared_with_group_where_direct_owner_as_maintainer
+ )
+
+ create(:group_group_link, :owner,
+ shared_with_group: group_where_direct_developer_but_developers_cannot_create_projects,
+ shared_group: shared_with_group_where_direct_developer_as_owner
+ )
+ end
+
+ describe '#execute' do
+ subject(:result) { described_class.new(user).execute }
+
+ it 'only returns groups where the user has access to create projects' do
+ expect(result).to match_array([
+ group_where_direct_owner,
+ subgroup_of_group_where_direct_owner,
+ group_where_direct_maintainer,
+ group_where_direct_developer,
+ # groups arising from group shares
+ shared_with_group_where_direct_owner_as_owner,
+ shared_with_group_where_direct_owner_as_maintainer,
+ subgroup_of_shared_with_group_where_direct_owner_as_maintainer,
+ shared_with_group_where_direct_developer_as_owner,
+ shared_with_group_where_direct_developer_as_maintainer,
+ shared_with_group_where_direct_owner_as_developer
+ ])
+ end
+
+ context 'when `include_groups_from_group_shares_in_project_creation_locations` flag is disabled' do
+ before do
+ stub_feature_flags(include_groups_from_group_shares_in_project_creation_locations: false)
+ end
+
+ it 'returns only groups accessible via direct membership where user has access to create projects' do
+ expect(result).to match_array([
+ group_where_direct_owner,
+ subgroup_of_group_where_direct_owner,
+ group_where_direct_maintainer,
+ group_where_direct_developer
+ ])
+ end
+ end
+ end
+end
diff --git a/spec/frontend/api/projects_api_spec.js b/spec/frontend/api/projects_api_spec.js
index 2d54d6173fd..4ceed885e6e 100644
--- a/spec/frontend/api/projects_api_spec.js
+++ b/spec/frontend/api/projects_api_spec.js
@@ -67,6 +67,20 @@ describe('~/api/projects_api.js', () => {
});
});
+ describe('createProject', () => {
+ it('posts to the correct URL and returns the data', () => {
+ const body = { name: 'test project' };
+ const expectedUrl = '/api/v7/projects.json';
+ const expectedRes = { id: 999, name: 'test project' };
+
+ mock.onPost(expectedUrl, body).replyOnce(HTTP_STATUS_OK, { data: expectedRes });
+
+ return projectsApi.createProject(body).then(({ data }) => {
+ expect(data).toStrictEqual(expectedRes);
+ });
+ });
+ });
+
describe('importProjectMembers', () => {
beforeEach(() => {
jest.spyOn(axios, 'post');
diff --git a/spec/frontend/groups/settings/components/group_settings_readme_spec.js b/spec/frontend/groups/settings/components/group_settings_readme_spec.js
new file mode 100644
index 00000000000..8d4da73934f
--- /dev/null
+++ b/spec/frontend/groups/settings/components/group_settings_readme_spec.js
@@ -0,0 +1,112 @@
+import { GlModal, GlSprintf } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import GroupSettingsReadme from '~/groups/settings/components/group_settings_readme.vue';
+import { GITLAB_README_PROJECT } from '~/groups/settings/constants';
+import {
+ MOCK_GROUP_PATH,
+ MOCK_GROUP_ID,
+ MOCK_PATH_TO_GROUP_README,
+ MOCK_PATH_TO_README_PROJECT,
+} from '../mock_data';
+
+describe('GroupSettingsReadme', () => {
+ let wrapper;
+
+ const defaultProps = {
+ groupPath: MOCK_GROUP_PATH,
+ groupId: MOCK_GROUP_ID,
+ };
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(GroupSettingsReadme, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ stubs: {
+ GlModal,
+ GlSprintf,
+ },
+ });
+ };
+
+ const findHasReadmeButtonLink = () => wrapper.findByText('README');
+ const findAddReadmeButton = () => wrapper.findByTestId('group-settings-add-readme-button');
+ const findModalBody = () => wrapper.findByTestId('group-settings-modal-readme-body');
+ const findModalCreateReadmeButton = () =>
+ wrapper.findByTestId('group-settings-modal-create-readme-button');
+
+ describe('Group has existing README', () => {
+ beforeEach(() => {
+ createComponent({
+ groupReadmePath: MOCK_PATH_TO_GROUP_README,
+ readmeProjectPath: MOCK_PATH_TO_README_PROJECT,
+ });
+ });
+
+ describe('template', () => {
+ it('renders README Button Link with correct path and text', () => {
+ expect(findHasReadmeButtonLink().exists()).toBe(true);
+ expect(findHasReadmeButtonLink().attributes('href')).toBe(MOCK_PATH_TO_GROUP_README);
+ });
+
+ it('does not render Add README Button', () => {
+ expect(findAddReadmeButton().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('Group has README project without README file', () => {
+ beforeEach(() => {
+ createComponent({ readmeProjectPath: MOCK_PATH_TO_README_PROJECT });
+ });
+
+ describe('template', () => {
+ it('does not render README', () => {
+ expect(findHasReadmeButtonLink().exists()).toBe(false);
+ });
+
+ it('does render Add Readme Button with correct text', () => {
+ expect(findAddReadmeButton().exists()).toBe(true);
+ expect(findAddReadmeButton().text()).toBe('Add README');
+ });
+
+ it('generates a hidden modal with correct body text', () => {
+ expect(findModalBody().text()).toMatchInterpolatedText(
+ `This will create a README.md for project ${MOCK_PATH_TO_README_PROJECT}.`,
+ );
+ });
+
+ it('generates a hidden modal with correct button text', () => {
+ expect(findModalCreateReadmeButton().text()).toBe('Add README');
+ });
+ });
+ });
+
+ describe('Group does not have README project', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ describe('template', () => {
+ it('does not render README', () => {
+ expect(findHasReadmeButtonLink().exists()).toBe(false);
+ });
+
+ it('does render Add Readme Button with correct text', () => {
+ expect(findAddReadmeButton().exists()).toBe(true);
+ expect(findAddReadmeButton().text()).toBe('Add README');
+ });
+
+ it('generates a hidden modal with correct body text', () => {
+ expect(findModalBody().text()).toMatchInterpolatedText(
+ `This will create a project ${MOCK_GROUP_PATH}/${GITLAB_README_PROJECT} and add a README.md.`,
+ );
+ });
+
+ it('generates a hidden modal with correct button text', () => {
+ expect(findModalCreateReadmeButton().text()).toBe('Create and add README');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/groups/settings/mock_data.js b/spec/frontend/groups/settings/mock_data.js
new file mode 100644
index 00000000000..4551ee3318b
--- /dev/null
+++ b/spec/frontend/groups/settings/mock_data.js
@@ -0,0 +1,6 @@
+export const MOCK_GROUP_PATH = 'test-group';
+export const MOCK_GROUP_ID = '999';
+
+export const MOCK_PATH_TO_GROUP_README = '/group/project/-/blob/main/README.md';
+
+export const MOCK_PATH_TO_README_PROJECT = 'group/project';
diff --git a/spec/frontend/lib/utils/web_ide_navigator_spec.js b/spec/frontend/lib/utils/web_ide_navigator_spec.js
new file mode 100644
index 00000000000..0f5cd09d50e
--- /dev/null
+++ b/spec/frontend/lib/utils/web_ide_navigator_spec.js
@@ -0,0 +1,38 @@
+import { visitUrl, webIDEUrl } from '~/lib/utils/url_utility';
+import { openWebIDE } from '~/lib/utils/web_ide_navigator';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn(),
+ webIDEUrl: jest.fn().mockImplementation((path) => `/-/ide/projects${path}`),
+}));
+
+describe('openWebIDE', () => {
+ it('when called without projectPath throws TypeError and does not call visitUrl', () => {
+ expect(() => {
+ openWebIDE();
+ }).toThrow(new TypeError('projectPath parameter is required'));
+ expect(visitUrl).not.toHaveBeenCalled();
+ });
+
+ it('when called with projectPath and without fileName calls visitUrl with correct path', () => {
+ const params = { projectPath: 'project-path' };
+ const expectedNonIDEPath = `/${params.projectPath}/edit/main/-/`;
+ const expectedIDEPath = `/-/ide/projects${expectedNonIDEPath}`;
+
+ openWebIDE(params.projectPath);
+
+ expect(webIDEUrl).toHaveBeenCalledWith(expectedNonIDEPath);
+ expect(visitUrl).toHaveBeenCalledWith(expectedIDEPath);
+ });
+
+ it('when called with projectPath and fileName calls visitUrl with correct path', () => {
+ const params = { projectPath: 'project-path', fileName: 'README' };
+ const expectedNonIDEPath = `/${params.projectPath}/edit/main/-/${params.fileName}/`;
+ const expectedIDEPath = `/-/ide/projects${expectedNonIDEPath}`;
+
+ openWebIDE(params.projectPath, params.fileName);
+
+ expect(webIDEUrl).toHaveBeenCalledWith(expectedNonIDEPath);
+ expect(visitUrl).toHaveBeenCalledWith(expectedIDEPath);
+ });
+});
diff --git a/spec/frontend/repository/components/fork_info_spec.js b/spec/frontend/repository/components/fork_info_spec.js
index 4fadedf6b83..75310ba8ca4 100644
--- a/spec/frontend/repository/components/fork_info_spec.js
+++ b/spec/frontend/repository/components/fork_info_spec.js
@@ -84,7 +84,8 @@ describe('ForkInfo component', () => {
const findLink = () => wrapper.findComponent(GlLink);
const findSkeleton = () => wrapper.findComponent(GlSkeletonLoader);
const findIcon = () => wrapper.findComponent(GlIcon);
- const findUpdateForkButton = () => wrapper.findComponent(GlButton);
+ const findUpdateForkButton = () => wrapper.findByTestId('update-fork-button');
+ const findCreateMrButton = () => wrapper.findByTestId('create-mr-button');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findDivergenceMessage = () => wrapper.findByTestId('divergence-message');
const findInaccessibleMessage = () => wrapper.findByTestId('inaccessible-project');
@@ -139,6 +140,16 @@ describe('ForkInfo component', () => {
expect(link.attributes('href')).toBe(propsForkInfo.sourcePath);
});
+ it('renders Create MR Button with correct path', async () => {
+ await createComponent();
+ expect(findCreateMrButton().attributes('href')).toBe(propsForkInfo.createMrPath);
+ });
+
+ it('does not render create MR button if user had no permission to Create MR in fork', async () => {
+ await createComponent({ canUserCreateMrInFork: false });
+ expect(findCreateMrButton().exists()).toBe(false);
+ });
+
it('renders alert with error message when request fails', async () => {
mockForkDetailsQuery.mockRejectedValue(forkInfoError);
await createComponent({});
@@ -170,7 +181,7 @@ describe('ForkInfo component', () => {
});
await createComponent({});
expect(findUpdateForkButton().exists()).toBe(true);
- expect(findUpdateForkButton().text()).toBe(i18n.sync);
+ expect(findUpdateForkButton().text()).toBe(i18n.updateFork);
});
});
@@ -211,7 +222,8 @@ describe('ForkInfo component', () => {
message: '3 commits behind, 7 commits ahead of the upstream repository.',
firstLink: propsForkInfo.behindComparePath,
secondLink: propsForkInfo.aheadComparePath,
- hasButton: true,
+ hasUpdateButton: true,
+ hasCreateMrButton: true,
},
{
ahead: 7,
@@ -219,7 +231,8 @@ describe('ForkInfo component', () => {
message: '7 commits ahead of the upstream repository.',
firstLink: propsForkInfo.aheadComparePath,
secondLink: '',
- hasButton: false,
+ hasUpdateButton: false,
+ hasCreateMrButton: true,
},
{
ahead: 0,
@@ -227,11 +240,12 @@ describe('ForkInfo component', () => {
message: '3 commits behind the upstream repository.',
firstLink: propsForkInfo.behindComparePath,
secondLink: '',
- hasButton: true,
+ hasUpdateButton: true,
+ hasCreateMrButton: false,
},
])(
'renders correct divergence message for ahead: $ahead, behind: $behind divergence commits',
- ({ ahead, behind, message, firstLink, secondLink, hasButton }) => {
+ ({ ahead, behind, message, firstLink, secondLink, hasUpdateButton, hasCreateMrButton }) => {
beforeEach(async () => {
mockResolvedForkDetailsQuery({ ahead, behind, isSyncing: false, hasConflicts: false });
await createComponent({});
@@ -251,9 +265,16 @@ describe('ForkInfo component', () => {
});
it('renders Update Fork button when fork is behind', () => {
- expect(findUpdateForkButton().exists()).toBe(hasButton);
- if (hasButton) {
- expect(findUpdateForkButton().text()).toBe(i18n.sync);
+ expect(findUpdateForkButton().exists()).toBe(hasUpdateButton);
+ if (hasUpdateButton) {
+ expect(findUpdateForkButton().text()).toBe(i18n.updateFork);
+ }
+ });
+
+ it('renders Create Merge Request button when fork is ahead', () => {
+ expect(findCreateMrButton().exists()).toBe(hasCreateMrButton);
+ if (hasCreateMrButton) {
+ expect(findCreateMrButton().text()).toBe(i18n.createMergeRequest);
}
});
},
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index 418a93a10cc..c7cc2820588 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -125,6 +125,8 @@ export const propsForkInfo = {
sourcePath: 'gitlab-org/gitlab',
aheadComparePath: '/nataliia/myGitLab/-/compare/main...ref?from_project_id=1',
behindComparePath: 'gitlab-org/gitlab/-/compare/ref...main?from_project_id=2',
+ createMrPath: 'path/to/new/mr',
+ canUserCreateMrInFork: true,
};
export const propsConflictsModal = {
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index ff9662f672b..9d28147bd74 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -1363,11 +1363,13 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
source_project = project_with_repo
allow(helper).to receive(:visible_fork_source).with(project).and_return(source_project)
+ allow(helper).to receive(:can_user_create_mr_in_fork).with(source_project).and_return(false)
ahead_path =
"/#{project.full_path}/-/compare/#{source_project.default_branch}...ref?from_project_id=#{source_project.id}"
behind_path =
"/#{source_project.full_path}/-/compare/ref...#{source_project.default_branch}?from_project_id=#{project.id}"
+ create_mr_path = "/#{project.full_path}/-/merge_requests/new?merge_request%5Bsource_branch%5D=ref&merge_request%5Btarget_branch%5D=#{source_project.default_branch}&merge_request%5Btarget_project_id%5D=#{source_project.id}"
expect(helper.vue_fork_divergence_data(project, 'ref')).to eq({
project_path: project.full_path,
@@ -1376,7 +1378,9 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do
source_path: project_path(source_project),
ahead_compare_path: ahead_path,
behind_compare_path: behind_path,
- source_default_branch: source_project.default_branch
+ source_default_branch: source_project.default_branch,
+ create_mr_path: create_mr_path,
+ can_user_create_mr_in_fork: false
})
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 715749a4195..4a300cc45b5 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -216,7 +216,7 @@ merge_requests:
- approver_groups
- approved_by_users
- draft_notes
-- merge_train
+- merge_train_car
- blocks_as_blocker
- blocks_as_blockee
- blocking_merge_requests
@@ -873,6 +873,7 @@ incident_management_setting:
- project
merge_trains:
- project
+merge_train_cars:
- merge_request
boards:
- group
diff --git a/spec/models/concerns/expirable_spec.rb b/spec/models/concerns/expirable_spec.rb
index 50dfb138ac9..68a25917ce1 100644
--- a/spec/models/concerns/expirable_spec.rb
+++ b/spec/models/concerns/expirable_spec.rb
@@ -3,40 +3,52 @@
require 'spec_helper'
RSpec.describe Expirable do
- describe 'ProjectMember' do
- let_it_be(:no_expire) { create(:project_member) }
- let_it_be(:expire_later) { create(:project_member, expires_at: 8.days.from_now) }
- let_it_be(:expired) { create(:project_member, expires_at: 1.day.from_now) }
+ let_it_be(:no_expire) { create(:project_member) }
+ let_it_be(:expire_later) { create(:project_member, expires_at: 8.days.from_now) }
+ let_it_be(:expired) { create(:project_member, expires_at: 1.day.from_now) }
- before do
- travel_to(3.days.from_now)
- end
+ before do
+ travel_to(3.days.from_now)
+ end
- describe '.expired' do
- it { expect(ProjectMember.expired).to match_array([expired]) }
- end
+ describe '.expired' do
+ it { expect(ProjectMember.expired).to match_array([expired]) }
- describe '.not_expired' do
- it { expect(ProjectMember.not_expired).to include(no_expire, expire_later) }
- it { expect(ProjectMember.not_expired).not_to include(expired) }
- end
+ it 'scopes the query when multiple models are expirable' do
+ expired_access_token = create(:personal_access_token, :expired, user: no_expire.user)
- describe '#expired?' do
- it { expect(no_expire.expired?).to eq(false) }
- it { expect(expire_later.expired?).to eq(false) }
- it { expect(expired.expired?).to eq(true) }
+ expect(PersonalAccessToken.expired.joins(user: :members)).to match_array([expired_access_token])
+ expect(PersonalAccessToken.joins(user: :members).merge(ProjectMember.expired)).to eq([])
end
- describe '#expires?' do
- it { expect(no_expire.expires?).to eq(false) }
- it { expect(expire_later.expires?).to eq(true) }
- it { expect(expired.expires?).to eq(true) }
- end
+ it 'works with a timestamp expired_at field', time_travel_to: '2022-03-14T11:30:00Z' do
+ expired_deploy_token = create(:deploy_token, expires_at: 5.minutes.ago.iso8601)
- describe '#expires_soon?' do
- it { expect(no_expire.expires_soon?).to eq(false) }
- it { expect(expire_later.expires_soon?).to eq(true) }
- it { expect(expired.expires_soon?).to eq(true) }
+ # Here verify that `expires_at` in the SQL uses `Time.current` instead of `Date.current`
+ expect(DeployToken.expired).to match_array([expired_deploy_token])
end
end
+
+ describe '.not_expired' do
+ it { expect(ProjectMember.not_expired).to include(no_expire, expire_later) }
+ it { expect(ProjectMember.not_expired).not_to include(expired) }
+ end
+
+ describe '#expired?' do
+ it { expect(no_expire.expired?).to eq(false) }
+ it { expect(expire_later.expired?).to eq(false) }
+ it { expect(expired.expired?).to eq(true) }
+ end
+
+ describe '#expires?' do
+ it { expect(no_expire.expires?).to eq(false) }
+ it { expect(expire_later.expires?).to eq(true) }
+ it { expect(expired.expires?).to eq(true) }
+ end
+
+ describe '#expires_soon?' do
+ it { expect(no_expire.expires_soon?).to eq(false) }
+ it { expect(expire_later.expires_soon?).to eq(true) }
+ it { expect(expired.expires_soon?).to eq(true) }
+ end
end
diff --git a/spec/models/group_group_link_spec.rb b/spec/models/group_group_link_spec.rb
index eec8fe0ef71..59370cf12d2 100644
--- a/spec/models/group_group_link_spec.rb
+++ b/spec/models/group_group_link_spec.rb
@@ -5,9 +5,29 @@ require 'spec_helper'
RSpec.describe GroupGroupLink do
let_it_be(:group) { create(:group) }
let_it_be(:shared_group) { create(:group) }
- let_it_be(:group_group_link) do
- create(:group_group_link, shared_group: shared_group,
- shared_with_group: group)
+
+ describe 'validation' do
+ let_it_be(:group_group_link) do
+ create(:group_group_link, shared_group: shared_group,
+ shared_with_group: group)
+ end
+
+ it { is_expected.to validate_presence_of(:shared_group) }
+
+ it do
+ is_expected.to(
+ validate_uniqueness_of(:shared_group_id)
+ .scoped_to(:shared_with_group_id)
+ .with_message('The group has already been shared with this group'))
+ end
+
+ it { is_expected.to validate_presence_of(:shared_with_group) }
+ it { is_expected.to validate_presence_of(:group_access) }
+
+ it do
+ is_expected.to(
+ validate_inclusion_of(:group_access).in_array(Gitlab::Access.values))
+ end
end
describe 'relations' do
@@ -16,42 +36,51 @@ RSpec.describe GroupGroupLink do
end
describe 'scopes' do
- describe '.non_guests' do
- let!(:group_group_link_reporter) { create :group_group_link, :reporter }
- let!(:group_group_link_maintainer) { create :group_group_link, :maintainer }
- let!(:group_group_link_owner) { create :group_group_link, :owner }
- let!(:group_group_link_guest) { create :group_group_link, :guest }
-
- it 'returns all records which are greater than Guests access' do
- expect(described_class.non_guests).to match_array([
- group_group_link_reporter, group_group_link,
- group_group_link_maintainer, group_group_link_owner
- ])
- end
- end
-
- describe '.with_owner_or_maintainer_access' do
+ context 'for scopes fetching records based on access levels' do
+ let_it_be(:group_group_link_guest) { create :group_group_link, :guest }
+ let_it_be(:group_group_link_reporter) { create :group_group_link, :reporter }
+ let_it_be(:group_group_link_developer) { create :group_group_link, :developer }
let_it_be(:group_group_link_maintainer) { create :group_group_link, :maintainer }
let_it_be(:group_group_link_owner) { create :group_group_link, :owner }
- let_it_be(:group_group_link_reporter) { create :group_group_link, :reporter }
- let_it_be(:group_group_link_guest) { create :group_group_link, :guest }
- it 'returns all records which have OWNER or MAINTAINER access' do
- expect(described_class.with_owner_or_maintainer_access).to match_array([
- group_group_link_maintainer,
- group_group_link_owner
- ])
+ describe '.non_guests' do
+ it 'returns all records which are greater than Guests access' do
+ expect(described_class.non_guests).to match_array([
+ group_group_link_reporter, group_group_link_developer,
+ group_group_link_maintainer, group_group_link_owner
+ ])
+ end
end
- end
- describe '.with_owner_access' do
- let_it_be(:group_group_link_maintainer) { create :group_group_link, :maintainer }
- let_it_be(:group_group_link_owner) { create :group_group_link, :owner }
- let_it_be(:group_group_link_reporter) { create :group_group_link, :reporter }
- let_it_be(:group_group_link_guest) { create :group_group_link, :guest }
+ describe '.with_owner_or_maintainer_access' do
+ it 'returns all records which have OWNER or MAINTAINER access' do
+ expect(described_class.with_owner_or_maintainer_access).to match_array([
+ group_group_link_maintainer,
+ group_group_link_owner
+ ])
+ end
+ end
- it 'returns all records which have OWNER access' do
- expect(described_class.with_owner_access).to match_array([group_group_link_owner])
+ describe '.with_owner_access' do
+ it 'returns all records which have OWNER access' do
+ expect(described_class.with_owner_access).to match_array([group_group_link_owner])
+ end
+ end
+
+ describe '.with_developer_access' do
+ it 'returns all records which have DEVELOPER access' do
+ expect(described_class.with_developer_access).to match_array([group_group_link_developer])
+ end
+ end
+
+ describe '.with_developer_maintainer_owner_access' do
+ it 'returns all records which have DEVELOPER, MAINTAINER or OWNER access' do
+ expect(described_class.with_developer_maintainer_owner_access).to match_array([
+ group_group_link_developer,
+ group_group_link_owner,
+ group_group_link_maintainer
+ ])
+ end
end
end
@@ -93,6 +122,15 @@ RSpec.describe GroupGroupLink do
let_it_be(:sub_shared_group) { create(:group, parent: shared_group) }
let_it_be(:other_group) { create(:group) }
+ let_it_be(:group_group_link_1) do
+ create(
+ :group_group_link,
+ shared_group: shared_group,
+ shared_with_group: group,
+ group_access: Gitlab::Access::DEVELOPER
+ )
+ end
+
let_it_be(:group_group_link_2) do
create(
:group_group_link,
@@ -125,7 +163,7 @@ RSpec.describe GroupGroupLink do
expect(described_class.all.count).to eq(4)
expect(distinct_group_group_links.count).to eq(2)
- expect(distinct_group_group_links).to include(group_group_link)
+ expect(distinct_group_group_links).to include(group_group_link_1)
expect(distinct_group_group_links).not_to include(group_group_link_2)
expect(distinct_group_group_links).not_to include(group_group_link_3)
expect(distinct_group_group_links).to include(group_group_link_4)
@@ -133,27 +171,9 @@ RSpec.describe GroupGroupLink do
end
end
- describe 'validation' do
- it { is_expected.to validate_presence_of(:shared_group) }
-
- it do
- is_expected.to(
- validate_uniqueness_of(:shared_group_id)
- .scoped_to(:shared_with_group_id)
- .with_message('The group has already been shared with this group'))
- end
-
- it { is_expected.to validate_presence_of(:shared_with_group) }
- it { is_expected.to validate_presence_of(:group_access) }
-
- it do
- is_expected.to(
- validate_inclusion_of(:group_access).in_array(Gitlab::Access.values))
- end
- end
-
describe '#human_access' do
it 'delegates to Gitlab::Access' do
+ group_group_link = create(:group_group_link, :reporter)
expect(Gitlab::Access).to receive(:human_access).with(group_group_link.group_access)
group_group_link.human_access
@@ -161,6 +181,8 @@ RSpec.describe GroupGroupLink do
end
describe 'search by group name' do
+ let_it_be(:group_group_link) { create(:group_group_link, :reporter, shared_with_group: group) }
+
it { expect(described_class.search(group.name)).to eq([group_group_link]) }
it { expect(described_class.search('not-a-group-name')).to be_empty }
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 8fd9cb0be28..0bf4540f535 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -969,6 +969,23 @@ RSpec.describe Group, feature_category: :subgroups do
end
end
+ describe '.with_project_creation_levels' do
+ let_it_be(:group_1) { create(:group, project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS) }
+ let_it_be(:group_2) { create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
+ let_it_be(:group_3) { create(:group, project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS) }
+ let_it_be(:group_4) { create(:group, project_creation_level: nil) }
+
+ it 'returns groups with the specified project creation levels' do
+ result = described_class.with_project_creation_levels([
+ Gitlab::Access::NO_ONE_PROJECT_ACCESS,
+ Gitlab::Access::MAINTAINER_PROJECT_ACCESS
+ ])
+
+ expect(result).to include(group_1, group_3)
+ expect(result).not_to include(group_2, group_4)
+ end
+ end
+
describe '.project_creation_allowed' do
let_it_be(:group_1) { create(:group, project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS) }
let_it_be(:group_2) { create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
diff --git a/spec/views/groups/settings/_general.html.haml_spec.rb b/spec/views/groups/settings/_general.html.haml_spec.rb
new file mode 100644
index 00000000000..d58e25c0d99
--- /dev/null
+++ b/spec/views/groups/settings/_general.html.haml_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'groups/settings/_general.html.haml', feature_category: :subgroups do
+ describe 'Group Settings README' do
+ let_it_be(:group) { build_stubbed(:group) }
+ let_it_be(:user) { build_stubbed(:admin) }
+
+ before do
+ assign(:group, group)
+ allow(view).to receive(:current_user).and_return(user)
+ end
+
+ describe 'with :show_group_readme FF true' do
+ before do
+ stub_feature_flags(show_group_readme: true)
+ end
+
+ it 'renders #js-group-settings-readme' do
+ render
+
+ expect(rendered).to have_selector('#js-group-settings-readme')
+ end
+ end
+
+ describe 'with :show_group_readme FF false' do
+ before do
+ stub_feature_flags(show_group_readme: false)
+ end
+
+ it 'does not render #js-group-settings-readme' do
+ render
+
+ expect(rendered).not_to have_selector('#js-group-settings-readme')
+ end
+ end
+ end
+end