diff options
Diffstat (limited to 'spec')
38 files changed, 1189 insertions, 640 deletions
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb index 418ca6f3210..1e8a8145b35 100644 --- a/spec/controllers/boards/lists_controller_spec.rb +++ b/spec/controllers/boards/lists_controller_spec.rb @@ -30,6 +30,21 @@ describe Boards::ListsController do expect(json_response.length).to eq 3 end + it 'avoids n+1 queries when serializing lists' do + list_1 = create(:list, board: board) + list_1.update_preferences_for(user, { collapsed: true }) + + control_count = ActiveRecord::QueryRecorder.new { read_board_list user: user, board: board }.count + + list_2 = create(:list, board: board) + list_2.update_preferences_for(user, { collapsed: true }) + + list_3 = create(:list, board: board) + list_3.update_preferences_for(user, { collapsed: true }) + + expect { read_board_list user: user, board: board }.not_to exceed_query_limit(control_count) + end + context 'with unauthorized user' do let(:unauth_user) { create(:user) } @@ -154,6 +169,22 @@ describe Boards::ListsController do end end + context 'with collapsed preference' do + it 'saves collapsed preference for user' do + save_setting user: user, board: board, list: planning, setting: { collapsed: true } + + expect(planning.preferences_for(user).collapsed).to eq(true) + expect(response).to have_gitlab_http_status(200) + end + + it 'saves not collapsed preference for user' do + save_setting user: user, board: board, list: planning, setting: { collapsed: false } + + expect(planning.preferences_for(user).collapsed).to eq(false) + expect(response).to have_gitlab_http_status(200) + end + end + def move(user:, board:, list:, position:) sign_in(user) @@ -166,6 +197,19 @@ describe Boards::ListsController do patch :update, params: params, as: :json end + + def save_setting(user:, board:, list:, setting: {}) + sign_in(user) + + params = { namespace_id: project.namespace.to_param, + project_id: project, + board_id: board.to_param, + id: list.to_param, + list: setting, + format: :json } + + patch :update, params: params, as: :json + end end describe 'DELETE destroy' do diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index a090461261b..0b3f905b5de 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -14,7 +14,6 @@ describe "User browses files" do before do stub_feature_flags(vue_file_list: false) - stub_feature_flags(csslab: false) sign_in(user) end diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb index 4203f58fe81..6920fb4e572 100644 --- a/spec/finders/members_finder_spec.rb +++ b/spec/finders/members_finder_spec.rb @@ -17,11 +17,10 @@ describe MembersFinder, '#execute' do result = described_class.new(project, user2).execute - expect(result.to_a).to match_array([member1, member2, member3]) + expect(result).to contain_exactly(member1, member2, member3) end - it 'includes nested group members if asked' do - project = create(:project, namespace: group) + it 'includes nested group members if asked', :nested_groups do nested_group.request_access(user1) member1 = group.add_maintainer(user2) member2 = nested_group.add_maintainer(user3) @@ -29,7 +28,28 @@ describe MembersFinder, '#execute' do result = described_class.new(project, user2).execute(include_descendants: true) - expect(result.to_a).to match_array([member1, member2, member3]) + expect(result).to contain_exactly(member1, member2, member3) + end + + it 'returns the members.access_level when the user is invited', :nested_groups do + member_invite = create(:project_member, :invited, project: project, invite_email: create(:user).email) + member1 = group.add_maintainer(user2) + + result = described_class.new(project, user2).execute(include_descendants: true) + + expect(result).to contain_exactly(member1, member_invite) + expect(result.last.access_level).to eq(member_invite.access_level) + end + + it 'returns the highest access_level for the user', :nested_groups do + member1 = project.add_guest(user1) + group.add_developer(user1) + nested_group.add_reporter(user1) + + result = described_class.new(project, user1).execute(include_descendants: true) + + expect(result).to contain_exactly(member1) + expect(result.first.access_level).to eq(Gitlab::Access::DEVELOPER) end context 'when include_invited_groups_members == true' do @@ -37,8 +57,8 @@ describe MembersFinder, '#execute' do set(:linked_group) { create(:group, :public, :access_requestable) } set(:nested_linked_group) { create(:group, parent: linked_group) } - set(:linked_group_member) { linked_group.add_developer(user1) } - set(:nested_linked_group_member) { nested_linked_group.add_developer(user2) } + set(:linked_group_member) { linked_group.add_guest(user1) } + set(:nested_linked_group_member) { nested_linked_group.add_guest(user2) } it 'includes all the invited_groups members including members inherited from ancestor groups' do create(:project_group_link, project: project, group: nested_linked_group) @@ -60,5 +80,17 @@ describe MembersFinder, '#execute' do expect(subject).to contain_exactly(linked_group_member) end + + context 'when the user is a member of invited group and ancestor groups' do + it 'returns the highest access_level for the user limited by project_group_link.group_access', :nested_groups do + create(:project_group_link, project: project, group: nested_linked_group, group_access: Gitlab::Access::REPORTER) + nested_linked_group.add_developer(user1) + + result = subject + + expect(result).to contain_exactly(linked_group_member, nested_linked_group_member) + expect(result.first.access_level).to eq(Gitlab::Access::REPORTER) + end + end end end diff --git a/spec/fixtures/api/schemas/entities/merge_request_noteable.json b/spec/fixtures/api/schemas/entities/merge_request_noteable.json new file mode 100644 index 00000000000..88b0fecc24c --- /dev/null +++ b/spec/fixtures/api/schemas/entities/merge_request_noteable.json @@ -0,0 +1,28 @@ +{ + "type": "object", + "properties" : { + "merge_params": { "type": ["object", "null"] }, + "state": { "type": "string" }, + "source_branch": { "type": "string" }, + "target_branch": { "type": "string" }, + "diff_head_sha": { "type": "string" }, + "create_note_path": { "type": ["string", "null"] }, + "preview_note_path": { "type": ["string", "null"] }, + "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] }, + "new_blob_path": { "type": ["string", "null"] }, + "can_receive_suggestion": { "type": "boolean" }, + "current_user": { + "type": "object", + "required": [ + "can_create_note", + "can_update" + ], + "properties": { + "can_create_note": { "type": "boolean" }, + "can_update": { "type": "boolean" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json index 2052892dfa3..1eda0e12920 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json @@ -24,22 +24,20 @@ "ci_status": { "type": ["string", "null"] }, "cancel_auto_merge_path": { "type": ["string", "null"] }, "test_reports_path": { "type": ["string", "null"] }, - "can_receive_suggestion": { "type": "boolean" }, "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] }, "current_user": { "type": "object", "required": [ "can_remove_source_branch", "can_revert_on_current_merge_request", - "can_cherry_pick_on_current_merge_request" + "can_cherry_pick_on_current_merge_request", + "can_create_issue" ], "properties": { "can_remove_source_branch": { "type": "boolean" }, "can_revert_on_current_merge_request": { "type": ["boolean", "null"] }, "can_cherry_pick_on_current_merge_request": { "type": ["boolean", "null"] }, - "can_create_note": { "type": "boolean" }, - "can_create_issue": { "type": "boolean" }, - "can_update": { "type": "boolean" } + "can_create_issue": { "type": "boolean" } }, "additionalProperties": false }, diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 779a47222b7..e2df7952d8f 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -5,7 +5,6 @@ { "$ref": "merge_request_poll_widget.json" }, { "properties" : { - "merge_params": { "type": ["object", "null"] }, "source_project_full_path": { "type": ["string", "null"]}, "target_project_full_path": { "type": ["string", "null"]}, "email_patches_path": { "type": "string" }, @@ -13,9 +12,7 @@ "merge_request_basic_path": { "type": "string" }, "merge_request_widget_path": { "type": "string" }, "merge_request_cached_widget_path": { "type": "string" }, - "create_note_path": { "type": ["string", "null"] }, "commit_change_content_path": { "type": "string" }, - "preview_note_path": { "type": ["string", "null"] }, "conflicts_docs_path": { "type": ["string", "null"] }, "merge_request_pipelines_docs_path": { "type": ["string", "null"] }, "ci_environments_status_path": { "type": "string" }, diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js new file mode 100644 index 00000000000..1f4d1e17ea0 --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js @@ -0,0 +1,55 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; +import AutoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue'; +import eventHub from '~/vue_merge_request_widget/event_hub'; + +describe('MRWidgetAutoMergeFailed', () => { + let wrapper; + const mergeError = 'This is the merge error'; + const findButton = () => wrapper.find('button'); + + const createComponent = (props = {}) => { + wrapper = shallowMount(AutoMergeFailedComponent, { + sync: false, + propsData: { ...props }, + }); + }; + + beforeEach(() => { + createComponent({ + mr: { mergeError }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders failed message', () => { + expect(wrapper.text()).toContain('This merge request failed to be merged automatically'); + }); + + it('renders merge error provided', () => { + expect(wrapper.text()).toContain(mergeError); + }); + + it('render refresh button', () => { + expect(findButton().text()).toEqual('Refresh'); + }); + + it('emits event and shows loading icon when button is clicked', () => { + jest.spyOn(eventHub, '$emit'); + findButton().trigger('click'); + + expect(eventHub.$emit.mock.calls[0][0]).toBe('MRWidgetUpdateRequested'); + + return wrapper.vm.$nextTick(() => { + expect(findButton().attributes('disabled')).toEqual('disabled'); + expect( + findButton() + .find(GlLoadingIcon) + .exists(), + ).toBe(true); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js new file mode 100644 index 00000000000..328eec0a80a --- /dev/null +++ b/spec/frontend/vue_shared/components/file_icon_spec.js @@ -0,0 +1,75 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; +import FileIcon from '~/vue_shared/components/file_icon.vue'; +import Icon from '~/vue_shared/components/icon.vue'; + +describe('File Icon component', () => { + let wrapper; + const findIcon = () => wrapper.find('svg'); + const getIconName = () => + findIcon() + .find('use') + .element.getAttribute('xlink:href') + .replace(`${gon.sprite_file_icons}#`, ''); + + const createComponent = (props = {}) => { + wrapper = shallowMount(FileIcon, { + sync: false, + propsData: { ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('should render a span element and an icon', () => { + createComponent({ + fileName: 'test.js', + }); + + expect(wrapper.element.tagName).toEqual('SPAN'); + expect(findIcon().exists()).toBeDefined(); + }); + + it.each` + fileName | iconName + ${'test.js'} | ${'javascript'} + ${'test.png'} | ${'image'} + ${'webpack.js'} | ${'webpack'} + `('should render a $iconName icon based on file ending', ({ fileName, iconName }) => { + createComponent({ fileName }); + expect(getIconName()).toBe(iconName); + }); + + it('should render a standard folder icon', () => { + createComponent({ + fileName: 'js', + folder: true, + }); + + expect(findIcon().exists()).toBe(false); + expect(wrapper.find(Icon).props('cssClasses')).toContain('folder-icon'); + }); + + it('should render a loading icon', () => { + createComponent({ + fileName: 'test.js', + loading: true, + }); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + + it('should add a special class and a size class', () => { + const size = 120; + createComponent({ + fileName: 'test.js', + cssClasses: 'extraclasses', + size, + }); + + expect(findIcon().classes()).toContain(`s${size}`); + expect(findIcon().classes()).toContain('extraclasses'); + }); +}); diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index a70bfc2adc7..29c43d1977e 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -503,7 +503,7 @@ describe ProjectsHelper do allow(Gitlab::CurrentSettings.current_application_settings).to receive(:enabled_git_access_protocol) { 'ssh' } allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return('git@localhost:') - expect(helper.push_to_create_project_command(user)).to eq('git push --set-upstream git@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)') + expect(helper.push_to_create_project_command(user)).to eq("git push --set-upstream #{Gitlab.config.gitlab.user}@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)") end end diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js index 388d7063d13..f9ee4648128 100644 --- a/spec/javascripts/environments/environment_item_spec.js +++ b/spec/javascripts/environments/environment_item_spec.js @@ -106,6 +106,7 @@ describe('Environment item', () => { play_path: '/play', }, ], + deployed_at: '2016-11-29T18:11:58.430Z', }, has_stop_action: true, environment_path: 'root/ci-folders/environments/31', @@ -139,9 +140,7 @@ describe('Environment item', () => { it('should render last deployment date', () => { const timeagoInstance = new timeago(); // eslint-disable-line - const formatedDate = timeagoInstance.format( - environment.last_deployment.deployable.created_at, - ); + const formatedDate = timeagoInstance.format(environment.last_deployment.deployed_at); expect( component.$el.querySelector('.environment-created-date-timeago').textContent, diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js deleted file mode 100644 index 55a11a72551..00000000000 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js +++ /dev/null @@ -1,47 +0,0 @@ -import Vue from 'vue'; -import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue'; -import eventHub from '~/vue_merge_request_widget/event_hub'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('MRWidgetAutoMergeFailed', () => { - let vm; - const mergeError = 'This is the merge error'; - - beforeEach(() => { - const Component = Vue.extend(autoMergeFailedComponent); - vm = mountComponent(Component, { - mr: { mergeError }, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders failed message', () => { - expect(vm.$el.textContent).toContain('This merge request failed to be merged automatically'); - }); - - it('renders merge error provided', () => { - expect(vm.$el.innerText).toContain(mergeError); - }); - - it('render refresh button', () => { - expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Refresh'); - }); - - it('emits event and shows loading icon when button is clicked', done => { - spyOn(eventHub, '$emit'); - vm.$el.querySelector('button').click(); - - expect(eventHub.$emit.calls.argsFor(0)[0]).toEqual('MRWidgetUpdateRequested'); - - Vue.nextTick(() => { - expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled'); - expect(vm.$el.querySelector('button .loading-container span').classList).toContain( - 'gl-spinner', - ); - done(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js deleted file mode 100644 index 1f61e19fa84..00000000000 --- a/spec/javascripts/vue_shared/components/file_icon_spec.js +++ /dev/null @@ -1,92 +0,0 @@ -import Vue from 'vue'; -import fileIcon from '~/vue_shared/components/file_icon.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('File Icon component', () => { - let vm; - let FileIcon; - - beforeEach(() => { - FileIcon = Vue.extend(fileIcon); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('should render a span element with an svg', () => { - vm = mountComponent(FileIcon, { - fileName: 'test.js', - }); - - expect(vm.$el.tagName).toEqual('SPAN'); - expect(vm.$el.querySelector('span > svg')).toBeDefined(); - }); - - it('should render a javascript icon based on file ending', () => { - vm = mountComponent(FileIcon, { - fileName: 'test.js', - }); - - expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe( - `${gon.sprite_file_icons}#javascript`, - ); - }); - - it('should render a image icon based on file ending', () => { - vm = mountComponent(FileIcon, { - fileName: 'test.png', - }); - - expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe( - `${gon.sprite_file_icons}#image`, - ); - }); - - it('should render a webpack icon based on file namer', () => { - vm = mountComponent(FileIcon, { - fileName: 'webpack.js', - }); - - expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe( - `${gon.sprite_file_icons}#webpack`, - ); - }); - - it('should render a standard folder icon', () => { - vm = mountComponent(FileIcon, { - fileName: 'js', - folder: true, - }); - - expect(vm.$el.querySelector('span > svg > use').getAttribute('xlink:href')).toBe( - `${gon.sprite_file_icons}#folder`, - ); - }); - - it('should render a loading icon', () => { - vm = mountComponent(FileIcon, { - fileName: 'test.js', - loading: true, - }); - - const { classList } = vm.$el.querySelector('.loading-container span'); - - expect(classList.contains('gl-spinner')).toEqual(true); - }); - - it('should add a special class and a size class', () => { - vm = mountComponent(FileIcon, { - fileName: 'test.js', - cssClasses: 'extraclasses', - size: 120, - }); - - const { classList } = vm.$el.firstChild; - const containsSizeClass = classList.contains('s120'); - const containsCustomClass = classList.contains('extraclasses'); - - expect(containsSizeClass).toBe(true); - expect(containsCustomClass).toBe(true); - }); -}); diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb index 42bc509eeef..adf36cf1050 100644 --- a/spec/lib/gitlab/authorized_keys_spec.rb +++ b/spec/lib/gitlab/authorized_keys_spec.rb @@ -5,10 +5,81 @@ require 'spec_helper' describe Gitlab::AuthorizedKeys do let(:logger) { double('logger').as_null_object } - subject { described_class.new(logger) } + subject(:authorized_keys) { described_class.new(logger) } + + describe '#accessible?' do + subject { authorized_keys.accessible? } + + context 'authorized_keys file exists' do + before do + create_authorized_keys_fixture + end + + after do + delete_authorized_keys_file + end + + context 'can open file' do + it { is_expected.to be_truthy } + end + + context 'cannot open file' do + before do + allow(File).to receive(:open).and_raise(Errno::EACCES) + end + + it { is_expected.to be_falsey } + end + end + + context 'authorized_keys file does not exist' do + it { is_expected.to be_falsey } + end + end + + describe '#create' do + subject { authorized_keys.create } + + context 'authorized_keys file exists' do + before do + create_authorized_keys_fixture + end + + after do + delete_authorized_keys_file + end + + it { is_expected.to be_truthy } + end + + context 'authorized_keys file does not exist' do + after do + delete_authorized_keys_file + end + + it 'creates authorized_keys file' do + expect(subject).to be_truthy + expect(File.exist?(tmp_authorized_keys_path)).to be_truthy + end + end + + context 'cannot create file' do + before do + allow(File).to receive(:open).and_raise(Errno::EACCES) + end + + it { is_expected.to be_falsey } + end + end describe '#add_key' do + let(:id) { 'key-741' } + + subject { authorized_keys.add_key(id, key) } + context 'authorized_keys file exists' do + let(:key) { 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage' } + before do create_authorized_keys_fixture end @@ -21,19 +92,20 @@ describe Gitlab::AuthorizedKeys do auth_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E" expect(logger).to receive(:info).with('Adding key (key-741): ssh-rsa AAAAB3NzaDAxx2E') - expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage')) - .to be_truthy + expect(subject).to be_truthy expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line}\n") end end context 'authorized_keys file does not exist' do + let(:key) { 'ssh-rsa AAAAB3NzaDAxx2E' } + before do delete_authorized_keys_file end it 'creates the file' do - expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E')).to be_truthy + expect(subject).to be_truthy expect(File.exist?(tmp_authorized_keys_path)).to be_truthy end end @@ -47,6 +119,8 @@ describe Gitlab::AuthorizedKeys do ] end + subject { authorized_keys.batch_add_keys(keys) } + context 'authorized_keys file exists' do before do create_authorized_keys_fixture @@ -62,7 +136,7 @@ describe Gitlab::AuthorizedKeys do expect(logger).to receive(:info).with('Adding key (key-12): ssh-dsa ASDFASGADG') expect(logger).to receive(:info).with('Adding key (key-123): ssh-rsa GFDGDFSGSDFG') - expect(subject.batch_add_keys(keys)).to be_truthy + expect(subject).to be_truthy expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line1}\n#{auth_line2}\n") end @@ -70,7 +144,7 @@ describe Gitlab::AuthorizedKeys do let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] } it "doesn't add keys" do - expect(subject.batch_add_keys(keys)).to be_falsey + expect(subject).to be_falsey expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n") end end @@ -82,16 +156,28 @@ describe Gitlab::AuthorizedKeys do end it 'creates the file' do - expect(subject.batch_add_keys(keys)).to be_truthy + expect(subject).to be_truthy expect(File.exist?(tmp_authorized_keys_path)).to be_truthy end end end describe '#rm_key' do + let(:key) { 'key-741' } + + subject { authorized_keys.rm_key(key) } + context 'authorized_keys file exists' do + let(:other_line) { "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E" } + let(:delete_line) { "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E" } + before do create_authorized_keys_fixture + + File.open(tmp_authorized_keys_path, 'a') do |auth_file| + auth_file.puts delete_line + auth_file.puts other_line + end end after do @@ -99,16 +185,10 @@ describe Gitlab::AuthorizedKeys do end it "removes the right line" do - other_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E" - delete_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E" erased_line = delete_line.gsub(/./, '#') - File.open(tmp_authorized_keys_path, 'a') do |auth_file| - auth_file.puts delete_line - auth_file.puts other_line - end expect(logger).to receive(:info).with('Removing key (key-741)') - expect(subject.rm_key('key-741')).to be_truthy + expect(subject).to be_truthy expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{erased_line}\n#{other_line}\n") end end @@ -118,13 +198,13 @@ describe Gitlab::AuthorizedKeys do delete_authorized_keys_file end - it 'returns false' do - expect(subject.rm_key('key-741')).to be_falsey - end + it { is_expected.to be_falsey } end end describe '#clear' do + subject { authorized_keys.clear } + context 'authorized_keys file exists' do before do create_authorized_keys_fixture @@ -134,9 +214,7 @@ describe Gitlab::AuthorizedKeys do delete_authorized_keys_file end - it "returns true" do - expect(subject.clear).to be_truthy - end + it { is_expected.to be_truthy } end context 'authorized_keys file does not exist' do @@ -144,13 +222,13 @@ describe Gitlab::AuthorizedKeys do delete_authorized_keys_file end - it "still returns true" do - expect(subject.clear).to be_truthy - end + it { is_expected.to be_truthy } end end describe '#list_key_ids' do + subject { authorized_keys.list_key_ids } + context 'authorized_keys file exists' do before do create_authorized_keys_fixture( @@ -163,9 +241,7 @@ describe Gitlab::AuthorizedKeys do delete_authorized_keys_file end - it 'returns array of key IDs' do - expect(subject.list_key_ids).to eq([1, 2, 3, 9000]) - end + it { is_expected.to eq([1, 2, 3, 9000]) } end context 'authorized_keys file does not exist' do @@ -173,9 +249,7 @@ describe Gitlab::AuthorizedKeys do delete_authorized_keys_file end - it 'returns an empty array' do - expect(subject.list_key_ids).to be_empty - end + it { is_expected.to be_empty } end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 3c6b17c10ec..ec4a6ef05b9 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -483,3 +483,4 @@ lists: - milestone - board - label +- list_user_preferences diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index d6e1fbaa979..0aef4887c75 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -396,6 +396,27 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(project.lfs_enabled).to be_falsey end + + it 'overrides project feature access levels' do + access_level_keys = project.project_feature.attributes.keys.select { |a| a =~ /_access_level/ } + + # `pages_access_level` is not included, since it is not available in the public API + # and has a dependency on project's visibility level + # see ProjectFeature model + access_level_keys.delete('pages_access_level') + + disabled_access_levels = Hash[access_level_keys.collect { |item| [item, 'disabled'] }] + + project.create_import_data(data: { override_params: disabled_access_levels }) + + restored_project_json + + aggregate_failures do + access_level_keys.each do |key| + expect(project.public_send(key)).to eq(ProjectFeature::DISABLED) + end + end + end end context 'with a project that has a group' do diff --git a/spec/lib/gitlab/internal_post_receive/response_spec.rb b/spec/lib/gitlab/internal_post_receive/response_spec.rb new file mode 100644 index 00000000000..f43762c9248 --- /dev/null +++ b/spec/lib/gitlab/internal_post_receive/response_spec.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::InternalPostReceive::Response do + subject { described_class.new } + + describe '#add_merge_request_urls' do + context 'when there are urls_data' do + it 'adds a message for each merge request URL' do + urls_data = [ + { new_merge_request: false, branch_name: 'foo', url: 'http://example.com/foo/bar/merge_requests/1' }, + { new_merge_request: true, branch_name: 'bar', url: 'http://example.com/foo/bar/merge_requests/new?merge_request%5Bsource_branch%5D=bar' } + ] + + subject.add_merge_request_urls(urls_data) + + expected = [a_kind_of(described_class::Message), a_kind_of(described_class::Message)] + expect(subject.messages).to match(expected) + end + end + end + + describe '#add_merge_request_url' do + context 'when :new_merge_request is false' do + it 'adds a basic message to view the existing merge request' do + url_data = { new_merge_request: false, branch_name: 'foo', url: 'http://example.com/foo/bar/merge_requests/1' } + + subject.add_merge_request_url(url_data) + + message = <<~MESSAGE.strip + View merge request for foo: + http://example.com/foo/bar/merge_requests/1 + MESSAGE + + expect(subject.messages.first.message).to eq(message) + expect(subject.messages.first.type).to eq(:basic) + end + end + + context 'when :new_merge_request is true' do + it 'adds a basic message to create a new merge request' do + url_data = { new_merge_request: true, branch_name: 'bar', url: 'http://example.com/foo/bar/merge_requests/new?merge_request%5Bsource_branch%5D=bar' } + + subject.add_merge_request_url(url_data) + + message = <<~MESSAGE.strip + To create a merge request for bar, visit: + http://example.com/foo/bar/merge_requests/new?merge_request%5Bsource_branch%5D=bar + MESSAGE + + expect(subject.messages.first.message).to eq(message) + expect(subject.messages.first.type).to eq(:basic) + end + end + end + + describe '#add_basic_message' do + context 'when text is present' do + it 'adds a basic message' do + subject.add_basic_message('hello') + + expect(subject.messages.first.message).to eq('hello') + expect(subject.messages.first.type).to eq(:basic) + end + end + + context 'when text is blank' do + it 'does not add a message' do + subject.add_basic_message(' ') + + expect(subject.messages).to be_blank + end + end + end + + describe '#add_alert_message' do + context 'when text is present' do + it 'adds a alert message' do + subject.add_alert_message('hello') + + expect(subject.messages.first.message).to eq('hello') + expect(subject.messages.first.type).to eq(:alert) + end + end + + context 'when text is blank' do + it 'does not add a message' do + subject.add_alert_message(' ') + + expect(subject.messages).to be_blank + end + end + end + + describe '#reference_counter_decreased' do + context 'initially' do + it 'reference_counter_decreased is set to false' do + expect(subject.reference_counter_decreased).to eq(false) + end + end + end + + describe '#reference_counter_decreased=' do + context 'when the argument is truthy' do + it 'reference_counter_decreased is truthy' do + subject.reference_counter_decreased = true + + expect(subject.reference_counter_decreased).to be_truthy + end + end + + context 'when the argument is falsey' do + it 'reference_counter_decreased is falsey' do + subject.reference_counter_decreased = false + + expect(subject.reference_counter_decreased).to be_falsey + end + end + end +end diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index f52095bf633..16595102375 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -202,7 +202,7 @@ describe Gitlab::Middleware::Go do def expect_response_with_path(response, protocol, path) repository_url = case protocol when :ssh - "ssh://git@#{Gitlab.config.gitlab.host}/#{path}.git" + "ssh://#{Gitlab.config.gitlab.user}@#{Gitlab.config.gitlab.host}/#{path}.git" when :http, nil "http://#{Gitlab.config.gitlab.host}/#{path}.git" end diff --git a/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb new file mode 100644 index 00000000000..3b92261f0fe --- /dev/null +++ b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rspec-parameterized' + +describe Gitlab::PerformanceBar::WithTopLevelWarnings do + using RSpec::Parameterized::TableSyntax + + subject { Module.new } + + before do + subject.singleton_class.prepend(described_class) + end + + describe '#has_warnings?' do + where(:has_warnings, :results) do + false | { data: {} } + false | { data: { gitaly: { warnings: [] } } } + true | { data: { gitaly: { warnings: [1] } } } + true | { data: { gitaly: { warnings: [] }, redis: { warnings: [1] } } } + end + + with_them do + it do + expect(subject.has_warnings?(results)).to eq(has_warnings) + end + end + end +end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 0ba16b93ee7..fe4853fd819 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -52,38 +52,14 @@ describe Gitlab::Shell do describe '#add_key' do context 'when authorized_keys_enabled is true' do - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - allow(gitlab_shell) - .to receive(:gitlab_shell_keys_path) - .and_return(:gitlab_shell_keys_path) - end - - it 'calls #gitlab_shell_fast_execute with add-key command' do - expect(gitlab_shell) - .to receive(:gitlab_shell_fast_execute) - .with([ - :gitlab_shell_keys_path, - 'add-key', - 'key-123', - 'ssh-rsa foobar' - ]) - - gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') - end - end - - context 'authorized_keys_file set' do - it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do - expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) + it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do + expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) - expect(gitlab_authorized_keys) - .to receive(:add_key) - .with('key-123', 'ssh-rsa foobar') + expect(gitlab_authorized_keys) + .to receive(:add_key) + .with('key-123', 'ssh-rsa foobar') - gitlab_shell.add_key('key-123', 'ssh-rsa foobar') - end + gitlab_shell.add_key('key-123', 'ssh-rsa foobar') end end @@ -92,24 +68,10 @@ describe Gitlab::Shell do stub_application_setting(authorized_keys_enabled: false) end - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - end + it 'does nothing' do + expect(Gitlab::AuthorizedKeys).not_to receive(:new) - it 'does nothing' do - expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute) - - gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') - end - end - - context 'authorized_keys_file set' do - it 'does nothing' do - expect(Gitlab::AuthorizedKeys).not_to receive(:new) - - gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') - end + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') end end @@ -118,38 +80,14 @@ describe Gitlab::Shell do stub_application_setting(authorized_keys_enabled: nil) end - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - allow(gitlab_shell) - .to receive(:gitlab_shell_keys_path) - .and_return(:gitlab_shell_keys_path) - end - - it 'calls #gitlab_shell_fast_execute with add-key command' do - expect(gitlab_shell) - .to receive(:gitlab_shell_fast_execute) - .with([ - :gitlab_shell_keys_path, - 'add-key', - 'key-123', - 'ssh-rsa foobar' - ]) - - gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') - end - end - - context 'authorized_keys_file set' do - it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do - expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) + it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do + expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) - expect(gitlab_authorized_keys) - .to receive(:add_key) - .with('key-123', 'ssh-rsa foobar') + expect(gitlab_authorized_keys) + .to receive(:add_key) + .with('key-123', 'ssh-rsa foobar') - gitlab_shell.add_key('key-123', 'ssh-rsa foobar') - end + gitlab_shell.add_key('key-123', 'ssh-rsa foobar') end end end @@ -158,50 +96,14 @@ describe Gitlab::Shell do let(:keys) { [double(shell_id: 'key-123', key: 'ssh-rsa foobar')] } context 'when authorized_keys_enabled is true' do - context 'authorized_keys_file not set' do - let(:io) { double } - - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - end - - context 'valid keys' do - before do - allow(gitlab_shell) - .to receive(:gitlab_shell_keys_path) - .and_return(:gitlab_shell_keys_path) - end - - it 'calls gitlab-keys with batch-add-keys command' do - expect(IO) - .to receive(:popen) - .with("gitlab_shell_keys_path batch-add-keys", 'w') - .and_yield(io) - - expect(io).to receive(:puts).with("key-123\tssh-rsa foobar") - expect(gitlab_shell.batch_add_keys(keys)).to be_truthy - end - end - - context 'invalid keys' do - let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] } - - it 'catches failure and returns false' do - expect(gitlab_shell.batch_add_keys(keys)).to be_falsey - end - end - end + it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do + expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) - context 'authorized_keys_file set' do - it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do - expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) + expect(gitlab_authorized_keys) + .to receive(:batch_add_keys) + .with(keys) - expect(gitlab_authorized_keys) - .to receive(:batch_add_keys) - .with(keys) - - gitlab_shell.batch_add_keys(keys) - end + gitlab_shell.batch_add_keys(keys) end end @@ -210,24 +112,10 @@ describe Gitlab::Shell do stub_application_setting(authorized_keys_enabled: false) end - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - end - - it 'does nothing' do - expect(IO).not_to receive(:popen) - - gitlab_shell.batch_add_keys(keys) - end - end - - context 'authorized_keys_file set' do - it 'does nothing' do - expect(Gitlab::AuthorizedKeys).not_to receive(:new) + it 'does nothing' do + expect(Gitlab::AuthorizedKeys).not_to receive(:new) - gitlab_shell.batch_add_keys(keys) - end + gitlab_shell.batch_add_keys(keys) end end @@ -236,72 +124,25 @@ describe Gitlab::Shell do stub_application_setting(authorized_keys_enabled: nil) end - context 'authorized_keys_file not set' do - let(:io) { double } + it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do + expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - allow(gitlab_shell) - .to receive(:gitlab_shell_keys_path) - .and_return(:gitlab_shell_keys_path) - end - - it 'calls gitlab-keys with batch-add-keys command' do - expect(IO) - .to receive(:popen) - .with("gitlab_shell_keys_path batch-add-keys", 'w') - .and_yield(io) + expect(gitlab_authorized_keys) + .to receive(:batch_add_keys) + .with(keys) - expect(io).to receive(:puts).with("key-123\tssh-rsa foobar") - - gitlab_shell.batch_add_keys(keys) - end - end - - context 'authorized_keys_file set' do - it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do - expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) - - expect(gitlab_authorized_keys) - .to receive(:batch_add_keys) - .with(keys) - - gitlab_shell.batch_add_keys(keys) - end + gitlab_shell.batch_add_keys(keys) end end end describe '#remove_key' do context 'when authorized_keys_enabled is true' do - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - allow(gitlab_shell) - .to receive(:gitlab_shell_keys_path) - .and_return(:gitlab_shell_keys_path) - end - - it 'calls #gitlab_shell_fast_execute with rm-key command' do - expect(gitlab_shell) - .to receive(:gitlab_shell_fast_execute) - .with([ - :gitlab_shell_keys_path, - 'rm-key', - 'key-123' - ]) - - gitlab_shell.remove_key('key-123') - end - end + it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do + expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) + expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123') - context 'authorized_keys_file not set' do - it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do - expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) - expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123') - - gitlab_shell.remove_key('key-123') - end + gitlab_shell.remove_key('key-123') end end @@ -310,24 +151,10 @@ describe Gitlab::Shell do stub_application_setting(authorized_keys_enabled: false) end - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - end - - it 'does nothing' do - expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute) + it 'does nothing' do + expect(Gitlab::AuthorizedKeys).not_to receive(:new) - gitlab_shell.remove_key('key-123') - end - end - - context 'authorized_keys_file set' do - it 'does nothing' do - expect(Gitlab::AuthorizedKeys).not_to receive(:new) - - gitlab_shell.remove_key('key-123') - end + gitlab_shell.remove_key('key-123') end end @@ -336,64 +163,22 @@ describe Gitlab::Shell do stub_application_setting(authorized_keys_enabled: nil) end - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - allow(gitlab_shell) - .to receive(:gitlab_shell_keys_path) - .and_return(:gitlab_shell_keys_path) - end - - it 'calls #gitlab_shell_fast_execute with rm-key command' do - expect(gitlab_shell) - .to receive(:gitlab_shell_fast_execute) - .with([ - :gitlab_shell_keys_path, - 'rm-key', - 'key-123' - ]) - - gitlab_shell.remove_key('key-123') - end - end - - context 'authorized_keys_file not set' do - it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do - expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) - expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123') + it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do + expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) + expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123') - gitlab_shell.remove_key('key-123') - end + gitlab_shell.remove_key('key-123') end end end describe '#remove_all_keys' do context 'when authorized_keys_enabled is true' do - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - allow(gitlab_shell) - .to receive(:gitlab_shell_keys_path) - .and_return(:gitlab_shell_keys_path) - end + it 'calls Gitlab::AuthorizedKeys#clear' do + expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) + expect(gitlab_authorized_keys).to receive(:clear) - it 'calls #gitlab_shell_fast_execute with clear command' do - expect(gitlab_shell) - .to receive(:gitlab_shell_fast_execute) - .with([:gitlab_shell_keys_path, 'clear']) - - gitlab_shell.remove_all_keys - end - end - - context 'authorized_keys_file set' do - it 'calls Gitlab::AuthorizedKeys#clear' do - expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) - expect(gitlab_authorized_keys).to receive(:clear) - - gitlab_shell.remove_all_keys - end + gitlab_shell.remove_all_keys end end @@ -402,24 +187,10 @@ describe Gitlab::Shell do stub_application_setting(authorized_keys_enabled: false) end - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - end - - it 'does nothing' do - expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute) + it 'does nothing' do + expect(Gitlab::AuthorizedKeys).not_to receive(:new) - gitlab_shell.remove_all_keys - end - end - - context 'authorized_keys_file set' do - it 'does nothing' do - expect(Gitlab::AuthorizedKeys).not_to receive(:new) - - gitlab_shell.remove_all_keys - end + gitlab_shell.remove_all_keys end end @@ -428,163 +199,73 @@ describe Gitlab::Shell do stub_application_setting(authorized_keys_enabled: nil) end - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - allow(gitlab_shell) - .to receive(:gitlab_shell_keys_path) - .and_return(:gitlab_shell_keys_path) - end - - it 'calls #gitlab_shell_fast_execute with clear command' do - expect(gitlab_shell) - .to receive(:gitlab_shell_fast_execute) - .with([:gitlab_shell_keys_path, 'clear']) + it 'calls Gitlab::AuthorizedKeys#clear' do + expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) + expect(gitlab_authorized_keys).to receive(:clear) - gitlab_shell.remove_all_keys - end - end - - context 'authorized_keys_file set' do - it 'calls Gitlab::AuthorizedKeys#clear' do - expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys) - expect(gitlab_authorized_keys).to receive(:clear) - - gitlab_shell.remove_all_keys - end + gitlab_shell.remove_all_keys end end end describe '#remove_keys_not_found_in_db' do context 'when keys are in the file that are not in the DB' do - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - gitlab_shell.remove_all_keys - gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') - gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF') - @another_key = create(:key) # this one IS in the DB - end - - it 'removes the keys' do - expect(gitlab_shell).to receive(:remove_key).with('key-1234') - expect(gitlab_shell).to receive(:remove_key).with('key-9876') - expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}") - - gitlab_shell.remove_keys_not_found_in_db - end + before do + gitlab_shell.remove_all_keys + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF') + @another_key = create(:key) # this one IS in the DB end - context 'authorized_keys_file set' do - before do - gitlab_shell.remove_all_keys - gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') - gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF') - @another_key = create(:key) # this one IS in the DB - end - - it 'removes the keys' do - expect(gitlab_shell).to receive(:remove_key).with('key-1234') - expect(gitlab_shell).to receive(:remove_key).with('key-9876') - expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}") + it 'removes the keys' do + expect(gitlab_shell).to receive(:remove_key).with('key-1234') + expect(gitlab_shell).to receive(:remove_key).with('key-9876') + expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}") - gitlab_shell.remove_keys_not_found_in_db - end + gitlab_shell.remove_keys_not_found_in_db end end context 'when keys there are duplicate keys in the file that are not in the DB' do - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - gitlab_shell.remove_all_keys - gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') - gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') - end - - it 'removes the keys' do - expect(gitlab_shell).to receive(:remove_key).with('key-1234') - - gitlab_shell.remove_keys_not_found_in_db - end + before do + gitlab_shell.remove_all_keys + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') end - context 'authorized_keys_file set' do - before do - gitlab_shell.remove_all_keys - gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') - gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') - end - - it 'removes the keys' do - expect(gitlab_shell).to receive(:remove_key).with('key-1234') + it 'removes the keys' do + expect(gitlab_shell).to receive(:remove_key).with('key-1234') - gitlab_shell.remove_keys_not_found_in_db - end + gitlab_shell.remove_keys_not_found_in_db end end context 'when keys there are duplicate keys in the file that ARE in the DB' do - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - gitlab_shell.remove_all_keys - @key = create(:key) - gitlab_shell.add_key(@key.shell_id, @key.key) - end - - it 'does not remove the key' do - expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}") - - gitlab_shell.remove_keys_not_found_in_db - end + before do + gitlab_shell.remove_all_keys + @key = create(:key) + gitlab_shell.add_key(@key.shell_id, @key.key) end - context 'authorized_keys_file set' do - before do - gitlab_shell.remove_all_keys - @key = create(:key) - gitlab_shell.add_key(@key.shell_id, @key.key) - end - - it 'does not remove the key' do - expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}") + it 'does not remove the key' do + expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}") - gitlab_shell.remove_keys_not_found_in_db - end + gitlab_shell.remove_keys_not_found_in_db end end unless ENV['CI'] # Skip in CI, it takes 1 minute context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do - context 'authorized_keys_file not set' do - before do - stub_gitlab_shell_setting(authorized_keys_file: nil) - gitlab_shell.remove_all_keys - 100.times { |i| create(:key) } # first batch is all in the DB - gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') - end - - it 'removes the keys not in the DB' do - expect(gitlab_shell).to receive(:remove_key).with('key-1234') - - gitlab_shell.remove_keys_not_found_in_db - end + before do + gitlab_shell.remove_all_keys + 100.times { |i| create(:key) } # first batch is all in the DB + gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') end - context 'authorized_keys_file set' do - before do - gitlab_shell.remove_all_keys - 100.times { |i| create(:key) } # first batch is all in the DB - gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF') - end - - it 'removes the keys not in the DB' do - expect(gitlab_shell).to receive(:remove_key).with('key-1234') + it 'removes the keys not in the DB' do + expect(gitlab_shell).to receive(:remove_key).with('key-1234') - gitlab_shell.remove_keys_not_found_in_db - end + gitlab_shell.remove_keys_not_found_in_db end end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 5c5ff46112f..98421cd12d3 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -14,6 +14,12 @@ describe Gitlab::Workhorse do [key, command, params] end + before do + allow(Feature::Gitaly).to receive(:server_feature_flags).and_return({ + 'gitaly-feature-foobar' => 'true' + }) + end + describe ".send_git_archive" do let(:ref) { 'master' } let(:format) { 'zip' } @@ -41,6 +47,7 @@ describe Gitlab::Workhorse do expected_params = metadata.merge( 'GitalyRepository' => repository.gitaly_repository.to_h, 'GitalyServer' => { + features: { 'gitaly-feature-foobar' => 'true' }, address: Gitlab::GitalyClient.address(project.repository_storage), token: Gitlab::GitalyClient.token(project.repository_storage) } @@ -69,6 +76,7 @@ describe Gitlab::Workhorse do expect(command).to eq('git-archive') expect(params).to eq({ 'GitalyServer' => { + features: { 'gitaly-feature-foobar' => 'true' }, address: Gitlab::GitalyClient.address(project.repository_storage), token: Gitlab::GitalyClient.token(project.repository_storage) }, @@ -117,6 +125,7 @@ describe Gitlab::Workhorse do expect(command).to eq("git-format-patch") expect(params).to eq({ 'GitalyServer' => { + features: { 'gitaly-feature-foobar' => 'true' }, address: Gitlab::GitalyClient.address(project.repository_storage), token: Gitlab::GitalyClient.token(project.repository_storage) }, @@ -178,6 +187,7 @@ describe Gitlab::Workhorse do expect(command).to eq("git-diff") expect(params).to eq({ 'GitalyServer' => { + features: { 'gitaly-feature-foobar' => 'true' }, address: Gitlab::GitalyClient.address(project.repository_storage), token: Gitlab::GitalyClient.token(project.repository_storage) }, @@ -315,6 +325,7 @@ describe Gitlab::Workhorse do let(:gitaly_params) do { GitalyServer: { + features: { 'gitaly-feature-foobar' => 'true' }, address: Gitlab::GitalyClient.address('default'), token: Gitlab::GitalyClient.token('default') } @@ -463,6 +474,7 @@ describe Gitlab::Workhorse do expect(command).to eq('git-blob') expect(params).to eq({ 'GitalyServer' => { + features: { 'gitaly-feature-foobar' => 'true' }, address: Gitlab::GitalyClient.address(project.repository_storage), token: Gitlab::GitalyClient.token(project.repository_storage) }, @@ -504,6 +516,7 @@ describe Gitlab::Workhorse do expect(command).to eq('git-snapshot') expect(params).to eq( 'GitalyServer' => { + 'features' => { 'gitaly-feature-foobar' => 'true' }, 'address' => Gitlab::GitalyClient.address(project.repository_storage), 'token' => Gitlab::GitalyClient.token(project.repository_storage) }, diff --git a/spec/lib/peek/views/detailed_view_spec.rb b/spec/lib/peek/views/detailed_view_spec.rb new file mode 100644 index 00000000000..d8660a55ea9 --- /dev/null +++ b/spec/lib/peek/views/detailed_view_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Peek::Views::DetailedView, :request_store do + context 'when a class defines thresholds' do + let(:threshold_view) do + Class.new(described_class) do + def self.thresholds + { + calls: 1, + duration: 10, + individual_call: 5 + } + end + + def key + 'threshold-view' + end + end.new + end + + context 'when the results exceed the calls threshold' do + before do + allow(threshold_view) + .to receive(:detail_store).and_return([{ duration: 0.001 }, { duration: 0.001 }]) + end + + it 'adds a warning to the results key' do + expect(threshold_view.results).to include(warnings: [a_string_matching('threshold-view calls')]) + end + end + + context 'when the results exceed the duration threshold' do + before do + allow(threshold_view) + .to receive(:detail_store).and_return([{ duration: 0.011 }]) + end + + it 'adds a warning to the results key' do + expect(threshold_view.results).to include(warnings: [a_string_matching('threshold-view duration')]) + end + end + + context 'when a single call exceeds the duration threshold' do + before do + allow(threshold_view) + .to receive(:detail_store).and_return([{ duration: 0.001 }, { duration: 0.006 }]) + end + + it 'adds a warning to that call detail entry' do + expect(threshold_view.results) + .to include(details: a_collection_containing_exactly( + { duration: 1.0, warnings: [] }, + { duration: 6.0, warnings: ['6.0 over 5'] } + )) + end + end + end + + context 'when a view does not define thresholds' do + let(:no_threshold_view) { Class.new(described_class).new } + + before do + allow(no_threshold_view) + .to receive(:detail_store).and_return([{ duration: 100 }, { duration: 100 }]) + end + + it 'does not add warnings to the top level' do + expect(no_threshold_view.results).to include(warnings: []) + end + + it 'does not add warnings to call details entries' do + expect(no_threshold_view.results) + .to include(details: a_collection_containing_exactly( + { duration: 100000, warnings: [] }, + { duration: 100000, warnings: [] } + )) + end + end +end diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb index 61096e6c69e..fa9532226f2 100644 --- a/spec/lib/peek/views/redis_detailed_spec.rb +++ b/spec/lib/peek/views/redis_detailed_spec.rb @@ -21,10 +21,10 @@ describe Peek::Views::RedisDetailed, :request_store do expect(subject.results[:details].count).to eq(1) expect(subject.results[:details].first) - .to eq({ - cmd: expected, - duration: 1000 - }) + .to include({ + cmd: expected, + duration: 1000 + }) end end diff --git a/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb new file mode 100644 index 00000000000..1a8123c3f0a --- /dev/null +++ b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SystemCheck::App::AuthorizedKeysPermissionCheck do + subject(:system_check) { described_class.new } + + describe '#skip?' do + subject { system_check.skip? } + + context 'authorized keys enabled' do + it { is_expected.to eq(false) } + end + + context 'authorized keys not enabled' do + before do + stub_application_setting(authorized_keys_enabled: false) + end + + it { is_expected.to eq(true) } + end + end + + describe '#check?' do + subject { system_check.check? } + + before do + expect_next_instance_of(Gitlab::AuthorizedKeys) do |instance| + allow(instance).to receive(:accessible?) { accessible? } + end + end + + context 'authorized keys is accessible' do + let(:accessible?) { true } + + it { is_expected.to eq(true) } + end + + context 'authorized keys is not accessible' do + let(:accessible?) { false } + + it { is_expected.to eq(false) } + end + end + + describe '#repair!' do + subject { system_check.repair! } + + before do + expect_next_instance_of(Gitlab::AuthorizedKeys) do |instance| + allow(instance).to receive(:create) { created } + end + end + + context 'authorized_keys file created' do + let(:created) { true } + + it { is_expected.to eq(true) } + end + + context 'authorized_keys file is not created' do + let(:created) { false } + + it { is_expected.to eq(false) } + end + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 6cba7df114c..56fa26d5f23 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -187,6 +187,22 @@ describe Notify do end end + describe 'that are due soon' do + subject { described_class.issue_due_email(recipient.id, issue.id) } + + before do + issue.update(due_date: Date.tomorrow) + end + + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + end + describe 'status changed' do let(:status) { 'closed' } subject { described_class.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) } diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index 18d4549977c..2429cd408a6 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -81,4 +81,83 @@ describe List do expect(subject.title).to eq 'Closed' end end + + describe '#update_preferences_for' do + let(:user) { create(:user) } + let(:list) { create(:list) } + + context 'when user is present' do + context 'when there are no preferences for user' do + it 'creates new user preferences' do + expect { list.update_preferences_for(user, collapsed: true) }.to change { ListUserPreference.count }.by(1) + expect(list.preferences_for(user).collapsed).to eq(true) + end + end + + context 'when there are preferences for user' do + it 'updates user preferences' do + list.update_preferences_for(user, collapsed: false) + + expect { list.update_preferences_for(user, collapsed: true) }.not_to change { ListUserPreference.count } + expect(list.preferences_for(user).collapsed).to eq(true) + end + end + + context 'when user is nil' do + it 'does not create user preferences' do + expect { list.update_preferences_for(nil, collapsed: true) }.not_to change { ListUserPreference.count } + end + end + end + end + + describe '#preferences_for' do + let(:user) { create(:user) } + let(:list) { create(:list) } + + context 'when user is nil' do + it 'returns not persisted preferences' do + preferences = list.preferences_for(nil) + + expect(preferences.persisted?).to eq(false) + expect(preferences.list_id).to eq(list.id) + expect(preferences.user_id).to be_nil + end + end + + context 'when a user preference already exists' do + before do + list.update_preferences_for(user, collapsed: true) + end + + it 'loads preference for user' do + preferences = list.preferences_for(user) + + expect(preferences).to be_persisted + expect(preferences.collapsed).to eq(true) + end + + context 'when preferences are already loaded for user' do + it 'gets preloaded user preferences' do + fetched_list = described_class.where(id: list.id).with_preferences_for(user).first + + expect(fetched_list).to receive(:preloaded_preferences_for).with(user).and_call_original + + preferences = fetched_list.preferences_for(user) + + expect(preferences.collapsed).to eq(true) + end + end + end + + context 'when preferences for user does not exist' do + it 'returns not persisted preferences' do + preferences = list.preferences_for(user) + + expect(preferences.persisted?).to eq(false) + expect(preferences.user_id).to eq(user.id) + expect(preferences.list_id).to eq(list.id) + end + end + end end diff --git a/spec/models/list_user_preference_spec.rb b/spec/models/list_user_preference_spec.rb new file mode 100644 index 00000000000..1335a3700dc --- /dev/null +++ b/spec/models/list_user_preference_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ListUserPreference do + set(:user) { create(:user) } + set(:list) { create(:list) } + + before do + list.update_preferences_for(user, { collapsed: true }) + end + + describe 'relationships' do + it { is_expected.to belong_to(:list) } + it { is_expected.to belong_to(:user) } + + it do + is_expected.to validate_uniqueness_of(:user_id).scoped_to(:list_id) + .with_message("should have only one list preference per user") + end + end +end diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index 7edeb56efe2..f8d6e500e10 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -40,6 +40,13 @@ describe RemoteMirror, :mailer do expect(remote_mirror).to be_invalid expect(remote_mirror.errors[:url].first).to include('Requests to the local network are not allowed') end + + it 'returns a nil safe_url' do + remote_mirror = build(:remote_mirror, url: 'http://[0:0:0:0:ffff:123.123.123.123]/foo.git') + + expect(remote_mirror.url).to eq('http://[0:0:0:0:ffff:123.123.123.123]/foo.git') + expect(remote_mirror.safe_url).to be_nil + end end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 3ab1818bebb..c94f6d22e74 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -925,19 +925,20 @@ describe API::Internal do it 'returns link to create new merge request' do post api('/internal/post_receive'), params: valid_params - expect(json_response['merge_request_urls']).to match [{ - "branch_name" => branch_name, - "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}", - "new_merge_request" => true - }] + message = <<~MESSAGE.strip + To create a merge request for #{branch_name}, visit: + http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name} + MESSAGE + + expect(json_response['messages']).to include(build_basic_message(message)) end - it 'returns empty array if printing_merge_request_link_enabled is false' do + it 'returns no merge request messages if printing_merge_request_link_enabled is false' do project.update!(printing_merge_request_link_enabled: false) post api('/internal/post_receive'), params: valid_params - expect(json_response['merge_request_urls']).to eq([]) + expect(json_response['messages']).to be_blank end it 'does not invoke MergeRequests::PushOptionsHandlerService' do @@ -968,11 +969,12 @@ describe API::Internal do it 'links to the newly created merge request' do post api('/internal/post_receive'), params: valid_params - expect(json_response['merge_request_urls']).to match [{ - 'branch_name' => branch_name, - 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/1", - 'new_merge_request' => false - }] + message = <<~MESSAGE.strip + View merge request for #{branch_name}: + http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/1 + MESSAGE + + expect(json_response['messages']).to include(build_basic_message(message)) end it 'adds errors on the service instance to warnings' do @@ -982,7 +984,8 @@ describe API::Internal do post api('/internal/post_receive'), params: valid_params - expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error') + message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error" + expect(json_response['messages']).to include(build_alert_message(message)) end it 'adds ActiveRecord errors on invalid MergeRequest records to warnings' do @@ -995,38 +998,39 @@ describe API::Internal do post api('/internal/post_receive'), params: valid_params - expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error') + message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error" + expect(json_response['messages']).to include(build_alert_message(message)) end end context 'broadcast message exists' do let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) } - it 'returns one broadcast message' do + it 'outputs a broadcast message' do post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) - expect(json_response['broadcast_message']).to eq(broadcast_message.message) + expect(json_response['messages']).to include(build_alert_message(broadcast_message.message)) end end context 'broadcast message does not exist' do - it 'returns empty string' do + it 'does not output a broadcast message' do post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) - expect(json_response['broadcast_message']).to eq(nil) + expect(has_alert_messages?(json_response['messages'])).to be_falsey end end context 'nil broadcast message' do - it 'returns empty string' do + it 'does not output a broadcast message' do allow(BroadcastMessage).to receive(:current).and_return(nil) post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) - expect(json_response['broadcast_message']).to eq(nil) + expect(has_alert_messages?(json_response['messages'])).to be_falsey end end @@ -1038,8 +1042,7 @@ describe API::Internal do post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) - expect(json_response["redirected_message"]).to be_present - expect(json_response["redirected_message"]).to eq(project_moved.message) + expect(json_response['messages']).to include(build_basic_message(project_moved.message)) end end @@ -1051,8 +1054,7 @@ describe API::Internal do post api('/internal/post_receive'), params: valid_params expect(response).to have_gitlab_http_status(200) - expect(json_response["project_created_message"]).to be_present - expect(json_response["project_created_message"]).to eq(project_created.message) + expect(json_response['messages']).to include(build_basic_message(project_created.message)) end end @@ -1172,4 +1174,18 @@ describe API::Internal do } ) end + + def build_alert_message(message) + { 'type' => 'alert', 'message' => message } + end + + def build_basic_message(message) + { 'type' => 'basic', 'message' => message } + end + + def has_alert_messages?(messages) + messages.any? do |message| + message['type'] == 'alert' + end + end end diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb index 44b5ee1f130..2857715cdbe 100644 --- a/spec/requests/api/project_snapshots_spec.rb +++ b/spec/requests/api/project_snapshots_spec.rb @@ -6,6 +6,12 @@ describe API::ProjectSnapshots do let(:project) { create(:project) } let(:admin) { create(:admin) } + before do + allow(Feature::Gitaly).to receive(:server_feature_flags).and_return({ + 'gitaly-feature-foobar' => 'true' + }) + end + describe 'GET /projects/:id/snapshot' do def expect_snapshot_response_for(repository) type, params = workhorse_send_data @@ -13,6 +19,7 @@ describe API::ProjectSnapshots do expect(type).to eq('git-snapshot') expect(params).to eq( 'GitalyServer' => { + 'features' => { 'gitaly-feature-foobar' => 'true' }, 'address' => Gitlab::GitalyClient.address(repository.project.repository_storage), 'token' => Gitlab::GitalyClient.token(repository.project.repository_storage) }, diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 29f69b6ce20..58a28e636f1 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -96,6 +96,28 @@ describe API::ProjectSnippets do } end + context 'with a regular user' do + let(:user) { create(:user) } + + before do + project.add_developer(user) + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE]) + params['visibility'] = 'internal' + end + + it 'creates a new snippet' do + post api("/projects/#{project.id}/snippets/", user), params: params + + expect(response).to have_gitlab_http_status(201) + snippet = ProjectSnippet.find(json_response['id']) + expect(snippet.content).to eq(params[:code]) + expect(snippet.description).to eq(params[:description]) + expect(snippet.title).to eq(params[:title]) + expect(snippet.file_name).to eq(params[:file_name]) + expect(snippet.visibility_level).to eq(Snippet::INTERNAL) + end + end + it 'creates a new snippet' do post api("/projects/#{project.id}/snippets/", admin), params: params @@ -108,6 +130,29 @@ describe API::ProjectSnippets do expect(snippet.visibility_level).to eq(Snippet::PUBLIC) end + it 'creates a new snippet with content parameter' do + params[:content] = params.delete(:code) + + post api("/projects/#{project.id}/snippets/", admin), params: params + + expect(response).to have_gitlab_http_status(201) + snippet = ProjectSnippet.find(json_response['id']) + expect(snippet.content).to eq(params[:content]) + expect(snippet.description).to eq(params[:description]) + expect(snippet.title).to eq(params[:title]) + expect(snippet.file_name).to eq(params[:file_name]) + expect(snippet.visibility_level).to eq(Snippet::PUBLIC) + end + + it 'returns 400 when both code and content parameters specified' do + params[:content] = params[:code] + + post api("/projects/#{project.id}/snippets/", admin), params: params + + expect(response).to have_gitlab_http_status(400) + expect(json_response['error']).to eq('code, content are mutually exclusive') + end + it 'returns 400 for missing parameters' do params.delete(:title) @@ -167,7 +212,20 @@ describe API::ProjectSnippets do new_content = 'New content' new_description = 'New description' - put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content, description: new_description } + put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content, description: new_description, visibility: 'private' } + + expect(response).to have_gitlab_http_status(200) + snippet.reload + expect(snippet.content).to eq(new_content) + expect(snippet.description).to eq(new_description) + expect(snippet.visibility).to eq('private') + end + + it 'updates snippet with content parameter' do + new_content = 'New content' + new_description = 'New description' + + put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { content: new_content, description: new_description } expect(response).to have_gitlab_http_status(200) snippet.reload @@ -175,6 +233,13 @@ describe API::ProjectSnippets do expect(snippet.description).to eq(new_description) end + it 'returns 400 when both code and content parameters specified' do + put api("/projects/#{snippet.project.id}/snippets/1234", admin), params: { code: 'some content', content: 'other content' } + + expect(response).to have_gitlab_http_status(400) + expect(json_response['error']).to eq('code, content are mutually exclusive') + end + it 'returns 404 for invalid snippet id' do put api("/projects/#{snippet.project.id}/snippets/1234", admin), params: { title: 'foo' } diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index d600076e9fb..cc05b8d5b45 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -193,18 +193,32 @@ describe API::Snippets do } end - it 'creates a new snippet' do - expect do - post api("/snippets/", user), params: params - end.to change { PersonalSnippet.count }.by(1) + shared_examples 'snippet creation' do + it 'creates a new snippet' do + expect do + post api("/snippets/", user), params: params + end.to change { PersonalSnippet.count }.by(1) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['title']).to eq(params[:title]) + expect(json_response['description']).to eq(params[:description]) + expect(json_response['file_name']).to eq(params[:file_name]) + expect(json_response['visibility']).to eq(params[:visibility]) + end + end + + context 'with restricted visibility settings' do + before do + stub_application_setting(restricted_visibility_levels: + [Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PRIVATE]) + end - expect(response).to have_gitlab_http_status(201) - expect(json_response['title']).to eq(params[:title]) - expect(json_response['description']).to eq(params[:description]) - expect(json_response['file_name']).to eq(params[:file_name]) - expect(json_response['visibility']).to eq(params[:visibility]) + it_behaves_like 'snippet creation' end + it_behaves_like 'snippet creation' + it 'returns 400 for missing parameters' do params.delete(:title) @@ -253,18 +267,33 @@ describe API::Snippets do create(:personal_snippet, author: user, visibility_level: visibility_level) end - it 'updates snippet' do - new_content = 'New content' - new_description = 'New description' + shared_examples 'snippet updates' do + it 'updates a snippet' do + new_content = 'New content' + new_description = 'New description' - put api("/snippets/#{snippet.id}", user), params: { content: new_content, description: new_description } + put api("/snippets/#{snippet.id}", user), params: { content: new_content, description: new_description, visibility: 'internal' } - expect(response).to have_gitlab_http_status(200) - snippet.reload - expect(snippet.content).to eq(new_content) - expect(snippet.description).to eq(new_description) + expect(response).to have_gitlab_http_status(200) + snippet.reload + expect(snippet.content).to eq(new_content) + expect(snippet.description).to eq(new_description) + expect(snippet.visibility).to eq('internal') + end end + context 'with restricted visibility settings' do + before do + stub_application_setting(restricted_visibility_levels: + [Gitlab::VisibilityLevel::PUBLIC, + Gitlab::VisibilityLevel::PRIVATE]) + end + + it_behaves_like 'snippet updates' + end + + it_behaves_like 'snippet updates' + it 'returns 404 for invalid snippet id' do put api("/snippets/1234", user), params: { title: 'foo' } diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb index 276e0f6ff3d..d1483c3c41e 100644 --- a/spec/serializers/merge_request_serializer_spec.rb +++ b/spec/serializers/merge_request_serializer_spec.rb @@ -41,6 +41,14 @@ describe MergeRequestSerializer do end end + context 'noteable merge request serialization' do + let(:serializer) { 'noteable' } + + it 'matches noteable merge request json schema' do + expect(json_entity).to match_schema('entities/merge_request_noteable', strict: true) + end + end + context 'no serializer' do let(:serializer) { nil } diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb index 2ebfd295fa2..2535f339495 100644 --- a/spec/services/boards/lists/list_service_spec.rb +++ b/spec/services/boards/lists/list_service_spec.rb @@ -3,13 +3,15 @@ require 'spec_helper' describe Boards::Lists::ListService do + let(:user) { create(:user) } + describe '#execute' do context 'when board parent is a project' do let(:project) { create(:project) } let(:board) { create(:board, project: project) } let(:label) { create(:label, project: project) } let!(:list) { create(:list, board: board, label: label) } - let(:service) { described_class.new(project, double) } + let(:service) { described_class.new(project, user) } it_behaves_like 'lists list service' end @@ -19,7 +21,7 @@ describe Boards::Lists::ListService do let(:board) { create(:board, group: group) } let(:label) { create(:group_label, group: group) } let!(:list) { create(:list, board: board, label: label) } - let(:service) { described_class.new(group, double) } + let(:service) { described_class.new(group, user) } it_behaves_like 'lists list service' end diff --git a/spec/services/boards/lists/update_service_spec.rb b/spec/services/boards/lists/update_service_spec.rb new file mode 100644 index 00000000000..f28bbab941a --- /dev/null +++ b/spec/services/boards/lists/update_service_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Boards::Lists::UpdateService do + let(:user) { create(:user) } + let!(:list) { create(:list, board: board, position: 0) } + + shared_examples 'moving list' do + context 'when user can admin list' do + it 'calls Lists::MoveService to update list position' do + board.parent.add_developer(user) + service = described_class.new(board.parent, user, position: 1) + + expect(Boards::Lists::MoveService).to receive(:new).with(board.parent, user, { position: 1 }).and_call_original + expect_any_instance_of(Boards::Lists::MoveService).to receive(:execute).with(list) + + service.execute(list) + end + end + + context 'when user cannot admin list' do + it 'does not call Lists::MoveService to update list position' do + service = described_class.new(board.parent, user, position: 1) + + expect(Boards::Lists::MoveService).not_to receive(:new) + + service.execute(list) + end + end + end + + shared_examples 'updating list preferences' do + context 'when user can read list' do + it 'updates list preference for user' do + board.parent.add_guest(user) + service = described_class.new(board.parent, user, collapsed: true) + + service.execute(list) + + expect(list.preferences_for(user).collapsed).to eq(true) + end + end + + context 'when user cannot read list' do + it 'does not update list preference for user' do + service = described_class.new(board.parent, user, collapsed: true) + + service.execute(list) + + expect(list.preferences_for(user).collapsed).to be_nil + end + end + end + + describe '#execute' do + context 'when position parameter is present' do + context 'for projects' do + it_behaves_like 'moving list' do + let(:project) { create(:project, :private) } + let(:board) { create(:board, project: project) } + end + end + + context 'for groups' do + it_behaves_like 'moving list' do + let(:group) { create(:group, :private) } + let(:board) { create(:board, group: group) } + end + end + end + + context 'when collapsed parameter is present' do + context 'for projects' do + it_behaves_like 'updating list preferences' do + let(:project) { create(:project, :private) } + let(:board) { create(:board, project: project) } + end + end + + context 'for groups' do + it_behaves_like 'updating list preferences' do + let(:group) { create(:group, :private) } + let(:board) { create(:board, group: group) } + end + end + end + end +end diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb index 9b83f65a17e..7d2491b3a49 100644 --- a/spec/services/create_snippet_service_spec.rb +++ b/spec/services/create_snippet_service_spec.rb @@ -34,6 +34,19 @@ describe CreateSnippetService do expect(snippet.errors.any?).to be_falsey expect(snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) end + + describe "when visibility level is passed as a string" do + before do + @opts[:visibility] = 'internal' + @opts.delete(:visibility_level) + end + + it "assigns the correct visibility level" do + snippet = create_snippet(nil, @user, @opts) + expect(snippet.errors.any?).to be_falsey + expect(snippet.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end end describe 'usage counter' do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index f46f9633c1c..910fe3b50b7 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -212,6 +212,13 @@ describe SystemNoteService do expect(build_note([assignee, assignee1, assignee2], [assignee, assignee1])).to eq \ "unassigned @#{assignee2.username}" end + + it 'builds a correct phrase when the locale is different' do + Gitlab::I18n.with_locale('pt-BR') do + expect(build_note([assignee, assignee1, assignee2], [assignee3])).to eq \ + "assigned to @#{assignee3.username} and unassigned @#{assignee.username}, @#{assignee1.username}, and @#{assignee2.username}" + end + end end describe '.change_milestone' do diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb index 0678f54c195..19b35dca6a7 100644 --- a/spec/services/update_snippet_service_spec.rb +++ b/spec/services/update_snippet_service_spec.rb @@ -32,12 +32,25 @@ describe UpdateSnippetService do expect(@snippet.visibility_level).to eq(old_visibility) end - it 'admins should be able to update to pubic visibility' do + it 'admins should be able to update to public visibility' do old_visibility = @snippet.visibility_level update_snippet(@project, @admin, @snippet, @opts) expect(@snippet.visibility_level).not_to eq(old_visibility) expect(@snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) end + + describe "when visibility level is passed as a string" do + before do + @opts[:visibility] = 'internal' + @opts.delete(:visibility_level) + end + + it "assigns the correct visibility level" do + update_snippet(@project, @user, @snippet, @opts) + expect(@snippet.errors.any?).to be_falsey + expect(@snippet.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end end describe 'usage counter' do diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb index 47804411b9d..0da3470433c 100644 --- a/spec/views/groups/edit.html.haml_spec.rb +++ b/spec/views/groups/edit.html.haml_spec.rb @@ -23,7 +23,7 @@ describe 'groups/edit.html.haml' do render expect(rendered).to have_content("Prevent sharing a project within #{test_group.name} with other groups") - expect(rendered).to have_css('.descr', text: 'help text here') + expect(rendered).to have_css('.js-descr', text: 'help text here') expect(rendered).to have_field('group_share_with_group_lock', checkbox_options) end end |