diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-11 15:10:59 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-11 15:10:59 +0300 |
commit | 4d68d8b937211e5cdcf58443ddf693f0f1d13794 (patch) | |
tree | 8f849136bb2bc35efab022a3d62cc48a7a119be7 /spec | |
parent | 564919dfc6c6b352163d4c6dc01827a5f12ffc88 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/controllers/projects/raw_controller_spec.rb | 12 | ||||
-rw-r--r-- | spec/frontend/invite_members/components/invite_members_modal_spec.js | 59 | ||||
-rw-r--r-- | spec/frontend/syntax_highlight_spec.js | 63 | ||||
-rw-r--r-- | spec/requests/api/projects_spec.rb | 53 | ||||
-rw-r--r-- | spec/requests/api/repositories_spec.rb | 12 | ||||
-rw-r--r-- | spec/services/members/import_project_team_service_spec.rb | 91 | ||||
-rw-r--r-- | spec/support/shared_examples/namespaces/traversal_scope_examples.rb | 17 |
7 files changed, 274 insertions, 33 deletions
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 5dee36ee7c2..2c25c7e20ea 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -33,15 +33,25 @@ RSpec.describe Projects::RawController do end context 'regular filename' do - let(:filepath) { 'master/README.md' } + let(:filepath) { 'master/CONTRIBUTING.md' } it 'delivers ASCII file' do + allow(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original + subject expect(response).to have_gitlab_http_status(:ok) expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true' expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:') + + expect(Gitlab::Workhorse).to have_received(:send_git_blob) do |repository, blob| + expected_blob = project.repository.blob_at('master', 'CONTRIBUTING.md') + + expect(repository).to eq(project.repository) + expect(blob.id).to eq(expected_blob.id) + expect(blob).to be_truncated + end end it_behaves_like 'project cache control headers' diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js index 702dc20619d..95b1c55b82d 100644 --- a/spec/frontend/invite_members/components/invite_members_modal_spec.js +++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js @@ -83,7 +83,7 @@ const createComponent = (data = {}, props = {}) => { GlDropdownItem: true, GlSprintf, GlFormGroup: stubComponent(GlFormGroup, { - props: ['state', 'invalidFeedback'], + props: ['state', 'invalidFeedback', 'description'], }), }, }); @@ -125,8 +125,10 @@ describe('InviteMembersModal', () => { const findCancelButton = () => wrapper.findByTestId('cancel-button'); const findInviteButton = () => wrapper.findByTestId('invite-button'); const clickInviteButton = () => findInviteButton().vm.$emit('click'); + const clickCancelButton = () => findCancelButton().vm.$emit('click'); const findMembersFormGroup = () => wrapper.findByTestId('members-form-group'); const membersFormGroupInvalidFeedback = () => findMembersFormGroup().props('invalidFeedback'); + const membersFormGroupDescription = () => findMembersFormGroup().props('description'); const findMembersSelect = () => wrapper.findComponent(MembersTokenSelect); const findAreaofFocusCheckBoxGroup = () => wrapper.findComponent(GlFormCheckboxGroup); @@ -189,13 +191,14 @@ describe('InviteMembersModal', () => { }); }); - describe('displaying the correct introText', () => { + describe('displaying the correct introText and form group description', () => { describe('when inviting to a project', () => { describe('when inviting members', () => { it('includes the correct invitee, type, and formatted name', () => { createInviteMembersToProjectWrapper(); expect(findIntroText()).toBe("You're inviting members to the test name project."); + expect(membersFormGroupDescription()).toBe('Select members or type email addresses'); }); }); @@ -204,6 +207,7 @@ describe('InviteMembersModal', () => { createInviteGroupToProjectWrapper(); expect(findIntroText()).toBe("You're inviting a group to the test name project."); + expect(membersFormGroupDescription()).toBe(''); }); }); }); @@ -214,6 +218,7 @@ describe('InviteMembersModal', () => { createInviteMembersToGroupWrapper(); expect(findIntroText()).toBe("You're inviting members to the test name group."); + expect(membersFormGroupDescription()).toBe('Select members or type email addresses'); }); }); @@ -222,6 +227,7 @@ describe('InviteMembersModal', () => { createInviteGroupToGroupWrapper(); expect(findIntroText()).toBe("You're inviting a group to the test name group."); + expect(membersFormGroupDescription()).toBe(''); }); }); }); @@ -321,6 +327,50 @@ describe('InviteMembersModal', () => { expect(findInviteButton().props('loading')).toBe(false); }); + describe('clearing the invalid state and message', () => { + beforeEach(async () => { + mockMembersApi(httpStatus.CONFLICT, membersApiResponse.MEMBER_ALREADY_EXISTS); + + clickInviteButton(); + + await waitForPromises(); + }); + + it('clears the error when the list of members to invite is cleared', async () => { + expect(membersFormGroupInvalidFeedback()).toBe('Member already exists'); + expect(findMembersFormGroup().props('state')).toBe(false); + expect(findMembersSelect().props('validationState')).toBe(false); + + findMembersSelect().vm.$emit('clear'); + + await wrapper.vm.$nextTick(); + + expect(membersFormGroupInvalidFeedback()).toBe(''); + expect(findMembersFormGroup().props('state')).not.toBe(false); + expect(findMembersSelect().props('validationState')).not.toBe(false); + }); + + it('clears the error when the cancel button is clicked', async () => { + clickCancelButton(); + + await wrapper.vm.$nextTick(); + + expect(membersFormGroupInvalidFeedback()).toBe(''); + expect(findMembersFormGroup().props('state')).not.toBe(false); + expect(findMembersSelect().props('validationState')).not.toBe(false); + }); + + it('clears the error when the modal is hidden', async () => { + wrapper.findComponent(GlModal).vm.$emit('hide'); + + await wrapper.vm.$nextTick(); + + expect(membersFormGroupInvalidFeedback()).toBe(''); + expect(findMembersFormGroup().props('state')).not.toBe(false); + expect(findMembersSelect().props('validationState')).not.toBe(false); + }); + }); + it('clears the invalid state and message once the list of members to invite is cleared', async () => { mockMembersApi(httpStatus.CONFLICT, membersApiResponse.MEMBER_ALREADY_EXISTS); @@ -627,9 +677,9 @@ describe('InviteMembersModal', () => { describe('when sharing the group fails', () => { beforeEach(() => { - createComponent({ groupToBeSharedWith: sharedGroup }); + createInviteGroupToGroupWrapper(); - wrapper.setData({ inviteeType: 'group' }); + wrapper.setData({ groupToBeSharedWith: sharedGroup }); wrapper.vm.$toast = { show: jest.fn() }; jest @@ -641,6 +691,7 @@ describe('InviteMembersModal', () => { it('displays the generic error message', () => { expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong'); + expect(membersFormGroupDescription()).toBe(''); }); }); }); diff --git a/spec/frontend/syntax_highlight_spec.js b/spec/frontend/syntax_highlight_spec.js index 418679e7d18..8ad4f8d5c70 100644 --- a/spec/frontend/syntax_highlight_spec.js +++ b/spec/frontend/syntax_highlight_spec.js @@ -10,39 +10,50 @@ describe('Syntax Highlighter', () => { } return (window.gon.user_color_scheme = value); }; - describe('on a js-syntax-highlight element', () => { - beforeEach(() => { - setFixtures('<div class="js-syntax-highlight"></div>'); - }); - - it('applies syntax highlighting', () => { - stubUserColorScheme('monokai'); - syntaxHighlight($('.js-syntax-highlight')); - expect($('.js-syntax-highlight')).toHaveClass('monokai'); + // We have to bind `document.querySelectorAll` to `document` to not mess up the fn's context + describe.each` + desc | fn + ${'jquery'} | ${$} + ${'vanilla all'} | ${document.querySelectorAll.bind(document)} + ${'vanilla single'} | ${document.querySelector.bind(document)} + `('highlight using $desc syntax', ({ fn }) => { + describe('on a js-syntax-highlight element', () => { + beforeEach(() => { + setFixtures('<div class="js-syntax-highlight"></div>'); + }); + + it('applies syntax highlighting', () => { + stubUserColorScheme('monokai'); + syntaxHighlight(fn('.js-syntax-highlight')); + + expect(fn('.js-syntax-highlight')).toHaveClass('monokai'); + }); }); - }); - describe('on a parent element', () => { - beforeEach(() => { - setFixtures( - '<div class="parent">\n <div class="js-syntax-highlight"></div>\n <div class="foo"></div>\n <div class="js-syntax-highlight"></div>\n</div>', - ); - }); + describe('on a parent element', () => { + beforeEach(() => { + setFixtures( + '<div class="parent">\n <div class="js-syntax-highlight"></div>\n <div class="foo"></div>\n <div class="js-syntax-highlight"></div>\n</div>', + ); + }); - it('applies highlighting to all applicable children', () => { - stubUserColorScheme('monokai'); - syntaxHighlight($('.parent')); + it('applies highlighting to all applicable children', () => { + stubUserColorScheme('monokai'); + syntaxHighlight(fn('.parent')); - expect($('.parent, .foo')).not.toHaveClass('monokai'); - expect($('.monokai').length).toBe(2); - }); + expect(fn('.parent')).not.toHaveClass('monokai'); + expect(fn('.foo')).not.toHaveClass('monokai'); + + expect(document.querySelectorAll('.monokai').length).toBe(2); + }); - it('prevents an infinite loop when no matches exist', () => { - setFixtures('<div></div>'); - const highlight = () => syntaxHighlight($('div')); + it('prevents an infinite loop when no matches exist', () => { + setFixtures('<div></div>'); + const highlight = () => syntaxHighlight(fn('div')); - expect(highlight).not.toThrow(); + expect(highlight).not.toThrow(); + }); }); }); }); diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 288f8480a5b..3622eedfed5 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -3037,6 +3037,59 @@ RSpec.describe API::Projects do end end + describe 'POST /projects/:id/import_project_members/:project_id' do + let_it_be(:project2) { create(:project) } + let_it_be(:project2_user) { create(:user) } + + before_all do + project.add_maintainer(user) + project2.add_maintainer(user) + project2.add_developer(project2_user) + end + + it 'returns 200 when it successfully imports members from another project' do + expect do + post api("/projects/#{project.id}/import_project_members/#{project2.id}", user) + end.to change { project.members.count }.by(2) + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['message']).to eq('Successfully imported') + end + + it 'returns 404 if the source project does not exist' do + expect do + post api("/projects/#{project.id}/import_project_members/#{non_existing_record_id}", user) + end.not_to change { project.members.count } + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns 404 if the target project members cannot be administered by the requester' do + private_project = create(:project, :private) + + expect do + post api("/projects/#{private_project.id}/import_project_members/#{project2.id}", user) + end.not_to change { project.members.count } + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns 422 if the import failed for valid projects' do + allow_next_instance_of(::ProjectTeam) do |project_team| + allow(project_team).to receive(:import).and_return(false) + end + + expect do + post api("/projects/#{project.id}/import_project_members/#{project2.id}", user) + end.not_to change { project.members.count } + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['message']).to eq('Import failed') + end + end + describe 'PUT /projects/:id' do before do expect(project).to be_persisted diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index d019e89e0b4..d3262b8056b 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -107,13 +107,18 @@ RSpec.describe API::Repositories do shared_examples_for 'repository blob' do it 'returns blob attributes as json' do + stub_const("Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE", 5) + get api(route, current_user) expect(response).to have_gitlab_http_status(:ok) expect(json_response['size']).to eq(111) expect(json_response['encoding']).to eq("base64") - expect(Base64.decode64(json_response['content']).lines.first).to eq("class Commit\n") expect(json_response['sha']).to eq(sample_blob.oid) + + content = Base64.decode64(json_response['content']) + expect(content.lines.first).to eq("class Commit\n") + expect(content).to eq(project.repository.gitaly_blob_client.get_blob(oid: sample_blob.oid, limit: -1).data) end context 'when sha does not exist' do @@ -164,7 +169,10 @@ RSpec.describe API::Repositories do shared_examples_for 'repository raw blob' do it 'returns the repository raw blob' do - expect(Gitlab::Workhorse).to receive(:send_git_blob) + expect(Gitlab::Workhorse).to receive(:send_git_blob) do |_, blob| + expect(blob.id).to eq(sample_blob.oid) + expect(blob.loaded_size).to eq(0) + end get api(route, current_user) diff --git a/spec/services/members/import_project_team_service_spec.rb b/spec/services/members/import_project_team_service_spec.rb new file mode 100644 index 00000000000..96e8db1ba73 --- /dev/null +++ b/spec/services/members/import_project_team_service_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Members::ImportProjectTeamService do + describe '#execute' do + let_it_be(:source_project) { create(:project) } + let_it_be(:target_project) { create(:project) } + let_it_be(:user) { create(:user) } + + subject { described_class.new(user, { id: target_project_id, project_id: source_project_id }) } + + before_all do + source_project.add_guest(user) + target_project.add_maintainer(user) + end + + context 'when project team members are imported successfully' do + let(:source_project_id) { source_project.id } + let(:target_project_id) { target_project.id } + + it 'returns true' do + expect(subject.execute).to be(true) + end + end + + context 'when the project team import fails' do + context 'when the target project cannot be found' do + let(:source_project_id) { source_project.id } + let(:target_project_id) { non_existing_record_id } + + it 'returns false' do + expect(subject.execute).to be(false) + end + end + + context 'when the source project cannot be found' do + let(:source_project_id) { non_existing_record_id } + let(:target_project_id) { target_project.id } + + it 'returns false' do + expect(subject.execute).to be(false) + end + end + + context 'when the user doing the import does not exist' do + let(:user) { nil } + let(:source_project_id) { source_project.id } + let(:target_project_id) { target_project.id } + + it 'returns false' do + expect(subject.execute).to be(false) + end + end + + context 'when the user does not have permission to read the source project members' do + let(:user) { create(:user) } + let(:source_project_id) { create(:project, :private).id } + let(:target_project_id) { target_project.id } + + it 'returns false' do + expect(subject.execute).to be(false) + end + end + + context 'when the user does not have permission to admin the target project' do + let(:source_project_id) { source_project.id } + let(:target_project_id) { create(:project).id } + + it 'returns false' do + expect(subject.execute).to be(false) + end + end + + context 'when the source and target project are valid but the ProjectTeam#import command fails' do + let(:source_project_id) { source_project.id } + let(:target_project_id) { target_project.id } + + before do + allow_next_instance_of(ProjectTeam) do |project_team| + allow(project_team).to receive(:import).and_return(false) + end + end + + it 'returns false' do + expect(subject.execute).to be(false) + end + end + end + end +end diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb index bc17d104034..4d328c03641 100644 --- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb +++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb @@ -41,11 +41,28 @@ RSpec.shared_examples 'namespace traversal scopes' do it { is_expected.to match_array(groups) } end + + context 'when include_self is false' do + subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendants(include_self: false) } + + it { is_expected.to contain_exactly(deep_nested_group_1, deep_nested_group_2) } + end end describe '.self_and_descendant_ids' do subject { described_class.where(id: [nested_group_1, nested_group_2]).self_and_descendant_ids.pluck(:id) } it { is_expected.to contain_exactly(nested_group_1.id, deep_nested_group_1.id, nested_group_2.id, deep_nested_group_2.id) } + + context 'when include_self is false' do + subject do + described_class + .where(id: [nested_group_1, nested_group_2]) + .self_and_descendant_ids(include_self: false) + .pluck(:id) + end + + it { is_expected.to contain_exactly(deep_nested_group_1.id, deep_nested_group_2.id) } + end end end |