diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-04 18:09:36 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-04 18:09:36 +0300 |
commit | e0655935eb32ba057b6ced978940076681d71177 (patch) | |
tree | b931975665fc002f346bdd3c5242fe644316f86c /spec | |
parent | 8322f051740fba113c5c1f6fba96b9c943240746 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
16 files changed, 1341 insertions, 924 deletions
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb index 0f5ad013a64..ad98e9d1f24 100644 --- a/spec/factories/ci/job_artifacts.rb +++ b/spec/factories/ci/job_artifacts.rb @@ -229,6 +229,16 @@ FactoryBot.define do end end + trait :coverage_with_paths_not_relative_to_project_root do + file_type { :cobertura } + file_format { :gzip } + + after(:build) do |artifact, evaluator| + artifact.file = fixture_file_upload( + Rails.root.join('spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz'), 'application/x-gzip') + end + end + trait :coverage_with_corrupted_data do file_type { :cobertura } file_format { :gzip } diff --git a/spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz b/spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz Binary files differnew file mode 100644 index 00000000000..c4adc63fcce --- /dev/null +++ b/spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz diff --git a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js index 3d615d9d05f..6df2efd624d 100644 --- a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js +++ b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js @@ -14,21 +14,18 @@ describe('Users admin page Modal Manager', () => { }, }; - const actionModals = { - action1: ModalStub, - action2: ModalStub, - }; - let wrapper; const createComponent = (props = {}) => { wrapper = mount(UserModalManager, { propsData: { - actionModals, modalConfiguration, csrfToken: 'dummyCSRF', ...props, }, + stubs: { + DeleteUserModal: ModalStub, + }, }); }; @@ -43,11 +40,6 @@ describe('Users admin page Modal Manager', () => { expect(wrapper.find({ ref: 'modal' }).exists()).toBeFalsy(); }); - it('throws if non-existing action is requested', () => { - createComponent(); - expect(() => wrapper.vm.show({ glModalAction: 'non-existing' })).toThrow(); - }); - it('throws if action has no proper configuration', () => { createComponent({ modalConfiguration: {}, diff --git a/spec/frontend/whats_new/components/app_spec.js b/spec/frontend/whats_new/components/app_spec.js index 7a9340da87a..cba550b19db 100644 --- a/spec/frontend/whats_new/components/app_spec.js +++ b/spec/frontend/whats_new/components/app_spec.js @@ -1,6 +1,6 @@ import { createLocalVue, mount } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlDrawer, GlInfiniteScroll, GlTabs } from '@gitlab/ui'; +import { GlDrawer, GlInfiniteScroll } from '@gitlab/ui'; import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import App from '~/whats_new/components/app.vue'; @@ -16,18 +16,12 @@ const localVue = createLocalVue(); localVue.use(Vuex); describe('App', () => { + const propsData = { storageKey: 'storage-key' }; let wrapper; let store; let actions; let state; let trackingSpy; - let gitlabDotCom = true; - - const buildProps = () => ({ - storageKey: 'storage-key', - versions: ['3.11', '3.10'], - gitlabDotCom, - }); const buildWrapper = () => { actions = { @@ -51,7 +45,7 @@ describe('App', () => { wrapper = mount(App, { localVue, store, - propsData: buildProps(), + propsData, directives: { GlResizeObserver: createMockDirective(), }, @@ -59,171 +53,112 @@ describe('App', () => { }; const findInfiniteScroll = () => wrapper.find(GlInfiniteScroll); + const emitBottomReached = () => findInfiniteScroll().vm.$emit('bottomReached'); - const setup = async () => { + beforeEach(async () => { document.body.dataset.page = 'test-page'; document.body.dataset.namespaceId = 'namespace-840'; trackingSpy = mockTracking('_category_', null, jest.spyOn); buildWrapper(); - wrapper.vm.$store.state.features = [ - { title: 'Whats New Drawer', url: 'www.url.com', release: 3.11 }, - ]; + wrapper.vm.$store.state.features = [{ title: 'Whats New Drawer', url: 'www.url.com' }]; wrapper.vm.$store.state.drawerBodyHeight = MOCK_DRAWER_BODY_HEIGHT; await wrapper.vm.$nextTick(); - }; + }); afterEach(() => { wrapper.destroy(); unmockTracking(); }); - describe('gitlab.com', () => { - beforeEach(() => { - setup(); - }); - - const getDrawer = () => wrapper.find(GlDrawer); - - it('contains a drawer', () => { - expect(getDrawer().exists()).toBe(true); - }); - - it('dispatches openDrawer and tracking calls when mounted', () => { - expect(actions.openDrawer).toHaveBeenCalledWith(expect.any(Object), 'storage-key'); - expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_whats_new_drawer', { - label: 'namespace_id', - value: 'namespace-840', - }); - }); - - it('dispatches closeDrawer when clicking close', () => { - getDrawer().vm.$emit('close'); - expect(actions.closeDrawer).toHaveBeenCalled(); - }); - - it.each([true, false])('passes open property', async openState => { - wrapper.vm.$store.state.open = openState; - - await wrapper.vm.$nextTick(); - - expect(getDrawer().props('open')).toBe(openState); - }); - - it('renders features when provided via ajax', () => { - expect(actions.fetchItems).toHaveBeenCalled(); - expect(wrapper.find('[data-test-id="feature-title"]').text()).toBe('Whats New Drawer'); - }); - - it('send an event when feature item is clicked', () => { - trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn); + const getDrawer = () => wrapper.find(GlDrawer); - const link = wrapper.find('.whats-new-item-title-link'); - triggerEvent(link.element); + it('contains a drawer', () => { + expect(getDrawer().exists()).toBe(true); + }); - expect(trackingSpy.mock.calls[1]).toMatchObject([ - '_category_', - 'click_whats_new_item', - { - label: 'Whats New Drawer', - property: 'www.url.com', - }, - ]); + it('dispatches openDrawer and tracking calls when mounted', () => { + expect(actions.openDrawer).toHaveBeenCalledWith(expect.any(Object), 'storage-key'); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_whats_new_drawer', { + label: 'namespace_id', + value: 'namespace-840', }); + }); - it('renders infinite scroll', () => { - const scroll = findInfiniteScroll(); - - expect(scroll.props()).toMatchObject({ - fetchedItems: wrapper.vm.$store.state.features.length, - maxListHeight: MOCK_DRAWER_BODY_HEIGHT, - }); - }); + it('dispatches closeDrawer when clicking close', () => { + getDrawer().vm.$emit('close'); + expect(actions.closeDrawer).toHaveBeenCalled(); + }); - describe('bottomReached', () => { - const emitBottomReached = () => findInfiniteScroll().vm.$emit('bottomReached'); + it.each([true, false])('passes open property', async openState => { + wrapper.vm.$store.state.open = openState; - beforeEach(() => { - actions.fetchItems.mockClear(); - }); + await wrapper.vm.$nextTick(); - it('when nextPage exists it calls fetchItems', () => { - wrapper.vm.$store.state.pageInfo = { nextPage: 840 }; - emitBottomReached(); + expect(getDrawer().props('open')).toBe(openState); + }); - expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), { page: 840 }); - }); + it('renders features when provided via ajax', () => { + expect(actions.fetchItems).toHaveBeenCalled(); + expect(wrapper.find('h5').text()).toBe('Whats New Drawer'); + }); - it('when nextPage does not exist it does not call fetchItems', () => { - wrapper.vm.$store.state.pageInfo = { nextPage: null }; - emitBottomReached(); + it('send an event when feature item is clicked', () => { + trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn); - expect(actions.fetchItems).not.toHaveBeenCalled(); - }); - }); + const link = wrapper.find('.whats-new-item-title-link'); + triggerEvent(link.element); - it('calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered', () => { - const { value } = getBinding(getDrawer().element, 'gl-resize-observer'); - - value(); + expect(trackingSpy.mock.calls[1]).toMatchObject([ + '_category_', + 'click_whats_new_item', + { + label: 'Whats New Drawer', + property: 'www.url.com', + }, + ]); + }); - expect(getDrawerBodyHeight).toHaveBeenCalledWith(wrapper.find(GlDrawer).element); + it('renders infinite scroll', () => { + const scroll = findInfiniteScroll(); - expect(actions.setDrawerBodyHeight).toHaveBeenCalledWith( - expect.any(Object), - MOCK_DRAWER_BODY_HEIGHT, - ); + expect(scroll.props()).toMatchObject({ + fetchedItems: wrapper.vm.$store.state.features.length, + maxListHeight: MOCK_DRAWER_BODY_HEIGHT, }); }); - describe('self managed', () => { - const findTabs = () => wrapper.find(GlTabs); - - const clickSecondTab = async () => { - const secondTab = wrapper.findAll('.nav-link').at(1); - await secondTab.trigger('click'); - await new Promise(resolve => requestAnimationFrame(resolve)); - }; - + describe('bottomReached', () => { beforeEach(() => { - gitlabDotCom = false; - setup(); + actions.fetchItems.mockClear(); }); - it('renders tabs with drawer body height and content', () => { - const scroll = findInfiniteScroll(); - const tabs = findTabs(); + it('when nextPage exists it calls fetchItems', () => { + wrapper.vm.$store.state.pageInfo = { nextPage: 840 }; + emitBottomReached(); - expect(scroll.exists()).toBe(false); - expect(tabs.attributes().style).toBe(`height: ${MOCK_DRAWER_BODY_HEIGHT}px;`); - expect(wrapper.find('h5').text()).toBe('Whats New Drawer'); + expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), 840); }); - describe('fetchVersion', () => { - beforeEach(() => { - actions.fetchItems.mockClear(); - }); + it('when nextPage does not exist it does not call fetchItems', () => { + wrapper.vm.$store.state.pageInfo = { nextPage: null }; + emitBottomReached(); - it('when version isnt fetched, clicking a tab calls fetchItems', async () => { - const fetchVersionSpy = jest.spyOn(wrapper.vm, 'fetchVersion'); - await clickSecondTab(); + expect(actions.fetchItems).not.toHaveBeenCalled(); + }); + }); - expect(fetchVersionSpy).toHaveBeenCalledWith('3.10'); - expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), { version: '3.10' }); - }); + it('calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered', () => { + const { value } = getBinding(getDrawer().element, 'gl-resize-observer'); - it('when version has been fetched, clicking a tab calls fetchItems', async () => { - wrapper.vm.$store.state.features.push({ title: 'GitLab Stories', release: 3.1 }); - await wrapper.vm.$nextTick(); + value(); - const fetchVersionSpy = jest.spyOn(wrapper.vm, 'fetchVersion'); - await clickSecondTab(); + expect(getDrawerBodyHeight).toHaveBeenCalledWith(wrapper.find(GlDrawer).element); - expect(fetchVersionSpy).toHaveBeenCalledWith('3.10'); - expect(actions.fetchItems).not.toHaveBeenCalled(); - expect(wrapper.find('.tab-pane.active h5').text()).toBe('GitLab Stories'); - }); - }); + expect(actions.setDrawerBodyHeight).toHaveBeenCalledWith( + expect.any(Object), + MOCK_DRAWER_BODY_HEIGHT, + ); }); }); diff --git a/spec/frontend/whats_new/store/actions_spec.js b/spec/frontend/whats_new/store/actions_spec.js index 82f17a2726f..12722b1b3b1 100644 --- a/spec/frontend/whats_new/store/actions_spec.js +++ b/spec/frontend/whats_new/store/actions_spec.js @@ -41,23 +41,6 @@ describe('whats new actions', () => { axiosMock.restore(); }); - it('passes arguments', () => { - axiosMock.reset(); - - axiosMock - .onGet('/-/whats_new', { params: { page: 8, version: 40 } }) - .replyOnce(200, [{ title: 'GitLab Stories' }]); - - testAction( - actions.fetchItems, - { page: 8, version: 40 }, - {}, - expect.arrayContaining([ - { type: types.ADD_FEATURES, payload: [{ title: 'GitLab Stories' }] }, - ]), - ); - }); - it('if already fetching, does not fetch', () => { testAction(actions.fetchItems, {}, { fetching: true }, []); }); diff --git a/spec/helpers/whats_new_helper_spec.rb b/spec/helpers/whats_new_helper_spec.rb index 017826921ff..cdb4fc60629 100644 --- a/spec/helpers/whats_new_helper_spec.rb +++ b/spec/helpers/whats_new_helper_spec.rb @@ -10,7 +10,7 @@ RSpec.describe WhatsNewHelper do let(:release_item) { double(:item) } before do - allow(ReleaseHighlight).to receive(:versions).and_return([84.0]) + allow(ReleaseHighlight).to receive(:most_recent_version).and_return(84.0) end it { is_expected.to eq('display-whats-new-notification-84.0') } @@ -18,7 +18,7 @@ RSpec.describe WhatsNewHelper do context 'when most recent release highlights do NOT exist' do before do - allow(ReleaseHighlight).to receive(:versions).and_return(nil) + allow(ReleaseHighlight).to receive(:most_recent_version).and_return(nil) end it { is_expected.to be_nil } @@ -44,14 +44,4 @@ RSpec.describe WhatsNewHelper do end end end - - describe '#whats_new_versions' do - let(:versions) { [84.0] } - - it 'returns ReleaseHighlight.versions' do - expect(ReleaseHighlight).to receive(:versions).and_return(versions) - - expect(helper.whats_new_versions).to eq(versions) - end - end end diff --git a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb index 45e87466532..2313378d1e9 100644 --- a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb +++ b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb @@ -4,207 +4,690 @@ require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Parsers::Coverage::Cobertura do describe '#parse!' do - subject { described_class.new.parse!(cobertura, coverage_report) } + subject(:parse_report) { described_class.new.parse!(cobertura, coverage_report, project_path: project_path, worktree_paths: paths) } let(:coverage_report) { Gitlab::Ci::Reports::CoverageReports.new } + let(:project_path) { 'foo/bar' } + let(:paths) { ['app/user.rb'] } + + let(:cobertura) do + <<~EOF + <coverage> + #{sources_xml} + #{classes_xml} + </coverage> + EOF + end context 'when data is Cobertura style XML' do - context 'when there is no <class>' do - let(:cobertura) { '' } + shared_examples_for 'ignoring sources, project_path, and worktree_paths' do + context 'when there is no <class>' do + let(:classes_xml) { '' } - it 'parses XML and returns empty coverage' do - expect { subject }.not_to raise_error + it 'parses XML and returns empty coverage' do + expect { parse_report }.not_to raise_error - expect(coverage_report.files).to eq({}) + expect(coverage_report.files).to eq({}) + end end - end - context 'when there is a <sources>' do - shared_examples_for 'ignoring sources' do - it 'parses XML without errors' do - expect { subject }.not_to raise_error + context 'when there is a single <class>' do + context 'with no lines' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="app.rb"></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns empty coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({}) + end + end - expect(coverage_report.files).to eq({}) + context 'with a single line' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="app.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns a single file with coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } }) + end + end + + context 'without a package parent' do + let(:classes_xml) do + <<~EOF + <packages> + <class filename="app.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + </packages> + EOF + end + + it 'parses XML and returns a single file with coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } }) + end + end + + context 'with multiple lines and methods info' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="app.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns a single file with coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } }) + end end end - context 'and has a single source' do - let(:cobertura) do - <<-EOF.strip_heredoc + context 'when there are multiple <class>' do + context 'without a package parent' do + let(:classes_xml) do + <<~EOF + <packages> + <class filename="app.rb"><methods/><lines> + <line number="1" hits="2"/> + </lines></class> + <class filename="foo.rb"><methods/><lines> + <line number="6" hits="1"/> + </lines></class> + </packages> + EOF + end + + it 'parses XML and returns coverage information per class' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 }, 'foo.rb' => { 6 => 1 } }) + end + end + + context 'with the same filename and different lines' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="app.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class filename="app.rb"><methods/><lines> + <line number="6" hits="1"/> + <line number="7" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns a single file with merged coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } }) + end + end + + context 'with the same filename and lines' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="app.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class filename="app.rb"><methods/><lines> + <line number="1" hits="1"/> + <line number="2" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns a single file with summed-up coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 3, 2 => 1 } }) + end + end + + context 'with missing filename' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="app.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class><methods/><lines> + <line number="6" hits="1"/> + <line number="7" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and ignores class with missing name' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } }) + end + end + + context 'with invalid line information' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="app.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class filename="app.rb"><methods/><lines> + <line null="test" hits="1"/> + <line number="7" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'raises an error' do + expect { parse_report }.to raise_error(described_class::InvalidLineInformationError) + end + end + end + end + + context 'when there is no <sources>' do + let(:sources_xml) { '' } + + it_behaves_like 'ignoring sources, project_path, and worktree_paths' + end + + context 'when there is a <sources>' do + context 'and has a single source with a pattern for Go projects' do + let(:project_path) { 'local/go' } # Make sure we're not making false positives + let(:sources_xml) do + <<~EOF <sources> - <source>project/src</source> + <source>/usr/local/go/src</source> </sources> EOF end - it_behaves_like 'ignoring sources' + it_behaves_like 'ignoring sources, project_path, and worktree_paths' end - context 'and has multiple sources' do - let(:cobertura) do - <<-EOF.strip_heredoc + context 'and has multiple sources with a pattern for Go projects' do + let(:project_path) { 'local/go' } # Make sure we're not making false positives + let(:sources_xml) do + <<~EOF <sources> - <source>project/src/foo</source> - <source>project/src/bar</source> + <source>/usr/local/go/src</source> + <source>/go/src</source> </sources> EOF end - it_behaves_like 'ignoring sources' + it_behaves_like 'ignoring sources, project_path, and worktree_paths' end - end - context 'when there is a single <class>' do - context 'with no lines' do - let(:cobertura) do - <<-EOF.strip_heredoc - <classes><class filename="app.rb"></class></classes> + context 'and has a single source but already is at the project root path' do + let(:sources_xml) do + <<~EOF + <sources> + <source>builds/#{project_path}</source> + </sources> EOF end - it 'parses XML and returns empty coverage' do - expect { subject }.not_to raise_error + it_behaves_like 'ignoring sources, project_path, and worktree_paths' + end - expect(coverage_report.files).to eq({}) + context 'and has multiple sources but already are at the project root path' do + let(:sources_xml) do + <<~EOF + <sources> + <source>builds/#{project_path}/</source> + <source>builds/somewhere/#{project_path}</source> + </sources> + EOF end + + it_behaves_like 'ignoring sources, project_path, and worktree_paths' end - context 'with a single line' do - let(:cobertura) do - <<-EOF.strip_heredoc - <classes> - <class filename="app.rb"><lines> - <line number="1" hits="2"/> - </lines></class> - </classes> + context 'and has a single source that is not at the project root path' do + let(:sources_xml) do + <<~EOF + <sources> + <source>builds/#{project_path}/app</source> + </sources> EOF end - it 'parses XML and returns a single file with coverage' do - expect { subject }.not_to raise_error + context 'when there is no <class>' do + let(:classes_xml) { '' } - expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } }) - end - end + it 'parses XML and returns empty coverage' do + expect { parse_report }.not_to raise_error - context 'with multipe lines and methods info' do - let(:cobertura) do - <<-EOF.strip_heredoc - <classes> - <class filename="app.rb"><methods/><lines> - <line number="1" hits="2"/> - <line number="2" hits="0"/> - </lines></class> - </classes> - EOF + expect(coverage_report.files).to eq({}) + end end - it 'parses XML and returns a single file with coverage' do - expect { subject }.not_to raise_error + context 'when there is a single <class>' do + context 'with no lines' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns empty coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({}) + end + end + + context 'with a single line but the filename cannot be determined based on extracted source and worktree paths' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="member.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns empty coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({}) + end + end + + context 'with a single line' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns a single file with the filename relative to project root' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2 } }) + end + end + + context 'with multiple lines and methods info' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns a single file with the filename relative to project root' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } }) + end + end + end - expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } }) + context 'when there are multiple <class>' do + context 'with the same filename but the filename cannot be determined based on extracted source and worktree paths' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="member.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class filename="member.rb"><methods/><lines> + <line number="6" hits="1"/> + <line number="7" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns empty coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({}) + end + end + + context 'without a parent package' do + let(:classes_xml) do + <<~EOF + <packages> + <class filename="user.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class filename="user.rb"><methods/><lines> + <line number="6" hits="1"/> + <line number="7" hits="1"/> + </lines></class> + </packages> + EOF + end + + it 'parses XML and returns coverage information with the filename relative to project root' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } }) + end + end + + context 'with the same filename and different lines' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class filename="user.rb"><methods/><lines> + <line number="6" hits="1"/> + <line number="7" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns a single file with merged coverage, and with the filename relative to project root' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } }) + end + end + + context 'with the same filename and lines' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class filename="user.rb"><methods/><lines> + <line number="1" hits="1"/> + <line number="2" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns a single file with summed-up coverage, and with the filename relative to project root' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 3, 2 => 1 } }) + end + end + + context 'with missing filename' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class><methods/><lines> + <line number="6" hits="1"/> + <line number="7" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and ignores class with missing name' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } }) + end + end + + context 'with filename that cannot be determined based on extracted source and worktree paths' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class filename="member.rb"><methods/><lines> + <line number="6" hits="1"/> + <line number="7" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and ignores class with undetermined filename' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } }) + end + end + + context 'with invalid line information' do + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><methods/><lines> + <line number="1" hits="2"/> + <line number="2" hits="0"/> + </lines></class> + <class filename="user.rb"><methods/><lines> + <line null="test" hits="1"/> + <line number="7" hits="1"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'raises an error' do + expect { parse_report }.to raise_error(described_class::InvalidLineInformationError) + end + end end end - end - context 'when there are multipe <class>' do - context 'with the same filename and different lines' do - let(:cobertura) do - <<-EOF.strip_heredoc - <classes> - <class filename="app.rb"><methods/><lines> - <line number="1" hits="2"/> - <line number="2" hits="0"/> - </lines></class> - <class filename="app.rb"><methods/><lines> - <line number="6" hits="1"/> - <line number="7" hits="1"/> - </lines></class> - </classes> + context 'and has multiple sources that are not at the project root path' do + let(:sources_xml) do + <<~EOF + <sources> + <source>builds/#{project_path}/app1/</source> + <source>builds/#{project_path}/app2/</source> + </sources> EOF end - it 'parses XML and returns a single file with merged coverage' do - expect { subject }.not_to raise_error - - expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } }) + context 'and a class filename is available under multiple extracted sources' do + let(:paths) { ['app1/user.rb', 'app2/user.rb'] } + + let(:classes_xml) do + <<~EOF + <package name="app1"> + <classes> + <class filename="user.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + </classes> + </package> + <package name="app2"> + <classes> + <class filename="user.rb"><lines> + <line number="2" hits="3"/> + </lines></class> + </classes> + </package> + EOF + end + + it 'parses XML and returns the files with the filename relative to project root' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ + 'app1/user.rb' => { 1 => 2 }, + 'app2/user.rb' => { 2 => 3 } + }) + end end - end - context 'with the same filename and lines' do - let(:cobertura) do - <<-EOF.strip_heredoc - <packages><package><classes> - <class filename="app.rb"><methods/><lines> - <line number="1" hits="2"/> - <line number="2" hits="0"/> - </lines></class> - <class filename="app.rb"><methods/><lines> - <line number="1" hits="1"/> - <line number="2" hits="1"/> - </lines></class> - </classes></package></packages> - EOF + context 'and a class filename is available under one of the extracted sources' do + let(:paths) { ['app1/member.rb', 'app2/user.rb', 'app2/pet.rb'] } + + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns a single file with the filename relative to project root using the extracted source where it is first found under' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({ 'app2/user.rb' => { 1 => 2 } }) + end end - it 'parses XML and returns a single file with summed-up coverage' do - expect { subject }.not_to raise_error + context 'and a class filename is not found under any of the extracted sources' do + let(:paths) { ['app1/member.rb', 'app2/pet.rb'] } - expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 3, 2 => 1 } }) + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + </classes></package></packages> + EOF + end + + it 'parses XML and returns empty coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({}) + end end - end - context 'with missing filename' do - let(:cobertura) do - <<-EOF.strip_heredoc - <classes> - <class filename="app.rb"><methods/><lines> - <line number="1" hits="2"/> - <line number="2" hits="0"/> - </lines></class> - <class><methods/><lines> - <line number="6" hits="1"/> - <line number="7" hits="1"/> - </lines></class> - </classes> - EOF + context 'and a class filename is not found under any of the extracted sources within the iteratable limit' do + let(:paths) { ['app2/user.rb'] } + + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="record.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + <class filename="user.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + </classes></package></packages> + EOF + end + + before do + stub_const("#{described_class}::MAX_SOURCES", 1) + end + + it 'parses XML and returns empty coverage' do + expect { parse_report }.not_to raise_error + + expect(coverage_report.files).to eq({}) + end end + end + end - it 'parses XML and ignores class with missing name' do - expect { subject }.not_to raise_error + shared_examples_for 'non-smart parsing' do + let(:sources_xml) do + <<~EOF + <sources> + <source>builds/foo/bar/app</source> + </sources> + EOF + end - expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } }) - end + let(:classes_xml) do + <<~EOF + <packages><package name="app"><classes> + <class filename="user.rb"><lines> + <line number="1" hits="2"/> + </lines></class> + </classes></package></packages> + EOF end - context 'with invalid line information' do - let(:cobertura) do - <<-EOF.strip_heredoc - <classes> - <class filename="app.rb"><methods/><lines> - <line number="1" hits="2"/> - <line number="2" hits="0"/> - </lines></class> - <class filename="app.rb"><methods/><lines> - <line null="test" hits="1"/> - <line number="7" hits="1"/> - </lines></class> - </classes> - EOF - end + it 'parses XML and returns filenames unchanged just as how they are found in the class node' do + expect { parse_report }.not_to raise_error - it 'raises an error' do - expect { subject }.to raise_error(described_class::CoberturaParserError) - end + expect(coverage_report.files).to eq({ 'user.rb' => { 1 => 2 } }) end end + + context 'when project_path is not present' do + let(:project_path) { nil } + let(:paths) { ['app/user.rb'] } + + it_behaves_like 'non-smart parsing' + end + + context 'when worktree_paths is not present' do + let(:project_path) { 'foo/bar' } + let(:paths) { nil } + + it_behaves_like 'non-smart parsing' + end end context 'when data is not Cobertura style XML' do let(:cobertura) { { coverage: '12%' }.to_json } it 'raises an error' do - expect { subject }.to raise_error(described_class::CoberturaParserError) + expect { parse_report }.to raise_error(described_class::InvalidXMLError) end end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 0efb014fdfc..ade78efe239 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -4059,13 +4059,40 @@ RSpec.describe Ci::Build do end end + context 'when there is a Cobertura coverage report with class filename paths not relative to project root' do + before do + allow(build.project).to receive(:full_path).and_return('root/javademo') + allow(build.pipeline).to receive(:all_worktree_paths).and_return(['src/main/java/com/example/javademo/User.java']) + + create(:ci_job_artifact, :coverage_with_paths_not_relative_to_project_root, job: build, project: build.project) + end + + it 'parses blobs and add the results to the coverage report with corrected paths' do + expect { subject }.not_to raise_error + + expect(coverage_report.files.keys).to match_array(['src/main/java/com/example/javademo/User.java']) + end + + context 'and smart_cobertura_parser feature flag is disabled' do + before do + stub_feature_flags(smart_cobertura_parser: false) + end + + it 'parses blobs and add the results to the coverage report with unmodified paths' do + expect { subject }.not_to raise_error + + expect(coverage_report.files.keys).to match_array(['com/example/javademo/User.java']) + end + end + end + context 'when there is a corrupted Cobertura coverage report' do before do create(:ci_job_artifact, :coverage_with_corrupted_data, job: build, project: build.project) end it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Ci::Parsers::Coverage::Cobertura::CoberturaParserError) + expect { subject }.to raise_error(Gitlab::Ci::Parsers::Coverage::Cobertura::InvalidLineInformationError) end end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 255333dbc29..24f2b12c87b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2578,6 +2578,14 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do it 'receives a pending event once' do expect(WebMock).to have_requested_pipeline_hook('pending').once end + + it 'builds hook data once' do + create(:pipelines_email_service, project: project) + + expect(Gitlab::DataBuilder::Pipeline).to receive(:build).once.and_call_original + + pipeline.execute_hooks + end end context 'when build is run' do @@ -2639,6 +2647,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do it 'did not execute pipeline_hook after touched' do expect(WebMock).not_to have_requested(:post, hook.url) end + + it 'does not build hook data' do + expect(Gitlab::DataBuilder::Pipeline).not_to receive(:build) + + pipeline.execute_hooks + end end def create_build(name, stage_idx) @@ -3404,6 +3418,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do ]) end + it 'does not execute N+1 queries' do + single_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project) + single_rspec = create(:ci_build, :success, name: 'rspec', pipeline: single_build_pipeline, project: project) + create(:ci_job_artifact, :cobertura, job: single_rspec, project: project) + + control = ActiveRecord::QueryRecorder.new { single_build_pipeline.coverage_reports } + + expect { subject }.not_to exceed_query_limit(control) + end + context 'when builds are retried' do let!(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) } let!(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) } diff --git a/spec/models/release_highlight_spec.rb b/spec/models/release_highlight_spec.rb index 15477144894..b7817a04134 100644 --- a/spec/models/release_highlight_spec.rb +++ b/spec/models/release_highlight_spec.rb @@ -3,44 +3,21 @@ require 'spec_helper' RSpec.describe ReleaseHighlight do - let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) } - let(:cache_mock) { double(:cache_mock) } - - before do - allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob) - allow(cache_mock).to receive(:fetch).with('release_highlight:file_paths', expires_in: 1.hour).and_yield - end - - after do - ReleaseHighlight.instance_variable_set(:@file_paths, nil) - end - - describe '.for_version' do - subject { ReleaseHighlight.for_version(version: version) } - - let(:version) { '1.1' } - - context 'with version param that exists' do - it 'returns items from that version' do - expect(subject.items.first['title']).to eq("It's gonna be a bright") - end - end - - context 'with version param that does NOT exist' do - let(:version) { '84.0' } - - it 'returns nil' do - expect(subject).to be_nil - end - end - end - - describe '.paginated' do + describe '#paginated' do + let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) } + let(:cache_mock) { double(:cache_mock) } let(:dot_com) { false } before do allow(Gitlab).to receive(:com?).and_return(dot_com) + allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob) + expect(Rails).to receive(:cache).twice.and_return(cache_mock) + expect(cache_mock).to receive(:fetch).with('release_highlight:file_paths', expires_in: 1.hour).and_yield + end + + after do + ReleaseHighlight.instance_variable_set(:@file_paths, nil) end context 'with page param' do @@ -113,51 +90,46 @@ RSpec.describe ReleaseHighlight do end end - describe '.most_recent_item_count' do - subject { ReleaseHighlight.most_recent_item_count } + describe '.most_recent_version' do + subject { ReleaseHighlight.most_recent_version } - context 'when recent release items exist' do - it 'returns the count from the most recent file' do - allow(ReleaseHighlight).to receive(:paginated).and_return(double(:paginated, items: [double(:item)])) + context 'when version exist' do + let(:release_item) { double(:item) } - expect(subject).to eq(1) + before do + allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [release_item] }) + allow(release_item).to receive(:[]).with('release').and_return(84.0) end + + it { is_expected.to eq(84.0) } end - context 'when recent release items do NOT exist' do - it 'returns nil' do + context 'when most recent release highlights do NOT exist' do + before do allow(ReleaseHighlight).to receive(:paginated).and_return(nil) - - expect(subject).to be_nil end + + it { is_expected.to be_nil } end end - describe '.versions' do - it 'returns versions from the file paths' do - expect(ReleaseHighlight.versions).to eq(['1.5', '1.2', '1.1']) - end + describe '#most_recent_item_count' do + subject { ReleaseHighlight.most_recent_item_count } - context 'when there are more than 12 versions' do - let(:file_paths) do - i = 0 - Array.new(20) { "20201225_01_#{i += 1}.yml" } - end + context 'when recent release items exist' do + it 'returns the count from the most recent file' do + allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [double(:item)] }) - it 'limits to 12 versions' do - allow(ReleaseHighlight).to receive(:file_paths).and_return(file_paths) - expect(ReleaseHighlight.versions.count).to eq(12) + expect(subject).to eq(1) end end - end - - describe 'QueryResult' do - subject { ReleaseHighlight::QueryResult.new(items: items, next_page: 2) } - let(:items) { [:item] } + context 'when recent release items do NOT exist' do + it 'returns nil' do + allow(ReleaseHighlight).to receive(:paginated).and_return(nil) - it 'responds to map' do - expect(subject.map(&:to_s)).to eq(items.map(&:to_s)) + expect(subject).to be_nil + end end end end diff --git a/spec/requests/api/nuget_packages_spec.rb b/spec/requests/api/nuget_packages_spec.rb deleted file mode 100644 index 62f244c433b..00000000000 --- a/spec/requests/api/nuget_packages_spec.rb +++ /dev/null @@ -1,533 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -RSpec.describe API::NugetPackages do - include WorkhorseHelpers - include PackagesManagerApiSpecHelpers - include HttpBasicAuthHelpers - - let_it_be(:user) { create(:user) } - let_it_be(:project, reload: true) { create(:project, :public) } - let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } - let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } - let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } - - describe 'GET /api/v4/projects/:id/packages/nuget' do - let(:url) { "/projects/#{project.id}/packages/nuget/index.json" } - - subject { get api(url) } - - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - context 'personal token' do - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success - 'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success - 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success - 'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success - 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - - subject { get api(url), headers: headers } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - end - - context 'with job token' do - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success - 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') } - let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) } - - subject { get api(url), headers: headers } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - end - end - - it_behaves_like 'deploy token for package GET requests' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' - end - end - - describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do - let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } - let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } } - let(:url) { "/projects/#{project.id}/packages/nuget/authorize" } - let(:headers) { {} } - - subject { put api(url), headers: headers } - - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success - 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - let(:headers) { user_headers.merge(workhorse_header) } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - end - - it_behaves_like 'deploy token for package uploads' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' - end - end - - describe 'PUT /api/v4/projects/:id/packages/nuget' do - let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } - let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } } - let_it_be(:file_name) { 'package.nupkg' } - let(:url) { "/projects/#{project.id}/packages/nuget" } - let(:headers) { {} } - let(:params) { { package: temp_file(file_name) } } - let(:file_key) { :package } - let(:send_rewritten_field) { true } - - subject do - workhorse_finalize( - api(url), - method: :put, - file_key: file_key, - params: params, - headers: headers, - send_rewritten_field: send_rewritten_field - ) - end - - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget upload' | :created - 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | true | true | 'process nuget upload' | :created - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - let(:headers) { user_headers.merge(workhorse_header) } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - end - - it_behaves_like 'deploy token for package uploads' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' - - context 'file size above maximum limit' do - let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) } - - before do - allow_next_instance_of(UploadedFile) do |uploaded_file| - allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1) - end - end - - it_behaves_like 'returning response status', :bad_request - end - end - end - - describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do - include_context 'with expected presenters dependency groups' - - let_it_be(:package_name) { 'Dummy.Package' } - let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) } - let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } } - let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" } - - subject { get api(url) } - - before do - packages.each { |pkg| create_dependencies_for(pkg) } - end - - context 'without the need for license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success - 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - - subject { get api(url), headers: headers } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - - it_behaves_like 'deploy token for package GET requests' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' - end - end - end - - describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do - include_context 'with expected presenters dependency groups' - - let_it_be(:package_name) { 'Dummy.Package' } - let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) } - let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') } - let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" } - - subject { get api(url) } - - before do - create_dependencies_for(package) - end - - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success - 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - - subject { get api(url), headers: headers } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - end - - it_behaves_like 'deploy token for package GET requests' - - context 'with invalid package name' do - let_it_be(:package_name) { 'Unkown' } - - it_behaves_like 'rejects nuget packages access', :developer, :not_found - end - end - end - - describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do - let_it_be(:package_name) { 'Dummy.Package' } - let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) } - let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" } - - subject { get api(url) } - - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success - 'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success - 'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success - 'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success - 'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success - 'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success - 'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - - subject { get api(url), headers: headers } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - end - - it_behaves_like 'deploy token for package GET requests' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' - end - end - - describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do - let_it_be(:package_name) { 'Dummy.Package' } - let_it_be(:package) { create(:nuget_package, project: project, name: package_name) } - - let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" } - - subject { get api(url) } - - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success - 'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success - 'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success - 'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success - 'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success - 'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success - 'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - - subject { get api(url), headers: headers } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - end - - it_behaves_like 'deploy token for package GET requests' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' - end - end - - describe 'GET /api/v4/projects/:id/packages/nuget/query' do - let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) } - let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') } - let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) } - let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) } - let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) } - let_it_be(:package_e) { create(:nuget_package, name: 'Foo.BarE', project: project) } - let(:search_term) { 'uMmy' } - let(:take) { 26 } - let(:skip) { 0 } - let(:include_prereleases) { true } - let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } } - let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" } - - subject { get api(url) } - - context 'without the need for a license' do - context 'with valid project' do - using RSpec::Parameterized::TableSyntax - - where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget search request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget search request' | :success - 'PUBLIC' | :developer | true | false | 'process nuget search request' | :success - 'PUBLIC' | :guest | true | false | 'process nuget search request' | :success - 'PUBLIC' | :developer | false | true | 'process nuget search request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget search request' | :success - 'PUBLIC' | :developer | false | false | 'process nuget search request' | :success - 'PUBLIC' | :guest | false | false | 'process nuget search request' | :success - 'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success - 'PRIVATE' | :developer | true | true | 'process nuget search request' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - - subject { get api(url), headers: headers } - - before do - project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end - end - - it_behaves_like 'deploy token for package GET requests' - - it_behaves_like 'rejects nuget access with unknown project id' - - it_behaves_like 'rejects nuget access with invalid project id' - end - end -end diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb new file mode 100644 index 00000000000..df1daf39144 --- /dev/null +++ b/spec/requests/api/nuget_project_packages_spec.rb @@ -0,0 +1,280 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe API::NugetProjectPackages do + include WorkhorseHelpers + include PackagesManagerApiSpecHelpers + include HttpBasicAuthHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project, reload: true) { create(:project, :public) } + let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } + let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } + let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } + + describe 'GET /api/v4/projects/:id/packages/nuget' do + it_behaves_like 'handling nuget service requests' do + let(:url) { "/projects/#{project.id}/packages/nuget/index.json" } + end + end + + describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do + it_behaves_like 'handling nuget metadata requests with package name' do + let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" } + end + end + + describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do + it_behaves_like 'handling nuget metadata requests with package name and package version' do + let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" } + end + end + + describe 'GET /api/v4/projects/:id/packages/nuget/query' do + it_behaves_like 'handling nuget search requests' do + let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" } + end + end + + describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do + let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } + let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } } + let(:url) { "/projects/#{project.id}/packages/nuget/authorize" } + let(:headers) { {} } + + subject { put api(url), headers: headers } + + context 'without the need for a license' do + context 'with valid project' do + using RSpec::Parameterized::TableSyntax + + where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success + 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success + 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + let(:headers) { user_headers.merge(workhorse_header) } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + it_behaves_like 'deploy token for package uploads' + + it_behaves_like 'rejects nuget access with unknown project id' + + it_behaves_like 'rejects nuget access with invalid project id' + end + end + + describe 'PUT /api/v4/projects/:id/packages/nuget' do + let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } + let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } } + let_it_be(:file_name) { 'package.nupkg' } + let(:url) { "/projects/#{project.id}/packages/nuget" } + let(:headers) { {} } + let(:params) { { package: temp_file(file_name) } } + let(:file_key) { :package } + let(:send_rewritten_field) { true } + + subject do + workhorse_finalize( + api(url), + method: :put, + file_key: file_key, + params: params, + headers: headers, + send_rewritten_field: send_rewritten_field + ) + end + + context 'without the need for a license' do + context 'with valid project' do + using RSpec::Parameterized::TableSyntax + + where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget upload' | :created + 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | true | true | 'process nuget upload' | :created + 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + let(:headers) { user_headers.merge(workhorse_header) } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + it_behaves_like 'deploy token for package uploads' + + it_behaves_like 'rejects nuget access with unknown project id' + + it_behaves_like 'rejects nuget access with invalid project id' + + context 'file size above maximum limit' do + let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) } + + before do + allow_next_instance_of(UploadedFile) do |uploaded_file| + allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1) + end + end + + it_behaves_like 'returning response status', :bad_request + end + end + end + + describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do + let_it_be(:package_name) { 'Dummy.Package' } + let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) } + let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" } + + subject { get api(url) } + + context 'without the need for a license' do + context 'with valid project' do + using RSpec::Parameterized::TableSyntax + + where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success + 'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success + 'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success + 'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success + 'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success + 'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success + 'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success + 'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success + 'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success + 'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success + 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + subject { get api(url), headers: headers } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + it_behaves_like 'deploy token for package GET requests' + + it_behaves_like 'rejects nuget access with unknown project id' + + it_behaves_like 'rejects nuget access with invalid project id' + end + end + + describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do + let_it_be(:package_name) { 'Dummy.Package' } + let_it_be(:package) { create(:nuget_package, project: project, name: package_name) } + + let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" } + + subject { get api(url) } + + context 'without the need for a license' do + context 'with valid project' do + using RSpec::Parameterized::TableSyntax + + where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success + 'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success + 'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success + 'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success + 'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success + 'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success + 'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success + 'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success + 'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success + 'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success + 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + subject { get api(url), headers: headers } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + it_behaves_like 'deploy token for package GET requests' + + it_behaves_like 'rejects nuget access with unknown project id' + + it_behaves_like 'rejects nuget access with invalid project id' + end + end +end diff --git a/spec/requests/whats_new_controller_spec.rb b/spec/requests/whats_new_controller_spec.rb index 8005d38dbb0..30d741ee0f0 100644 --- a/spec/requests/whats_new_controller_spec.rb +++ b/spec/requests/whats_new_controller_spec.rb @@ -4,22 +4,22 @@ require 'spec_helper' RSpec.describe WhatsNewController do describe 'whats_new_path' do - let(:item) { double(:item) } - let(:highlights) { double(:highlight, items: [item], map: [item].map, next_page: 2) } - context 'with whats_new_drawer feature enabled' do before do stub_feature_flags(whats_new_drawer: true) end context 'with no page param' do + let(:most_recent) { { items: [item], next_page: 2 } } + let(:item) { double(:item) } + it 'responds with paginated data and headers' do - allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(highlights) + allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(most_recent) allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item) get whats_new_path, xhr: true - expect(response.body).to eq(highlights.items.to_json) + expect(response.body).to eq(most_recent[:items].to_json) expect(response.headers['X-Next-Page']).to eq(2) end end @@ -37,18 +37,6 @@ RSpec.describe WhatsNewController do expect(response).to have_gitlab_http_status(:not_found) end end - - context 'with version param' do - it 'returns items without pagination headers' do - allow(ReleaseHighlight).to receive(:for_version).with(version: '42').and_return(highlights) - allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item) - - get whats_new_path(version: 42), xhr: true - - expect(response.body).to eq(highlights.items.to_json) - expect(response.headers['X-Next-Page']).to be_nil - end - end end context 'with whats_new_drawer feature disabled' do diff --git a/spec/services/ci/pipelines/create_artifact_service_spec.rb b/spec/services/ci/pipelines/create_artifact_service_spec.rb index 6f177889ed3..4e9248d9d1a 100644 --- a/spec/services/ci/pipelines/create_artifact_service_spec.rb +++ b/spec/services/ci/pipelines/create_artifact_service_spec.rb @@ -7,7 +7,8 @@ RSpec.describe ::Ci::Pipelines::CreateArtifactService do subject { described_class.new.execute(pipeline) } context 'when pipeline has coverage reports' do - let(:pipeline) { create(:ci_pipeline, :with_coverage_reports) } + let(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_pipeline, :with_coverage_reports, project: project) } context 'when pipeline is finished' do it 'creates a pipeline artifact' do diff --git a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb new file mode 100644 index 00000000000..f808d12baf4 --- /dev/null +++ b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb @@ -0,0 +1,265 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'handling nuget service requests' do + subject { get api(url) } + + context 'with valid project' do + using RSpec::Parameterized::TableSyntax + + context 'personal token' do + where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success + 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success + 'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success + 'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success + 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success + 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success + 'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success + 'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success + 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success + 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success + 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + subject { get api(url), headers: headers } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + context 'with job token' do + where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success + 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success + 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success + 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success + 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success + 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success + 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + end + + with_them do + let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') } + let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) } + + subject { get api(url), headers: headers } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + end + + it_behaves_like 'deploy token for package GET requests' + + it_behaves_like 'rejects nuget access with unknown project id' + + it_behaves_like 'rejects nuget access with invalid project id' +end + +RSpec.shared_examples 'handling nuget metadata requests with package name' do + include_context 'with expected presenters dependency groups' + + let_it_be(:package_name) { 'Dummy.Package' } + let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) } + let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } } + + subject { get api(url) } + + before do + packages.each { |pkg| create_dependencies_for(pkg) } + end + + context 'with valid project' do + using RSpec::Parameterized::TableSyntax + + where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success + 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success + 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success + 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + subject { get api(url), headers: headers } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + + it_behaves_like 'deploy token for package GET requests' + + it_behaves_like 'rejects nuget access with unknown project id' + + it_behaves_like 'rejects nuget access with invalid project id' + end +end + +RSpec.shared_examples 'handling nuget metadata requests with package name and package version' do + include_context 'with expected presenters dependency groups' + + let_it_be(:package_name) { 'Dummy.Package' } + let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) } + let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') } + + subject { get api(url) } + + before do + create_dependencies_for(package) + end + + context 'with valid project' do + using RSpec::Parameterized::TableSyntax + + where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success + 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success + 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success + 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + subject { get api(url), headers: headers } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + it_behaves_like 'deploy token for package GET requests' + + context 'with invalid package name' do + let_it_be(:package_name) { 'Unkown' } + + it_behaves_like 'rejects nuget packages access', :developer, :not_found + end +end + +RSpec.shared_examples 'handling nuget search requests' do + let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) } + let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') } + let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) } + let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) } + let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) } + let_it_be(:package_e) { create(:nuget_package, name: 'Foo.BarE', project: project) } + let(:search_term) { 'uMmy' } + let(:take) { 26 } + let(:skip) { 0 } + let(:include_prereleases) { true } + let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } } + + subject { get api(url) } + + context 'with valid project' do + using RSpec::Parameterized::TableSyntax + + where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | 'process nuget search request' | :success + 'PUBLIC' | :guest | true | true | 'process nuget search request' | :success + 'PUBLIC' | :developer | true | false | 'process nuget search request' | :success + 'PUBLIC' | :guest | true | false | 'process nuget search request' | :success + 'PUBLIC' | :developer | false | true | 'process nuget search request' | :success + 'PUBLIC' | :guest | false | true | 'process nuget search request' | :success + 'PUBLIC' | :developer | false | false | 'process nuget search request' | :success + 'PUBLIC' | :guest | false | false | 'process nuget search request' | :success + 'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success + 'PRIVATE' | :developer | true | true | 'process nuget search request' | :success + 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + subject { get api(url), headers: headers } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + it_behaves_like 'deploy token for package GET requests' + + it_behaves_like 'rejects nuget access with unknown project id' + + it_behaves_like 'rejects nuget access with invalid project id' +end diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb index 58e99776fd9..d78e203c741 100644 --- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb @@ -26,7 +26,7 @@ RSpec.shared_examples 'process nuget service index request' do |user_type, statu it_behaves_like 'returning response status', status - it_behaves_like 'a package tracking event', described_class.name, 'cli_metadata' + it_behaves_like 'a package tracking event', 'API::NugetPackages', 'cli_metadata' it 'returns a valid json response' do subject @@ -169,7 +169,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = context 'with correct params' do it_behaves_like 'package workhorse uploads' it_behaves_like 'creates nuget package files' - it_behaves_like 'a package tracking event', described_class.name, 'push_package' + it_behaves_like 'a package tracking event', 'API::NugetPackages', 'push_package' end end @@ -286,7 +286,7 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st it_behaves_like 'returning response status', status - it_behaves_like 'a package tracking event', described_class.name, 'pull_package' + it_behaves_like 'a package tracking event', 'API::NugetPackages', 'pull_package' it 'returns a valid package archive' do subject @@ -336,7 +336,7 @@ RSpec.shared_examples 'process nuget search request' do |user_type, status, add_ it_behaves_like 'returns a valid json search response', status, 4, [1, 5, 5, 1] - it_behaves_like 'a package tracking event', described_class.name, 'search_package' + it_behaves_like 'a package tracking event', 'API::NugetPackages', 'search_package' context 'with skip set to 2' do let(:skip) { 2 } |