diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-11 15:08:32 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-11 15:08:32 +0000 |
commit | f1ce71c88c407709987dd4a7b40bdb7596b6baa2 (patch) | |
tree | 0d20ea80baaf8c11524584f586c2cc763af07cb2 /spec | |
parent | 28e90894e1e6f17320f5b1d2fff6fe736bf65dff (diff) | |
download | gitlab-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.rb | 73 | ||||
-rw-r--r-- | spec/features/issues/issue_sidebar_spec.rb | 2 | ||||
-rw-r--r-- | spec/finders/groups/accepting_project_creations_finder_spec.rb | 119 | ||||
-rw-r--r-- | spec/frontend/api/projects_api_spec.js | 14 | ||||
-rw-r--r-- | spec/frontend/groups/settings/components/group_settings_readme_spec.js | 112 | ||||
-rw-r--r-- | spec/frontend/groups/settings/mock_data.js | 6 | ||||
-rw-r--r-- | spec/frontend/lib/utils/web_ide_navigator_spec.js | 38 | ||||
-rw-r--r-- | spec/frontend/repository/components/fork_info_spec.js | 39 | ||||
-rw-r--r-- | spec/frontend/repository/mock_data.js | 2 | ||||
-rw-r--r-- | spec/helpers/projects_helper_spec.rb | 6 | ||||
-rw-r--r-- | spec/lib/gitlab/import_export/all_models.yml | 3 | ||||
-rw-r--r-- | spec/models/concerns/expirable_spec.rb | 66 | ||||
-rw-r--r-- | spec/models/group_group_link_spec.rb | 128 | ||||
-rw-r--r-- | spec/models/group_spec.rb | 17 | ||||
-rw-r--r-- | spec/views/groups/settings/_general.html.haml_spec.rb | 39 |
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 |