diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-10 12:08:16 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-10 12:08:16 +0000 |
commit | 1fa79760ad2d4bd67f5c5a27f372a7533b9b7c69 (patch) | |
tree | ffdfbd9113743831ff4f1290959a62cf6567fde5 /spec | |
parent | 82fa8a3d1e8466ef36b58604d20fcc145ea12118 (diff) | |
download | gitlab-ce-1fa79760ad2d4bd67f5c5a27f372a7533b9b7c69.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
23 files changed, 569 insertions, 89 deletions
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index c6345a2153c..a224a2101d3 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -23,7 +23,6 @@ describe Projects::ClustersController do describe 'functionality' do context 'when project has one or more clusters' do - let(:project) { create(:project) } let!(:enabled_cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } let!(:disabled_cluster) { create(:cluster, :disabled, :provided_by_gcp, :production_environment, projects: [project]) } @@ -53,8 +52,6 @@ describe Projects::ClustersController do end context 'when project does not have a cluster' do - let(:project) { create(:project) } - it 'returns an empty state page' do go diff --git a/spec/features/projects/container_registry_spec.rb b/spec/features/projects/container_registry_spec.rb index b99dab39c34..de17d831fbd 100644 --- a/spec/features/projects/container_registry_spec.rb +++ b/spec/features/projects/container_registry_spec.rb @@ -126,6 +126,7 @@ describe 'Container Registry', :js do describe 'image repo details' do before do + stub_container_registry_tags(repository: %r{my/image}, tags: ('1'..'20').to_a, with_manifest: true) visit_container_registry_details 'my/image' end @@ -140,12 +141,18 @@ describe 'Container Registry', :js do it 'user removes a specific tag from container repository' do service = double('service') expect(service).to receive(:execute).with(container_repository) { { status: :success } } - expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: ['latest']) { service } + expect(Projects::ContainerRepository::DeleteTagsService).to receive(:new).with(container_repository.project, user, tags: ['1']) { service } - click_on(class: 'js-delete-registry') + first('.js-delete-registry').click expect(find('.modal .modal-title')).to have_content _('Remove tag') find('.modal .modal-footer .btn-danger').click end + + it('pagination navigate to the second page') do + pagination = find('.gl-pagination') + pagination.click_link('2') + expect(page).to have_content '20' + end end end end diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index ee312a5811d..28d24073b79 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -95,6 +95,29 @@ shared_examples_for 'snippet editor' do link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) end + + context 'when the git operation fails' do + let(:error) { 'This is a git error' } + + before do + allow_next_instance_of(Snippets::CreateService) do |instance| + allow(instance).to receive(:create_commit).and_raise(StandardError, error) + end + + fill_form + + click_button('Create snippet') + wait_for_requests + end + + it 'displays the error' do + expect(page).to have_content(error) + end + + it 'renders new page' do + expect(page).to have_content('New Snippet') + end + end end context 'when a user is not authenticated' do diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index 88f7896bfa6..f200355c6d2 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -78,6 +78,29 @@ shared_examples_for 'snippet editor' do expect(reqs.first.status_code).to eq(200) end + context 'when the git operation fails' do + let(:error) { 'This is a git error' } + + before do + allow_next_instance_of(Snippets::CreateService) do |instance| + allow(instance).to receive(:create_commit).and_raise(StandardError, error) + end + + fill_form + + click_button('Create snippet') + wait_for_requests + end + + it 'displays the error' do + expect(page).to have_content(error) + end + + it 'renders new page' do + expect(page).to have_content('New Snippet') + end + end + it 'validation fails for the first time' do fill_in 'personal_snippet_title', with: 'My Snippet Title' click_button('Create snippet') diff --git a/spec/javascripts/blob/3d_viewer/mesh_object_spec.js b/spec/frontend/blob/3d_viewer/mesh_object_spec.js index 60be285039f..60be285039f 100644 --- a/spec/javascripts/blob/3d_viewer/mesh_object_spec.js +++ b/spec/frontend/blob/3d_viewer/mesh_object_spec.js diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/frontend/blob/viewer/index_spec.js index 06c06613887..7239f59c6fa 100644 --- a/spec/javascripts/blob/viewer/index_spec.js +++ b/spec/frontend/blob/viewer/index_spec.js @@ -9,9 +9,14 @@ describe('Blob viewer', () => { let blob; let mock; + const jQueryMock = { + tooltip: jest.fn(), + }; + preloadFixtures('snippets/show.html'); beforeEach(() => { + $.fn.extend(jQueryMock); mock = new MockAdapter(axios); loadFixtures('snippets/show.html'); @@ -27,7 +32,7 @@ describe('Blob viewer', () => { html: '<div>testing</div>', }); - spyOn(axios, 'get').and.callThrough(); + jest.spyOn(axios, 'get'); }); afterEach(() => { @@ -38,7 +43,7 @@ describe('Blob viewer', () => { it('loads source file after switching views', done => { document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); - setTimeout(() => { + setImmediate(() => { expect( document .querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]') @@ -54,7 +59,7 @@ describe('Blob viewer', () => { new BlobViewer(); - setTimeout(() => { + setImmediate(() => { expect( document .querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]') @@ -65,26 +70,20 @@ describe('Blob viewer', () => { }); }); - it('doesnt reload file if already loaded', done => { + it('doesnt reload file if already loaded', () => { const asyncClick = () => new Promise(resolve => { document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); - setTimeout(resolve); + setImmediate(resolve); }); - asyncClick() + return asyncClick() .then(() => asyncClick()) .then(() => { expect( document.querySelector('.blob-viewer[data-type="simple"]').getAttribute('data-loaded'), ).toBe('true'); - - done(); - }) - .catch(() => { - fail(); - done(); }); }); @@ -100,13 +99,13 @@ describe('Blob viewer', () => { }); it('has tooltip when disabled', () => { - expect(copyButton.getAttribute('data-original-title')).toBe( + expect(copyButton.getAttribute('title')).toBe( 'Switch to the source to copy the file contents', ); }); it('is blurred when clicked and disabled', () => { - spyOn(copyButton, 'blur'); + jest.spyOn(copyButton, 'blur').mockImplementation(() => {}); copyButton.click(); @@ -114,7 +113,7 @@ describe('Blob viewer', () => { }); it('is not blurred when clicked and not disabled', () => { - spyOn(copyButton, 'blur'); + jest.spyOn(copyButton, 'blur').mockImplementation(() => {}); copyButton.classList.remove('disabled'); copyButton.click(); @@ -125,7 +124,7 @@ describe('Blob viewer', () => { it('enables after switching to simple view', done => { document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); - setTimeout(() => { + setImmediate(() => { expect(copyButton.classList.contains('disabled')).toBeFalsy(); done(); @@ -135,8 +134,8 @@ describe('Blob viewer', () => { it('updates tooltip after switching to simple view', done => { document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); - setTimeout(() => { - expect(copyButton.getAttribute('data-original-title')).toBe('Copy file contents'); + setImmediate(() => { + expect(copyButton.getAttribute('title')).toBe('Copy file contents'); done(); }); @@ -155,7 +154,7 @@ describe('Blob viewer', () => { it('adds active class to new viewer button', () => { const simpleBtn = document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]'); - spyOn(simpleBtn, 'blur'); + jest.spyOn(simpleBtn, 'blur').mockImplementation(() => {}); blob.switchToViewer('simple'); @@ -174,7 +173,7 @@ describe('Blob viewer', () => { blob.switchToViewer('simple'); blob.switchToViewer('rich'); - expect(axios.get.calls.count()).toBe(1); + expect(axios.get.mock.calls.length).toBe(1); }); }); }); diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js index 2b83f7e7351..15c6b36af03 100644 --- a/spec/frontend/registry/explorer/pages/details_spec.js +++ b/spec/frontend/registry/explorer/pages/details_spec.js @@ -219,7 +219,7 @@ describe('Details Page', () => { dispatchSpy.mockResolvedValue(); wrapper.setData({ currentPage: 2 }); expect(store.dispatch).toHaveBeenCalledWith('requestTagsList', { - id: wrapper.vm.$route.params.id, + params: wrapper.vm.$route.params.id, pagination: { page: 2 }, }); }); diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap new file mode 100644 index 00000000000..4f1d46dffef --- /dev/null +++ b/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap @@ -0,0 +1,94 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] = ` +<div + class="form-group" +> + <label> + + Visibility level + + <gl-link-stub + href="/foo/bar" + target="_blank" + > + <gl-icon-stub + name="question" + size="12" + /> + </gl-link-stub> + </label> + + <gl-form-group-stub + id="visibility-level-setting" + > + <gl-form-radio-group-stub + checked="0" + disabledfield="disabled" + htmlfield="html" + options="" + stacked="" + textfield="text" + valuefield="value" + > + <gl-form-radio-stub + class="mb-3" + value="0" + > + <div + class="d-flex align-items-center" + > + <gl-icon-stub + name="lock" + size="16" + /> + + <span + class="font-weight-bold ml-1" + > + Private + </span> + </div> + </gl-form-radio-stub> + <gl-form-radio-stub + class="mb-3" + value="1" + > + <div + class="d-flex align-items-center" + > + <gl-icon-stub + name="shield" + size="16" + /> + + <span + class="font-weight-bold ml-1" + > + Internal + </span> + </div> + </gl-form-radio-stub> + <gl-form-radio-stub + class="mb-3" + value="2" + > + <div + class="d-flex align-items-center" + > + <gl-icon-stub + name="earth" + size="16" + /> + + <span + class="font-weight-bold ml-1" + > + Public + </span> + </div> + </gl-form-radio-stub> + </gl-form-radio-group-stub> + </gl-form-group-stub> +</div> +`; diff --git a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js new file mode 100644 index 00000000000..5104d742bb3 --- /dev/null +++ b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js @@ -0,0 +1,94 @@ +import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue'; +import { GlFormRadio } from '@gitlab/ui'; +import { SNIPPET_VISIBILITY } from '~/snippets/constants'; +import { mount, shallowMount } from '@vue/test-utils'; + +describe('Snippet Visibility Edit component', () => { + let wrapper; + let radios; + const defaultHelpLink = '/foo/bar'; + const defaultVisibilityLevel = '0'; + + function findElements(sel) { + return wrapper.findAll(sel); + } + + function createComponent( + { + helpLink = defaultHelpLink, + isProjectSnippet = false, + visibilityLevel = defaultVisibilityLevel, + } = {}, + deep = false, + ) { + const method = deep ? mount : shallowMount; + wrapper = method.call(this, SnippetVisibilityEdit, { + propsData: { + helpLink, + isProjectSnippet, + visibilityLevel, + }, + }); + radios = findElements(GlFormRadio); + } + + afterEach(() => { + wrapper.destroy(); + }); + + describe('rendering', () => { + it('matches the snapshot', () => { + createComponent(); + expect(wrapper.element).toMatchSnapshot(); + }); + + it.each` + label | value + ${SNIPPET_VISIBILITY.private.label} | ${`0`} + ${SNIPPET_VISIBILITY.internal.label} | ${`1`} + ${SNIPPET_VISIBILITY.public.label} | ${`2`} + `('should render correct $label label', ({ label, value }) => { + createComponent(); + const radio = radios.at(parseInt(value, 10)); + + expect(radio.attributes('value')).toBe(value); + expect(radio.text()).toContain(label); + }); + + describe('rendered help-text', () => { + it.each` + description | value | label + ${SNIPPET_VISIBILITY.private.description} | ${`0`} | ${SNIPPET_VISIBILITY.private.label} + ${SNIPPET_VISIBILITY.internal.description} | ${`1`} | ${SNIPPET_VISIBILITY.internal.label} + ${SNIPPET_VISIBILITY.public.description} | ${`2`} | ${SNIPPET_VISIBILITY.public.label} + `('should render correct $label description', ({ description, value }) => { + createComponent({}, true); + + const help = findElements('.help-text').at(parseInt(value, 10)); + + expect(help.text()).toBe(description); + }); + + it('renders correct Private description for a project snippet', () => { + createComponent({ isProjectSnippet: true }, true); + + const helpText = findElements('.help-text') + .at(0) + .text(); + + expect(helpText).not.toContain(SNIPPET_VISIBILITY.private.description); + expect(helpText).toBe(SNIPPET_VISIBILITY.private.description_project); + }); + }); + }); + + describe('functionality', () => { + it('pre-selects correct option in the list', () => { + const pos = 1; + + createComponent({ visibilityLevel: `${pos}` }, true); + const radio = radios.at(pos); + expect(radio.find('input[type="radio"]').element.checked).toBe(true); + }); + }); +}); diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index d229753a0f0..7db45c05cb5 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -23,14 +23,30 @@ describe SubmoduleHelper do it 'detects ssh on standard port' do allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(22) # set this just to be sure allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix)) - stub_url([config.user, '@', config.host, ':gitlab-org/gitlab-foss.git'].join('')) + stub_url([config.ssh_user, '@', config.host, ':gitlab-org/gitlab-foss.git'].join('')) + expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')]) + end + + it 'detects ssh on standard port without a username' do + allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(22) # set this just to be sure + allow(Gitlab.config.gitlab_shell).to receive(:ssh_user).and_return('') + allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix)) + stub_url([config.host, ':gitlab-org/gitlab-foss.git'].join('')) expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')]) end it 'detects ssh on non-standard port' do allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(2222) allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix)) - stub_url(['ssh://', config.user, '@', config.host, ':2222/gitlab-org/gitlab-foss.git'].join('')) + stub_url(['ssh://', config.ssh_user, '@', config.host, ':2222/gitlab-org/gitlab-foss.git'].join('')) + expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')]) + end + + it 'detects ssh on non-standard port without a username' do + allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(2222) + allow(Gitlab.config.gitlab_shell).to receive(:ssh_user).and_return('') + allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix)) + stub_url(['ssh://', config.host, ':2222/gitlab-org/gitlab-foss.git'].join('')) expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-foss'), namespace_project_tree_path('gitlab-org', 'gitlab-foss', 'hash')]) end diff --git a/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb new file mode 100644 index 00000000000..073f93ce542 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::Ci::Config::Entry::Inherit::Default do + using RSpec::Parameterized::TableSyntax + + subject { described_class.new(config) } + + context 'validations' do + where(:config, :valid) do + true | true + false | true + %w[image] | true + %w[unknown] | false + %i[image] | false + [true] | false + "string" | false + end + + with_them do + it do + expect(subject.valid?).to eq(valid) + end + end + end + + describe '#inherit?' do + where(:config, :inherit) do + true | true + false | false + %w[image] | true + %w[before_script] | false + end + + with_them do + it do + expect(subject.inherit?('image')).to eq(inherit) + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb new file mode 100644 index 00000000000..06deed11c15 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::Ci::Config::Entry::Inherit::Variables do + using RSpec::Parameterized::TableSyntax + + subject { described_class.new(config) } + + context 'validations' do + where(:config, :valid) do + true | true + false | true + %w[A] | true + %w[A B] | true + %i[image] | true + [true] | false + "string" | false + end + + with_them do + it do + expect(subject.valid?).to eq(valid) + end + end + end + + describe '#inherit?' do + where(:config, :inherit) do + true | true + false | false + %w[A] | true + %w[B] | false + end + + with_them do + it do + expect(subject.inherit?('A')).to eq(inherit) + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 7df0eccb3ed..b6279485426 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Ci::Config::Entry::Job do end before do - allow(entry).to receive_message_chain(:inherit_entry, :default_value).and_return(true) + allow(entry).to receive_message_chain(:inherit_entry, :default_entry, :inherit?).and_return(true) end end diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb index 5c2c6520f25..8447a29c772 100644 --- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb @@ -269,13 +269,13 @@ describe Gitlab::Ci::Config::Entry::Processable do context 'when root yaml variables are used' do let(:variables) do Gitlab::Ci::Config::Entry::Variables.new( - A: 'root', C: 'root' + A: 'root', C: 'root', D: 'root' ).value end it 'does return all variables and overwrite them' do expect(entry.value).to include( - variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root' } + variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root', 'D' => 'root' } ) end @@ -293,32 +293,61 @@ describe Gitlab::Ci::Config::Entry::Processable do ) end end + + context 'when inherit of only specific variable is enabled' do + let(:config) do + { + variables: { A: 'job', B: 'job' }, + inherit: { variables: ['D'] } + } + end + + it 'does return only job variables' do + expect(entry.value).to include( + variables: { 'A' => 'job', 'B' => 'job', 'D' => 'root' } + ) + end + end end end context 'of default:tags' do using RSpec::Parameterized::TableSyntax - where(:default_tags, :tags, :inherit_default, :result) do - nil | %w[a b] | nil | %w[a b] - nil | %w[a b] | true | %w[a b] - nil | %w[a b] | false | %w[a b] - %w[b c] | %w[a b] | nil | %w[a b] - %w[b c] | %w[a b] | true | %w[a b] - %w[b c] | %w[a b] | false | %w[a b] - %w[b c] | nil | nil | %w[b c] - %w[b c] | nil | true | %w[b c] - %w[b c] | nil | false | nil + where(:name, :default_tags, :tags, :inherit_default, :result) do + "only local tags" | nil | %w[a b] | nil | %w[a b] + "only local tags" | nil | %w[a b] | true | %w[a b] + "only local tags" | nil | %w[a b] | false | %w[a b] + "global and local tags" | %w[b c] | %w[a b] | nil | %w[a b] + "global and local tags" | %w[b c] | %w[a b] | true | %w[a b] + "global and local tags" | %w[b c] | %w[a b] | false | %w[a b] + "only global tags" | %w[b c] | nil | nil | %w[b c] + "only global tags" | %w[b c] | nil | true | %w[b c] + "only global tags" | %w[b c] | nil | false | nil + "only global tags" | %w[b c] | nil | %w[image] | nil + "only global tags" | %w[b c] | nil | %w[tags] | %w[b c] end with_them do - let(:config) { { tags: tags, inherit: { default: inherit_default } } } - let(:default_specified_tags) { double('tags', 'specified?' => true, 'valid?' => true, 'value' => default_tags) } + let(:config) do + { tags: tags, + inherit: { default: inherit_default } } + end + + let(:default_specified_tags) do + double('tags', + 'specified?' => true, + 'valid?' => true, + 'value' => default_tags, + 'errors' => []) + end before do allow(default).to receive('[]').with(:tags).and_return(default_specified_tags) entry.compose!(deps) + + expect(entry).to be_valid end it { expect(entry.tags_value).to eq(result) } diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 5c85a136972..af0a85f6c4e 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -515,6 +515,8 @@ module Gitlab nil | ["global script"] { default: false } | nil { default: true } | ["global script"] + { default: %w[before_script] } | ["global script"] + { default: %w[image] } | nil end with_them do @@ -527,26 +529,28 @@ module Gitlab it { expect(subject[:options][:before_script]).to eq(result) } end - end - context "in default context" do - using RSpec::Parameterized::TableSyntax + context "in default context" do + using RSpec::Parameterized::TableSyntax - where(:inherit, :result) do - nil | ["global script"] - { default: false } | nil - { default: true } | ["global script"] - end - - with_them do - let(:config) do - { - default: { before_script: ["global script"] }, - test: { script: ["script"], inherit: inherit } - } + where(:inherit, :result) do + nil | ["global script"] + { default: false } | nil + { default: true } | ["global script"] + { default: %w[before_script] } | ["global script"] + { default: %w[image] } | nil end - it { expect(subject[:options][:before_script]).to eq(result) } + with_them do + let(:config) do + { + default: { before_script: ["global script"] }, + test: { script: ["script"], inherit: inherit } + } + end + + it { expect(subject[:options][:before_script]).to eq(result) } + end end end @@ -845,6 +849,18 @@ module Gitlab ) end end + + context 'when specific variables are to inherited' do + let(:inherit) { { variables: %w[VAR1 VAR4] } } + + it 'returns all unique variables and inherits only specified variables' do + expect(subject).to contain_exactly( + { key: 'VAR4', value: 'global4', public: true }, + { key: 'VAR1', value: 'value1', public: true }, + { key: 'VAR2', value: 'value2', public: true } + ) + end + end end context 'when job variables are defined' do diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 99c2a364dfc..43a489f6df0 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -89,6 +89,13 @@ describe Gitlab::Middleware::Go do it 'returns the full project path' do expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch) end + + context 'with an empty ssh_user' do + it 'returns the full project path' do + allow(Gitlab.config.gitlab_shell).to receive(:ssh_user).and_return('') + expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch) + end + end end context 'without access to the project' do @@ -234,7 +241,9 @@ describe Gitlab::Middleware::Go do def expect_response_with_path(response, protocol, path, branch) repository_url = case protocol when :ssh - "ssh://#{Gitlab.config.gitlab.user}@#{Gitlab.config.gitlab.host}/#{path}.git" + shell = Gitlab.config.gitlab_shell + user = "#{shell.ssh_user}@" unless shell.ssh_user.empty? + "ssh://#{user}#{shell.ssh_host}/#{path}.git" when :http, nil "http://#{Gitlab.config.gitlab.host}/#{path}.git" end diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb index 47442f4ee86..d815534e873 100644 --- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb @@ -20,6 +20,8 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do let(:queue_duration_seconds) { double('queue duration seconds metric') } let(:completion_seconds_metric) { double('completion seconds metric') } let(:user_execution_seconds_metric) { double('user execution seconds metric') } + let(:db_seconds_metric) { double('db seconds metric') } + let(:gitaly_seconds_metric) { double('gitaly seconds metric') } let(:failed_total_metric) { double('failed total metric') } let(:retried_total_metric) { double('retried total metric') } let(:running_jobs_metric) { double('running jobs metric') } @@ -28,6 +30,8 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_queue_duration_seconds, anything, anything, anything).and_return(queue_duration_seconds) allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything, anything).and_return(completion_seconds_metric) allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_cpu_seconds, anything, anything, anything).and_return(user_execution_seconds_metric) + allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_db_seconds, anything, anything, anything).and_return(db_seconds_metric) + allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_gitaly_seconds, anything, anything, anything).and_return(gitaly_seconds_metric) allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric) allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric) allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :all).and_return(running_jobs_metric) @@ -55,16 +59,23 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do let(:queue_duration_for_job) { 0.01 } + let(:db_duration) { 3 } + let(:gitaly_duration) { 4 } + before do allow(subject).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after) allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after) allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job) + allow(ActiveRecord::LogSubscriber).to receive(:runtime).and_return(db_duration * 1000) + allow(Gitlab::GitalyClient).to receive(:query_time).and_return(gitaly_duration) expect(running_jobs_metric).to receive(:increment).with(labels, 1) expect(running_jobs_metric).to receive(:increment).with(labels, -1) expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration) + expect(db_seconds_metric).to receive(:observe).with(labels_with_job_status, db_duration) + expect(gitaly_seconds_metric).to receive(:observe).with(labels_with_job_status, gitaly_duration) expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration) end diff --git a/spec/lib/sentry/client/issue_spec.rb b/spec/lib/sentry/client/issue_spec.rb index 62cbfbf0b30..0f57d38d290 100644 --- a/spec/lib/sentry/client/issue_spec.rb +++ b/spec/lib/sentry/client/issue_spec.rb @@ -254,6 +254,34 @@ describe Sentry::Client::Issue do expect(subject.gitlab_issue).to eq('https://gitlab.com/gitlab-org/gitlab/issues/1') end + context 'when issue annotations exist' do + before do + issue_sample_response['annotations'] = [ + nil, + '', + "<a href=\"http://github.com/issues/6\">github-issue-6</a>", + "<div>annotation</a>", + "<a href=\"http://localhost/gitlab-org/gitlab/issues/2\">gitlab-org/gitlab#2</a>" + ] + stub_sentry_request(sentry_request_url, body: issue_sample_response) + end + + it 'has a correct GitLab issue url' do + expect(subject.gitlab_issue).to eq('http://localhost/gitlab-org/gitlab/issues/2') + end + end + + context 'when no GitLab issue is linked' do + before do + issue_sample_response['pluginIssues'] = [] + stub_sentry_request(sentry_request_url, body: issue_sample_response) + end + + it 'does not find a GitLab issue' do + expect(subject.gitlab_issue).to be_nil + end + end + it 'has the correct tags' do expect(subject.tags).to eq({ level: issue_sample_response['level'], logger: issue_sample_response['logger'] }) end diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb index 120175fdd05..088d37725aa 100644 --- a/spec/models/snippet_repository_spec.rb +++ b/spec/models/snippet_repository_spec.rb @@ -168,34 +168,42 @@ describe SnippetRepository do end end - context 'when files are not named' do - let(:data) do - [ - { - file_path: '', - content: 'foo', - action: :create - }, - { - file_path: '', - content: 'bar', - action: :create - }, - { - file_path: 'foo.txt', - content: 'bar', - action: :create - } - ] + shared_examples 'snippet repository with file names' do |*filenames| + it 'sets a name for unnamed files' do + ls_files = snippet.repository.ls_files(nil) + expect(ls_files).to include(*filenames) end + end + + let_it_be(:named_snippet) { { file_path: 'fee.txt', content: 'bar', action: :create } } + let_it_be(:unnamed_snippet) { { file_path: '', content: 'dummy', action: :create } } - it 'sets a name for non named files' do + context 'when some files are not named' do + let(:data) { [named_snippet] + Array.new(2) { unnamed_snippet.clone } } + + before do expect do snippet_repository.multi_files_action(user, data, commit_opts) end.not_to raise_error + end + + it_behaves_like 'snippet repository with file names', 'snippetfile1.txt', 'snippetfile2.txt' + end - expect(snippet.repository.ls_files(nil)).to include('snippetfile1.txt', 'snippetfile2.txt', 'foo.txt') + context 'repository already has 10 unnamed snippets' do + let(:pre_populate_data) { Array.new(10) { unnamed_snippet.clone } } + let(:data) { [named_snippet] + Array.new(2) { unnamed_snippet.clone } } + + before do + # Pre-populate repository with 9 unnamed snippets. + snippet_repository.multi_files_action(user, pre_populate_data, commit_opts) + + expect do + snippet_repository.multi_files_action(user, data, commit_opts) + end.not_to raise_error end + + it_behaves_like 'snippet repository with file names', 'snippetfile10.txt', 'snippetfile11.txt' end end diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipients/build_service_spec.rb index 9c2283f555b..2e848c2f04d 100644 --- a/spec/services/notification_recipient_service_spec.rb +++ b/spec/services/notification_recipients/build_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe NotificationRecipientService do +describe NotificationRecipients::BuildService do let(:service) { described_class } let(:assignee) { create(:user) } let(:project) { create(:project, :public) } diff --git a/spec/services/notification_recipients/builder/default_spec.rb b/spec/services/notification_recipients/builder/default_spec.rb new file mode 100644 index 00000000000..307ca40248e --- /dev/null +++ b/spec/services/notification_recipients/builder/default_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe NotificationRecipients::Builder::Default do + describe '#build!' do + let_it_be(:group) { create(:group, :public) } + let_it_be(:project) { create(:project, :public, group: group).tap { |p| p.add_developer(project_watcher) } } + let_it_be(:issue) { create(:issue, project: project) } + + let_it_be(:current_user) { create(:user) } + let_it_be(:other_user) { create(:user) } + let_it_be(:participant) { create(:user) } + let_it_be(:group_watcher) { create(:user) } + let_it_be(:project_watcher) { create(:user) } + + let_it_be(:notification_setting_project_w) { create(:notification_setting, source: project, user: project_watcher, level: 2) } + let_it_be(:notification_setting_group_w) { create(:notification_setting, source: group, user: group_watcher, level: 2) } + + subject { described_class.new(issue, current_user, action: :new).tap { |s| s.build! } } + + context 'participants and project watchers' do + before do + expect(issue).to receive(:participants).and_return([participant, current_user]) + end + + it 'adds all participants and watchers' do + expect(subject.recipients.map(&:user)).to include(participant, project_watcher, group_watcher) + expect(subject.recipients.map(&:user)).not_to include(other_user) + end + end + + context 'subscribers' do + it 'adds all subscribers' do + subscriber = create(:user) + non_subscriber = create(:user) + create(:subscription, project: project, user: subscriber, subscribable: issue, subscribed: true) + create(:subscription, project: project, user: non_subscriber, subscribable: issue, subscribed: false) + + expect(subject.recipients.map(&:user)).to include(subscriber) + end + end + end +end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index d2680606738..96906b4ca3c 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -710,7 +710,7 @@ describe NotificationService, :mailer do user_3 = create(:user) recipient_1 = NotificationRecipient.new(user_1, :custom, custom_action: :new_release) recipient_2 = NotificationRecipient.new(user_2, :custom, custom_action: :new_release) - allow(NotificationRecipientService).to receive(:build_new_release_recipients).and_return([recipient_1, recipient_2]) + allow(NotificationRecipients::BuildService).to receive(:build_new_release_recipients).and_return([recipient_1, recipient_2]) release diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb index 5c853f8b7d7..ffad3c8b8e5 100644 --- a/spec/services/snippets/create_service_spec.rb +++ b/spec/services/snippets/create_service_spec.rb @@ -185,12 +185,10 @@ describe Snippets::CreateService do expect { subject }.not_to change { Snippet.count } end - it 'does not create the repository' do - expect(snippet.repository_exists?).to be_falsey - end - - it 'destroys the existing repository' do - expect(Repositories::DestroyService).to receive(:new).and_call_original + it 'destroys the created repository' do + expect_next_instance_of(Repository) do |instance| + expect(instance).to receive(:remove).and_call_original + end subject end |