Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-11 15:10:59 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-11 15:10:59 +0300
commit4d68d8b937211e5cdcf58443ddf693f0f1d13794 (patch)
tree8f849136bb2bc35efab022a3d62cc48a7a119be7 /spec
parent564919dfc6c6b352163d4c6dc01827a5f12ffc88 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb12
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js59
-rw-r--r--spec/frontend/syntax_highlight_spec.js63
-rw-r--r--spec/requests/api/projects_spec.rb53
-rw-r--r--spec/requests/api/repositories_spec.rb12
-rw-r--r--spec/services/members/import_project_team_service_spec.rb91
-rw-r--r--spec/support/shared_examples/namespaces/traversal_scope_examples.rb17
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