diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-13 15:08:52 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-13 15:08:52 +0000 |
commit | 0ab47b994caa80c5587f33dc818626b66cfdafe2 (patch) | |
tree | 5ef3976d2f84e3368903a67ba2dbd87a74b9a43c /spec | |
parent | 1308dc5eb484ab0f8064989fc551ebdb4b1a7976 (diff) | |
download | gitlab-ce-0ab47b994caa80c5587f33dc818626b66cfdafe2.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
39 files changed, 1428 insertions, 257 deletions
diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb index babc93a83e5..04f73749e1d 100644 --- a/spec/controllers/user_callouts_controller_spec.rb +++ b/spec/controllers/user_callouts_controller_spec.rb @@ -13,7 +13,7 @@ describe UserCalloutsController do subject { post :create, params: { feature_name: feature_name }, format: :json } context 'with valid feature name' do - let(:feature_name) { UserCallout.feature_names.keys.first } + let(:feature_name) { UserCallout.feature_names.each_key.first } context 'when callout entry does not exist' do it 'creates a callout entry with dismissed state' do @@ -28,7 +28,7 @@ describe UserCalloutsController do end context 'when callout entry already exists' do - let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) } + let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.each_key.first, user: user) } it 'returns success' do subject diff --git a/spec/factories/snippet_repositories.rb b/spec/factories/snippet_repositories.rb new file mode 100644 index 00000000000..1f9e68514bb --- /dev/null +++ b/spec/factories/snippet_repositories.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :snippet_repository do + snippet + + after(:build) do |snippet_repository, _| + snippet_repository.shard_name = snippet_repository.snippet.repository_storage + snippet_repository.disk_path = snippet_repository.snippet.disk_path + end + end +end diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb index 5990ed7ffb0..6fcb0319748 100644 --- a/spec/factories/snippets.rb +++ b/spec/factories/snippets.rb @@ -20,6 +20,21 @@ FactoryBot.define do trait :private do visibility_level { Snippet::PRIVATE } end + + # Test repository - https://gitlab.com/gitlab-org/gitlab-test + trait :repository do + after :create do |snippet| + TestEnv.copy_repo(snippet, + bare_repo: TestEnv.factory_repo_path_bare, + refs: TestEnv::BRANCH_SHA) + end + end + + trait :empty_repo do + after(:create) do |snippet| + raise "Failed to create repository!" unless snippet.repository.create_if_not_exists + end + end end factory :project_snippet, parent: :snippet, class: :ProjectSnippet do diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 94af023e804..5a425fb5d27 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -8,9 +8,17 @@ describe 'Projects > Snippets > Create Snippet', :js do let(:user) { create(:user) } let(:project) { create(:project, :public) } + def description_field + find('.js-description-input input,textarea') + end + def fill_form fill_in 'project_snippet_title', with: 'My Snippet Title' + + # Click placeholder first to expand full description field + description_field.click fill_in 'project_snippet_description', with: 'My Snippet **Description**' + page.within('.file-editor') do find('.ace_text-input', visible: false).send_keys('Hello World!') end @@ -27,6 +35,18 @@ describe 'Projects > Snippets > Create Snippet', :js do click_on('New snippet') end + it 'shows collapsible description input' do + collapsed = description_field + + expect(page).not_to have_field('project_snippet_description') + expect(collapsed).to be_visible + + collapsed.click + + expect(page).to have_field('project_snippet_description') + expect(collapsed).not_to be_visible + end + it 'creates a new snippet' do fill_form click_button('Create snippet') diff --git a/spec/features/snippets/spam_snippets_spec.rb b/spec/features/snippets/spam_snippets_spec.rb index 0c3ca6f17c8..dac36ba2b28 100644 --- a/spec/features/snippets/spam_snippets_spec.rb +++ b/spec/features/snippets/spam_snippets_spec.rb @@ -5,6 +5,10 @@ require 'spec_helper' describe 'User creates snippet', :js do let(:user) { create(:user) } + def description_field + find('.js-description-input input,textarea') + end + before do stub_feature_flags(allow_possible_spam: false) stub_feature_flags(snippets_vue: false) @@ -22,7 +26,11 @@ describe 'User creates snippet', :js do visit new_snippet_path fill_in 'personal_snippet_title', with: 'My Snippet Title' + + # Click placeholder first to expand full description field + description_field.click fill_in 'personal_snippet_description', with: 'My Snippet **Description**' + find('#personal_snippet_visibility_level_20').set(true) page.within('.file-editor') do find('.ace_text-input', visible: false).send_keys 'Hello World!' diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index b373264bbe4..eb55613b954 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -13,9 +13,17 @@ describe 'User creates snippet', :js do visit new_snippet_path end + def description_field + find('.js-description-input input,textarea') + end + def fill_form fill_in 'personal_snippet_title', with: 'My Snippet Title' + + # Click placeholder first to expand full description field + description_field.click fill_in 'personal_snippet_description', with: 'My Snippet **Description**' + page.within('.file-editor') do find('.ace_text-input', visible: false).send_keys 'Hello World!' end @@ -36,6 +44,8 @@ describe 'User creates snippet', :js do end it 'previews a snippet with file' do + # Click placeholder first to expand full description field + description_field.click fill_in 'personal_snippet_description', with: 'My Snippet' dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') find('.js-md-preview-button').click diff --git a/spec/frontend/diffs/components/diff_line_gutter_content_spec.js b/spec/frontend/diffs/components/diff_table_cell_spec.js index 0553498bfa0..1af0746f3bd 100644 --- a/spec/frontend/diffs/components/diff_line_gutter_content_spec.js +++ b/spec/frontend/diffs/components/diff_table_cell_spec.js @@ -1,6 +1,6 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import DiffLineGutterContent from '~/diffs/components/diff_line_gutter_content.vue'; +import DiffTableCell from '~/diffs/components/diff_table_cell.vue'; import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue'; import { LINE_POSITION_RIGHT } from '~/diffs/constants'; import { createStore } from '~/mr_notes/stores'; @@ -17,7 +17,7 @@ const TEST_LINE_NUMBER = 1; const TEST_LINE_CODE = 'LC_42'; const TEST_FILE_HASH = diffFileMockData.file_hash; -describe('DiffLineGutterContent', () => { +describe('DiffTableCell', () => { let wrapper; let line; let store; @@ -49,22 +49,40 @@ describe('DiffLineGutterContent', () => { value, }); }; + const createComponent = (props = {}) => { - wrapper = shallowMount(DiffLineGutterContent, { + wrapper = shallowMount(DiffTableCell, { localVue, store, propsData: { line, fileHash: TEST_FILE_HASH, contextLinesPath: '/context/lines/path', + isHighlighted: false, ...props, }, }); }; - const findNoteButton = () => wrapper.find('.js-add-diff-note-button'); + + const findTd = () => wrapper.find({ ref: 'td' }); + const findNoteButton = () => wrapper.find({ ref: 'addDiffNoteButton' }); const findLineNumber = () => wrapper.find({ ref: 'lineNumberRef' }); const findAvatars = () => wrapper.find(DiffGutterAvatars); + describe('td', () => { + it('highlights when isHighlighted true', () => { + createComponent({ isHighlighted: true }); + + expect(findTd().classes()).toContain('hll'); + }); + + it('does not highlight when isHighlighted false', () => { + createComponent({ isHighlighted: false }); + + expect(findTd().classes()).not.toContain('hll'); + }); + }); + describe('comment button', () => { it.each` showCommentButton | userData | query | expectation @@ -84,13 +102,13 @@ describe('DiffLineGutterContent', () => { ); it.each` - isHover | otherProps | discussions | expectation - ${true} | ${{}} | ${[]} | ${true} - ${false} | ${{}} | ${[]} | ${false} - ${true} | ${{ isMatchLine: true }} | ${[]} | ${false} - ${true} | ${{ isContextLine: true }} | ${[]} | ${false} - ${true} | ${{ isMetaLine: true }} | ${[]} | ${false} - ${true} | ${{}} | ${[{}]} | ${false} + isHover | otherProps | discussions | expectation + ${true} | ${{}} | ${[]} | ${true} + ${false} | ${{}} | ${[]} | ${false} + ${true} | ${{ line: { ...line, type: 'match' } }} | ${[]} | ${false} + ${true} | ${{ line: { ...line, type: 'context' } }} | ${[]} | ${false} + ${true} | ${{ line: { ...line, type: 'old-nonewline' } }} | ${[]} | ${false} + ${true} | ${{}} | ${[{}]} | ${false} `( 'visible is $expectation - with isHover ($isHover), discussions ($discussions), otherProps ($otherProps)', ({ isHover, otherProps, discussions, expectation }) => { @@ -109,7 +127,7 @@ describe('DiffLineGutterContent', () => { describe('line number', () => { describe('without lineNumber prop', () => { it('does not render', () => { - createComponent(); + createComponent({ lineType: 'old' }); expect(findLineNumber().exists()).toBe(false); }); diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index 0a7e3dca183..4871619c85a 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -74,6 +74,8 @@ describe('Time series component', () => { describe('general functions', () => { let timeSeriesChart; + const findChart = () => timeSeriesChart.find({ ref: 'chart' }); + beforeEach(done => { timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); timeSeriesChart.vm.$nextTick(done); @@ -109,8 +111,6 @@ describe('Time series component', () => { let startValue; let endValue; - const findChart = () => timeSeriesChart.find({ ref: 'chart' }); - beforeEach(done => { eChartMock = { handlers: {}, @@ -285,6 +285,8 @@ describe('Time series component', () => { }); describe('computed', () => { + const getChartOptions = () => findChart().props('option'); + describe('chartData', () => { let chartData; const seriesData = () => chartData[0]; @@ -329,7 +331,7 @@ describe('Time series component', () => { }); return timeSeriesChart.vm.$nextTick().then(() => { - expect(timeSeriesChart.vm.chartOptions).toEqual(expect.objectContaining(mockOption)); + expect(getChartOptions()).toEqual(expect.objectContaining(mockOption)); }); }); @@ -345,7 +347,7 @@ describe('Time series component', () => { }); return timeSeriesChart.vm.$nextTick().then(() => { - const optionSeries = timeSeriesChart.vm.chartOptions.series; + const optionSeries = getChartOptions().series; expect(optionSeries.length).toEqual(2); expect(optionSeries[0].name).toEqual(mockSeriesName); @@ -354,33 +356,58 @@ describe('Time series component', () => { }); describe('yAxis formatter', () => { - let format; + let dataFormatter; + let deploymentFormatter; beforeEach(() => { - format = timeSeriesChart.vm.chartOptions.yAxis.axisLabel.formatter; + dataFormatter = getChartOptions().yAxis[0].axisLabel.formatter; + deploymentFormatter = getChartOptions().yAxis[1].axisLabel.formatter; }); it('rounds to 3 decimal places', () => { - expect(format(0.88888)).toBe('0.889'); + expect(dataFormatter(0.88888)).toBe('0.889'); + }); + + it('deployment formatter is set as is required to display a tooltip', () => { + expect(deploymentFormatter).toEqual(expect.any(Function)); }); }); }); - describe('scatterSeries', () => { + describe('deploymentSeries', () => { it('utilizes deployment data', () => { - expect(timeSeriesChart.vm.scatterSeries.data).toEqual([ - ['2019-07-16T10:14:25.589Z', 0], - ['2019-07-16T11:14:25.589Z', 0], - ['2019-07-16T12:14:25.589Z', 0], + expect(timeSeriesChart.vm.deploymentSeries.yAxisIndex).toBe(1); // same as deployment y axis + expect(timeSeriesChart.vm.deploymentSeries.data).toEqual([ + ['2019-07-16T10:14:25.589Z', expect.any(Number)], + ['2019-07-16T11:14:25.589Z', expect.any(Number)], + ['2019-07-16T12:14:25.589Z', expect.any(Number)], ]); - expect(timeSeriesChart.vm.scatterSeries.symbolSize).toBe(14); + expect(timeSeriesChart.vm.deploymentSeries.symbolSize).toBe(14); }); }); describe('yAxisLabel', () => { + it('y axis is configured correctly', () => { + const { yAxis } = getChartOptions(); + + expect(yAxis).toHaveLength(2); + + const [dataAxis, deploymentAxis] = yAxis; + + expect(dataAxis.boundaryGap).toHaveLength(2); + expect(dataAxis.scale).toBe(true); + + expect(deploymentAxis.show).toBe(false); + expect(deploymentAxis.min).toEqual(expect.any(Number)); + expect(deploymentAxis.max).toEqual(expect.any(Number)); + expect(deploymentAxis.min).toBeLessThan(deploymentAxis.max); + }); + it('constructs a label for the chart y-axis', () => { - expect(timeSeriesChart.vm.yAxisLabel).toBe('Memory Used per Pod'); + const { yAxis } = getChartOptions(); + + expect(yAxis[0].name).toBe('Memory Used per Pod'); }); }); }); @@ -405,7 +432,7 @@ describe('Time series component', () => { glChartComponents.forEach(dynamicComponent => { describe(`GitLab UI: ${dynamicComponent.chartType}`, () => { let timeSeriesAreaChart; - const findChart = () => timeSeriesAreaChart.find(dynamicComponent.component); + const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component); beforeEach(done => { timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType); @@ -417,12 +444,12 @@ describe('Time series component', () => { }); it('is a Vue instance', () => { - expect(findChart().exists()).toBe(true); - expect(findChart().isVueInstance()).toBe(true); + expect(findChartComponent().exists()).toBe(true); + expect(findChartComponent().isVueInstance()).toBe(true); }); it('receives data properties needed for proper chart render', () => { - const props = findChart().props(); + const props = findChartComponent().props(); expect(props.data).toBe(timeSeriesAreaChart.vm.chartData); expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions); @@ -435,9 +462,9 @@ describe('Time series component', () => { timeSeriesAreaChart.vm.tooltip.title = mockTitle; timeSeriesAreaChart.vm.$nextTick(() => { - expect(shallowWrapperContainsSlotText(findChart(), 'tooltipTitle', mockTitle)).toBe( - true, - ); + expect( + shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', mockTitle), + ).toBe(true); done(); }); }); @@ -452,9 +479,9 @@ describe('Time series component', () => { }); it('uses deployment title', () => { - expect(shallowWrapperContainsSlotText(findChart(), 'tooltipTitle', 'Deployed')).toBe( - true, - ); + expect( + shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', 'Deployed'), + ).toBe(true); }); it('renders clickable commit sha in tooltip content', done => { diff --git a/spec/frontend/snippet/collapsible_input_spec.js b/spec/frontend/snippet/collapsible_input_spec.js new file mode 100644 index 00000000000..acd15164c95 --- /dev/null +++ b/spec/frontend/snippet/collapsible_input_spec.js @@ -0,0 +1,104 @@ +import setupCollapsibleInputs from '~/snippet/collapsible_input'; +import { setHTMLFixture } from 'helpers/fixtures'; + +describe('~/snippet/collapsible_input', () => { + let formEl; + let descriptionEl; + let titleEl; + let fooEl; + + beforeEach(() => { + setHTMLFixture(` + <form> + <div class="js-collapsible-input js-title"> + <div class="js-collapsed d-none"> + <input type="text" /> + </div> + <div class="js-expanded"> + <textarea>Hello World!</textarea> + </div> + </div> + <div class="js-collapsible-input js-description"> + <div class="js-collapsed"> + <input type="text" /> + </div> + <div class="js-expanded d-none"> + <textarea></textarea> + </div> + </div> + <input type="text" class="js-foo" /> + </form> + `); + + formEl = document.querySelector('form'); + titleEl = formEl.querySelector('.js-title'); + descriptionEl = formEl.querySelector('.js-description'); + fooEl = formEl.querySelector('.js-foo'); + + setupCollapsibleInputs(); + }); + + const findInput = el => el.querySelector('textarea,input'); + const findCollapsed = el => el.querySelector('.js-collapsed'); + const findExpanded = el => el.querySelector('.js-expanded'); + const findCollapsedInput = el => findInput(findCollapsed(el)); + const findExpandedInput = el => findInput(findExpanded(el)); + const focusIn = target => target.dispatchEvent(new Event('focusin', { bubbles: true })); + const expectIsCollapsed = (el, isCollapsed) => { + expect(findCollapsed(el).classList.contains('d-none')).toEqual(!isCollapsed); + expect(findExpanded(el).classList.contains('d-none')).toEqual(isCollapsed); + }; + + describe('when collapsed', () => { + it('is collapsed', () => { + expectIsCollapsed(descriptionEl, true); + }); + + describe('when focused', () => { + beforeEach(() => { + focusIn(findCollapsedInput(descriptionEl)); + }); + + it('is expanded', () => { + expectIsCollapsed(descriptionEl, false); + }); + + describe.each` + desc | value | isCollapsed + ${'is collapsed'} | ${''} | ${true} + ${'stays open if given value'} | ${'Hello world!'} | ${false} + `('when loses focus', ({ desc, value, isCollapsed }) => { + it(desc, () => { + findExpandedInput(descriptionEl).value = value; + focusIn(fooEl); + + expectIsCollapsed(descriptionEl, isCollapsed); + }); + }); + }); + }); + + describe('when expanded and has value', () => { + it('does not collapse, when focusing out', () => { + expectIsCollapsed(titleEl, false); + + focusIn(fooEl); + + expectIsCollapsed(titleEl, false); + }); + + describe('and loses value', () => { + beforeEach(() => { + findExpandedInput(titleEl).value = ''; + }); + + it('collapses, when focusing out', () => { + expectIsCollapsed(titleEl, false); + + focusIn(fooEl); + + expectIsCollapsed(titleEl, true); + }); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/diff_table_cell_spec.js b/spec/javascripts/diffs/components/diff_table_cell_spec.js deleted file mode 100644 index f91e3b56805..00000000000 --- a/spec/javascripts/diffs/components/diff_table_cell_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { createStore } from '~/mr_notes/stores'; -import DiffTableCell from '~/diffs/components/diff_table_cell.vue'; -import diffFileMockData from '../mock_data/diff_file'; - -describe('DiffTableCell', () => { - const createComponent = options => - createComponentWithStore(Vue.extend(DiffTableCell), createStore(), { - line: diffFileMockData.highlighted_diff_lines[0], - fileHash: diffFileMockData.file_hash, - contextLinesPath: 'contextLinesPath', - ...options, - }).$mount(); - - it('does not highlight row when isHighlighted prop is false', done => { - const vm = createComponent({ isHighlighted: false }); - - vm.$nextTick() - .then(() => { - expect(vm.$el.classList).not.toContain('hll'); - }) - .then(done) - .catch(done.fail); - }); - - it('highlights row when isHighlighted prop is true', done => { - const vm = createComponent({ isHighlighted: true }); - - vm.$nextTick() - .then(() => { - expect(vm.$el.classList).toContain('hll'); - }) - .then(done) - .catch(done.fail); - }); -}); diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb new file mode 100644 index 00000000000..ffb3d86408a --- /dev/null +++ b/spec/lib/gitlab/git_access_snippet_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::GitAccessSnippet do + include GitHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:personal_snippet) { create(:personal_snippet, :private, :repository) } + + let(:protocol) { 'ssh' } + let(:changes) { Gitlab::GitAccess::ANY } + let(:push_access_check) { access.check('git-receive-pack', changes) } + let(:pull_access_check) { access.check('git-upload-pack', changes) } + let(:snippet) { personal_snippet } + let(:actor) { personal_snippet.author } + + describe 'when feature flag :version_snippets is enabled' do + it 'allows push and pull access' do + aggregate_failures do + expect { pull_access_check }.not_to raise_error + expect { push_access_check }.not_to raise_error + end + end + end + + describe 'when feature flag :version_snippets is disabled' do + before do + stub_feature_flags(version_snippets: false) + end + + it 'does not allow push and pull access' do + aggregate_failures do + expect { push_access_check }.to raise_snippet_not_found + expect { pull_access_check }.to raise_snippet_not_found + end + end + end + + describe '#check_snippet_accessibility!' do + context 'when the snippet exists' do + it 'allows push and pull access' do + aggregate_failures do + expect { pull_access_check }.not_to raise_error + expect { push_access_check }.not_to raise_error + end + end + end + + context 'when the snippet is nil' do + let(:snippet) { nil } + + it 'blocks push and pull with "not found"' do + aggregate_failures do + expect { pull_access_check }.to raise_snippet_not_found + expect { push_access_check }.to raise_snippet_not_found + end + end + end + + context 'when the snippet does not have a repository' do + let(:snippet) { build_stubbed(:personal_snippet) } + + it 'blocks push and pull with "not found"' do + aggregate_failures do + expect { pull_access_check }.to raise_snippet_not_found + expect { push_access_check }.to raise_snippet_not_found + end + end + end + end + + private + + def access + described_class.new(actor, snippet, protocol, + authentication_abilities: [], + namespace_path: nil, project_path: nil, + redirected_path: nil, auth_result_type: nil) + end + + def raise_snippet_not_found + raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:snippet_not_found]) + end +end diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb index 8bbcc644f6d..7cf0442fbe1 100644 --- a/spec/lib/gitlab/gl_repository/repo_type_spec.rb +++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' describe Gitlab::GlRepository::RepoType do let_it_be(:project) { create(:project) } + let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) } + let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) } describe Gitlab::GlRepository::PROJECT do it_behaves_like 'a repo type' do @@ -16,6 +18,7 @@ describe Gitlab::GlRepository::RepoType do it 'knows its type' do expect(described_class).not_to be_wiki expect(described_class).to be_project + expect(described_class).not_to be_snippet end it 'checks if repository path is valid' do @@ -36,6 +39,7 @@ describe Gitlab::GlRepository::RepoType do it 'knows its type' do expect(described_class).to be_wiki expect(described_class).not_to be_project + expect(described_class).not_to be_snippet end it 'checks if repository path is valid' do @@ -43,4 +47,38 @@ describe Gitlab::GlRepository::RepoType do expect(described_class.valid?(project.wiki.repository.full_path)).to be_truthy end end + + describe Gitlab::GlRepository::SNIPPET do + context 'when PersonalSnippet' do + it_behaves_like 'a repo type' do + let(:expected_id) { personal_snippet.id.to_s } + let(:expected_identifier) { "snippet-#{expected_id}" } + let(:expected_suffix) { '' } + let(:expected_repository) { personal_snippet.repository } + let(:expected_container) { personal_snippet } + end + + it 'knows its type' do + expect(described_class).to be_snippet + expect(described_class).not_to be_wiki + expect(described_class).not_to be_project + end + end + + context 'when ProjectSnippet' do + it_behaves_like 'a repo type' do + let(:expected_id) { project_snippet.id.to_s } + let(:expected_identifier) { "snippet-#{expected_id}" } + let(:expected_suffix) { '' } + let(:expected_repository) { project_snippet.repository } + let(:expected_container) { project_snippet } + end + + it 'knows its type' do + expect(described_class).to be_snippet + expect(described_class).not_to be_wiki + expect(described_class).not_to be_project + end + end + end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 4c521ae7f07..e6a60f39bd4 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -91,6 +91,7 @@ snippets: - award_emoji - user_agent_detail - user_mentions +- snippet_repository releases: - author - project diff --git a/spec/lib/gitlab/import_export/hash_util_spec.rb b/spec/lib/gitlab/import_export/hash_util_spec.rb index ddd874ddecf..b97c6665d0e 100644 --- a/spec/lib/gitlab/import_export/hash_util_spec.rb +++ b/spec/lib/gitlab/import_export/hash_util_spec.rb @@ -9,7 +9,7 @@ describe Gitlab::ImportExport::HashUtil do describe '.deep_symbolize_array!' do it 'symbolizes keys' do expect { described_class.deep_symbolize_array!(stringified_array) }.to change { - stringified_array.first.keys.first + stringified_array.first.each_key.first }.from('test').to(:test) end end @@ -17,13 +17,13 @@ describe Gitlab::ImportExport::HashUtil do describe '.deep_symbolize_array_with_date!' do it 'symbolizes keys' do expect { described_class.deep_symbolize_array_with_date!(stringified_array_with_date) }.to change { - stringified_array_with_date.first.keys.first + stringified_array_with_date.first.each_key.first }.from('test_date').to(:test_date) end it 'transforms date strings into Time objects' do expect { described_class.deep_symbolize_array_with_date!(stringified_array_with_date) }.to change { - stringified_array_with_date.first.values.first.class + stringified_array_with_date.first.each_value.first.class }.from(String).to(ActiveSupport::TimeWithZone) end end diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb index 697bedf7362..2d3b61e61ce 100644 --- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb @@ -44,6 +44,12 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi it_behaves_like 'valid dashboard service response' end + context 'when the self monitoring dashboard is specified' do + let(:dashboard_path) { self_monitoring_dashboard_path } + + it_behaves_like 'valid dashboard service response' + end + context 'when no dashboard is specified' do let(:service_call) { described_class.find(project, user, environment: environment) } @@ -152,5 +158,33 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi expect(all_dashboard_paths).to contain_exactly(system_dashboard, project_dashboard) end end + + context 'when the project is self monitoring' do + let(:self_monitoring_dashboard) do + { + path: self_monitoring_dashboard_path, + display_name: 'Default', + default: true, + system_dashboard: false + } + end + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:project) { project_with_dashboard(dashboard_path) } + + before do + stub_application_setting(self_monitoring_project_id: project.id) + end + + it 'includes self monitoring and project dashboards' do + project_dashboard = { + path: dashboard_path, + display_name: 'test.yml', + default: false, + system_dashboard: false + } + + expect(all_dashboard_paths).to contain_exactly(self_monitoring_dashboard, project_dashboard) + end + end end end diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb index e0c8133994b..c0d71bfe5d0 100644 --- a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb @@ -30,6 +30,12 @@ describe Gitlab::Metrics::Dashboard::ServiceSelector do end end + context 'when the path is for the self monitoring dashboard' do + let(:arguments) { { dashboard_path: self_monitoring_dashboard_path } } + + it { is_expected.to be Metrics::Dashboard::SelfMonitoringDashboardService } + end + context 'when the embedded flag is provided' do let(:arguments) { { embedded: true } } diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index eefc548a4d9..7b8d1b6cd9b 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -397,7 +397,7 @@ describe Gitlab::Shell do describe 'namespace actions' do subject { described_class.new } - let(:storage) { Gitlab.config.repositories.storages.keys.first } + let(:storage) { Gitlab.config.repositories.storages.each_key.first } describe '#add_namespace' do it 'creates a namespace' do diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index 7099d000d4c..a0193b29bb3 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -6,6 +6,8 @@ describe Blob do include FakeBlobHelpers let(:project) { build(:project, lfs_enabled: true) } + let(:personal_snippet) { build(:personal_snippet) } + let(:project_snippet) { build(:project_snippet, project: project) } before do allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) @@ -18,77 +20,146 @@ describe Blob do end describe '.lazy' do - let(:project) { create(:project, :repository) } - let(:same_project) { Project.find(project.id) } - let(:other_project) { create(:project, :repository) } let(:commit_id) { 'e63f41fe459e62e1228fcef60d7189127aeba95a' } let(:blob_size_limit) { 10 * 1024 * 1024 } - it 'does not fetch blobs when none are accessed' do - expect(project.repository).not_to receive(:blobs_at) + shared_examples '.lazy checks' do + it 'does not fetch blobs when none are accessed' do + expect(container.repository).not_to receive(:blobs_at) - described_class.lazy(project, commit_id, 'CHANGELOG') - end + described_class.lazy(container, commit_id, 'CHANGELOG') + end + + it 'fetches all blobs for the same repository when one is accessed' do + expect(container.repository).to receive(:blobs_at) + .with([[commit_id, 'CHANGELOG'], [commit_id, 'CONTRIBUTING.md']], blob_size_limit: blob_size_limit) + .once.and_call_original + expect(other_container.repository).not_to receive(:blobs_at) + + changelog = described_class.lazy(container, commit_id, 'CHANGELOG') + contributing = described_class.lazy(same_container, commit_id, 'CONTRIBUTING.md') + + described_class.lazy(other_container, commit_id, 'CHANGELOG') + + # Access property so the values are loaded + changelog.id + contributing.id + end + + it 'does not include blobs from previous requests in later requests' do + changelog = described_class.lazy(container, commit_id, 'CHANGELOG') + contributing = described_class.lazy(same_container, commit_id, 'CONTRIBUTING.md') - it 'fetches all blobs for the same repository when one is accessed' do - expect(project.repository).to receive(:blobs_at) - .with([[commit_id, 'CHANGELOG'], [commit_id, 'CONTRIBUTING.md']], blob_size_limit: blob_size_limit) - .once.and_call_original - expect(other_project.repository).not_to receive(:blobs_at) + # Access property so the values are loaded + changelog.id + contributing.id - changelog = described_class.lazy(project, commit_id, 'CHANGELOG') - contributing = described_class.lazy(same_project, commit_id, 'CONTRIBUTING.md') + readme = described_class.lazy(container, commit_id, 'README.md') - described_class.lazy(other_project, commit_id, 'CHANGELOG') + expect(container.repository).to receive(:blobs_at) + .with([[commit_id, 'README.md']], blob_size_limit: blob_size_limit).once.and_call_original - # Access property so the values are loaded - changelog.id - contributing.id + readme.id + end end - it 'does not include blobs from previous requests in later requests' do - changelog = described_class.lazy(project, commit_id, 'CHANGELOG') - contributing = described_class.lazy(same_project, commit_id, 'CONTRIBUTING.md') + context 'with project' do + let(:container) { create(:project, :repository) } + let(:same_container) { Project.find(container.id) } + let(:other_container) { create(:project, :repository) } - # Access property so the values are loaded - changelog.id - contributing.id + it_behaves_like '.lazy checks' + end + + context 'with personal snippet' do + let(:container) { create(:personal_snippet, :repository) } + let(:same_container) { PersonalSnippet.find(container.id) } + let(:other_container) { create(:personal_snippet, :repository) } - readme = described_class.lazy(project, commit_id, 'README.md') + it_behaves_like '.lazy checks' + end - expect(project.repository).to receive(:blobs_at) - .with([[commit_id, 'README.md']], blob_size_limit: blob_size_limit).once.and_call_original + context 'with project snippet' do + let(:container) { create(:project_snippet, :repository) } + let(:same_container) { ProjectSnippet.find(container.id) } + let(:other_container) { create(:project_snippet, :repository) } - readme.id + it_behaves_like '.lazy checks' end end describe '#data' do - context 'using a binary blob' do - it 'returns the data as-is' do - data = "\n\xFF\xB9\xC3" - blob = fake_blob(binary: true, data: data) + shared_examples '#data checks' do + context 'using a binary blob' do + it 'returns the data as-is' do + data = "\n\xFF\xB9\xC3" + blob = fake_blob(binary: true, data: data, container: container) - expect(blob.data).to eq(data) + expect(blob.data).to eq(data) + end end - end - context 'using a text blob' do - it 'converts the data to UTF-8' do - blob = fake_blob(binary: false, data: "\n\xFF\xB9\xC3") + context 'using a text blob' do + it 'converts the data to UTF-8' do + blob = fake_blob(binary: false, data: "\n\xFF\xB9\xC3", container: container) - expect(blob.data).to eq("\n���") + expect(blob.data).to eq("\n���") + end end end + + context 'with project' do + let(:container) { project } + + it_behaves_like '#data checks' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like '#data checks' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like '#data checks' + end end describe '#external_storage_error?' do + shared_examples 'no error' do + it do + expect(blob.external_storage_error?).to be_falsey + end + end + + shared_examples 'returns error' do + it do + expect(blob.external_storage_error?).to be_truthy + end + end + context 'if the blob is stored in LFS' do - let(:blob) { fake_blob(path: 'file.pdf', lfs: true) } + let(:blob) { fake_blob(path: 'file.pdf', lfs: true, container: container) } context 'when the project has LFS enabled' do - it 'returns false' do - expect(blob.external_storage_error?).to be_falsey + context 'with project' do + let(:container) { project } + + it_behaves_like 'no error' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns error' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'no error' end end @@ -97,17 +168,39 @@ describe Blob do project.lfs_enabled = false end - it 'returns true' do - expect(blob.external_storage_error?).to be_truthy + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns error' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns error' end end end context 'if the blob is not stored in LFS' do - let(:blob) { fake_blob(path: 'file.md') } + let(:blob) { fake_blob(path: 'file.md', container: container) } - it 'returns false' do - expect(blob.external_storage_error?).to be_falsey + context 'with project' do + let(:container) { project } + + it_behaves_like 'no error' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'no error' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'no error' end end end @@ -116,19 +209,59 @@ describe Blob do context 'if the blob is stored in LFS' do let(:blob) { fake_blob(path: 'file.pdf', lfs: true) } - context 'when the project has LFS enabled' do - it 'returns true' do + shared_examples 'returns true' do + it do expect(blob.stored_externally?).to be_truthy end end + shared_examples 'returns false' do + it do + expect(blob.stored_externally?).to be_falsey + end + end + + context 'when the project has LFS enabled' do + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns true' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns true' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns true' + end + end + context 'when the project does not have LFS enabled' do before do project.lfs_enabled = false end - it 'returns false' do - expect(blob.stored_externally?).to be_falsey + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns false' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns false' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns false' end end end @@ -143,21 +276,63 @@ describe Blob do end describe '#binary?' do + shared_examples 'returns true' do + it do + expect(blob.binary?).to be_truthy + end + end + + shared_examples 'returns false' do + it do + expect(blob.binary?).to be_falsey + end + end + context 'if the blob is stored externally' do + let(:blob) { fake_blob(path: file, lfs: true) } + context 'if the extension has a rich viewer' do context 'if the viewer is binary' do - it 'returns true' do - blob = fake_blob(path: 'file.pdf', lfs: true) + let(:file) { 'file.pdf' } - expect(blob.binary?).to be_truthy + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns true' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns true' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns true' end end context 'if the viewer is text-based' do - it 'return false' do - blob = fake_blob(path: 'file.md', lfs: true) + let(:file) { 'file.md' } + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns false' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } - expect(blob.binary?).to be_falsey + it_behaves_like 'returns false' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns false' end end end @@ -165,54 +340,138 @@ describe Blob do context "if the extension doesn't have a rich viewer" do context 'if the extension has a text mime type' do context 'if the extension is for a programming language' do - it 'returns false' do - blob = fake_blob(path: 'file.txt', lfs: true) + let(:file) { 'file.txt' } + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns false' + end - expect(blob.binary?).to be_falsey + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns false' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns false' end end context 'if the extension is not for a programming language' do - it 'returns false' do - blob = fake_blob(path: 'file.ics', lfs: true) + let(:file) { 'file.ics' } + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns false' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns false' + end + + context 'with project snippet' do + let(:container) { project_snippet } - expect(blob.binary?).to be_falsey + it_behaves_like 'returns false' end end end context 'if the extension has a binary mime type' do context 'if the extension is for a programming language' do - it 'returns false' do - blob = fake_blob(path: 'file.rb', lfs: true) + let(:file) { 'file.rb' } + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns false' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns false' + end + + context 'with project snippet' do + let(:container) { project_snippet } - expect(blob.binary?).to be_falsey + it_behaves_like 'returns false' end end context 'if the extension is not for a programming language' do - it 'returns true' do - blob = fake_blob(path: 'file.exe', lfs: true) + let(:file) { 'file.exe' } - expect(blob.binary?).to be_truthy + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns true' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns true' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns true' end end end context 'if the extension has an unknown mime type' do context 'if the extension is for a programming language' do - it 'returns false' do - blob = fake_blob(path: 'file.ini', lfs: true) + let(:file) { 'file.ini' } + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns false' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns false' + end + + context 'with project snippet' do + let(:container) { project_snippet } - expect(blob.binary?).to be_falsey + it_behaves_like 'returns false' end end context 'if the extension is not for a programming language' do - it 'returns true' do - blob = fake_blob(path: 'file.wtf', lfs: true) + let(:file) { 'file.wtf' } + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns true' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } - expect(blob.binary?).to be_truthy + it_behaves_like 'returns true' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns true' end end end @@ -221,18 +480,46 @@ describe Blob do context 'if the blob is not stored externally' do context 'if the blob is binary' do - it 'returns true' do - blob = fake_blob(path: 'file.pdf', binary: true) + let(:blob) { fake_blob(path: 'file.pdf', binary: true, container: container) } - expect(blob.binary?).to be_truthy + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns true' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns true' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns true' end end context 'if the blob is text-based' do - it 'return false' do - blob = fake_blob(path: 'file.md') + let(:blob) { fake_blob(path: 'file.md', container: container) } + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns false' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns false' + end + + context 'with project snippet' do + let(:container) { project_snippet } - expect(blob.binary?).to be_falsey + it_behaves_like 'returns false' end end end @@ -389,38 +676,110 @@ describe Blob do end describe '#rendered_as_text?' do + shared_examples 'returns true' do + it do + expect(blob.rendered_as_text?(ignore_errors: ignore_errors)).to be_truthy + end + end + + shared_examples 'returns false' do + it do + expect(blob.rendered_as_text?(ignore_errors: ignore_errors)).to be_falsey + end + end + context 'when ignoring errors' do + let(:ignore_errors) { true } + context 'when the simple viewer is text-based' do - it 'returns true' do - blob = fake_blob(path: 'file.md', size: 100.megabytes) + let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes, container: container) } - expect(blob.rendered_as_text?).to be_truthy + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns true' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns true' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns true' end end context 'when the simple viewer is binary' do - it 'returns false' do - blob = fake_blob(path: 'file.pdf', binary: true, size: 100.megabytes) + let(:blob) { fake_blob(path: 'file.pdf', binary: true, size: 100.megabytes, container: container) } + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns false' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns false' + end + + context 'with project snippet' do + let(:container) { project_snippet } - expect(blob.rendered_as_text?).to be_falsey + it_behaves_like 'returns false' end end end context 'when not ignoring errors' do + let(:ignore_errors) { false } + context 'when the viewer has render errors' do - it 'returns false' do - blob = fake_blob(path: 'file.md', size: 100.megabytes) + let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes, container: container) } - expect(blob.rendered_as_text?(ignore_errors: false)).to be_falsey + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns false' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns false' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns false' end end context "when the viewer doesn't have render errors" do - it 'returns true' do - blob = fake_blob(path: 'file.md') + let(:blob) { fake_blob(path: 'file.md', container: container) } + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns true' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns true' + end + + context 'with project snippet' do + let(:container) { project_snippet } - expect(blob.rendered_as_text?(ignore_errors: false)).to be_truthy + it_behaves_like 'returns true' end end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index ada25005064..26cc68eb58c 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -3,8 +3,10 @@ require 'spec_helper' describe Commit do - let(:project) { create(:project, :public, :repository) } - let(:commit) { project.commit } + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:personal_snippet) { create(:personal_snippet, :repository) } + let_it_be(:project_snippet) { create(:project_snippet, :repository) } + let(:commit) { project.commit } describe 'modules' do subject { described_class } @@ -17,49 +19,67 @@ describe Commit do end describe '.lazy' do - let_it_be(:project) { create(:project, :repository) } + shared_examples '.lazy checks' do + context 'when the commits are found' do + let(:oids) do + %w( + 498214de67004b1da3d820901307bed2a68a8ef6 + c642fe9b8b9f28f9225d7ea953fe14e74748d53b + 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 + 048721d90c449b244b7b4c53a9186b04330174ec + 281d3a76f31c812dbf48abce82ccf6860adedd81 + ) + end - context 'when the commits are found' do - let(:oids) do - %w( - 498214de67004b1da3d820901307bed2a68a8ef6 - c642fe9b8b9f28f9225d7ea953fe14e74748d53b - 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 - 048721d90c449b244b7b4c53a9186b04330174ec - 281d3a76f31c812dbf48abce82ccf6860adedd81 - ) - end + subject { oids.map { |oid| described_class.lazy(container, oid) } } - subject { oids.map { |oid| described_class.lazy(project, oid) } } + it 'batches requests for commits' do + expect(container.repository).to receive(:commits_by).once.and_call_original - it 'batches requests for commits' do - expect(project.repository).to receive(:commits_by).once.and_call_original + subject.first.title + subject.last.title + end - subject.first.title - subject.last.title - end + it 'maintains ordering' do + subject.each_with_index do |commit, i| + expect(commit.id).to eq(oids[i]) + end + end - it 'maintains ordering' do - subject.each_with_index do |commit, i| - expect(commit.id).to eq(oids[i]) + it 'does not attempt to replace methods via BatchLoader' do + subject.each do |commit| + expect(commit).to receive(:method_missing).and_call_original + + commit.id + end end end - it 'does not attempt to replace methods via BatchLoader' do - subject.each do |commit| - expect(commit).to receive(:method_missing).and_call_original + context 'when not found' do + it 'returns nil as commit' do + commit = described_class.lazy(container, 'deadbeef').__sync - commit.id + expect(commit).to be_nil end end end - context 'when not found' do - it 'returns nil as commit' do - commit = described_class.lazy(project, 'deadbeef').__sync + context 'with project' do + let(:container) { project } - expect(commit).to be_nil - end + it_behaves_like '.lazy checks' + end + + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like '.lazy checks' + end + + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like '.lazy checks' end end @@ -231,15 +251,43 @@ describe Commit do end describe '#to_reference' do - let(:project) { create(:project, :repository, path: 'sample-project') } + context 'with project' do + let(:project) { create(:project, :repository, path: 'sample-project') } + + it 'returns a String reference to the object' do + expect(commit.to_reference).to eq commit.id + end - it 'returns a String reference to the object' do - expect(commit.to_reference).to eq commit.id + it 'supports a cross-project reference' do + another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) + expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}" + end end - it 'supports a cross-project reference' do - another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) - expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}" + context 'with personal snippet' do + let(:commit) { personal_snippet.commit } + + it 'returns a String reference to the object' do + expect(commit.to_reference).to eq "$#{personal_snippet.id}@#{commit.id}" + end + + it 'supports a cross-snippet reference' do + another_snippet = build(:personal_snippet) + expect(commit.to_reference(another_snippet)).to eq "$#{personal_snippet.id}@#{commit.id}" + end + end + + context 'with project snippet' do + let(:commit) { project_snippet.commit } + + it 'returns a String reference to the object' do + expect(commit.to_reference).to eq "$#{project_snippet.id}@#{commit.id}" + end + + it 'supports a cross-snippet project reference' do + another_snippet = build(:personal_snippet) + expect(commit.to_reference(another_snippet)).to eq "#{project_snippet.project.path}$#{project_snippet.id}@#{commit.id}" + end end end @@ -264,13 +312,41 @@ describe Commit do describe '#reference_link_text' do let(:project) { create(:project, :repository, path: 'sample-project') } - it 'returns a String reference to the object' do - expect(commit.reference_link_text).to eq commit.short_id + context 'with project' do + it 'returns a String reference to the object' do + expect(commit.reference_link_text).to eq commit.short_id + end + + it 'supports a cross-project reference' do + another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) + expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}" + end + end + + context 'with personal snippet' do + let(:commit) { personal_snippet.commit } + + it 'returns a String reference to the object' do + expect(commit.reference_link_text).to eq "$#{personal_snippet.id}@#{commit.short_id}" + end + + it 'supports a cross-snippet reference' do + another_snippet = build(:personal_snippet, :repository) + expect(commit.reference_link_text(another_snippet)).to eq "$#{personal_snippet.id}@#{commit.short_id}" + end end - it 'supports a cross-project reference' do - another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) - expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}" + context 'with project snippet' do + let(:commit) { project_snippet.commit } + + it 'returns a String reference to the object' do + expect(commit.reference_link_text).to eq "$#{project_snippet.id}@#{commit.short_id}" + end + + it 'supports a cross-snippet project reference' do + another_snippet = build(:project_snippet, :repository) + expect(commit.reference_link_text(another_snippet)).to eq "#{project_snippet.project.path}$#{project_snippet.id}@#{commit.short_id}" + end end end @@ -401,6 +477,26 @@ eos expect(commit.closes_issues).to be_empty end + + context 'with personal snippet' do + let(:commit) { personal_snippet.commit } + + it 'does not call Gitlab::ClosingIssueExtractor' do + expect(Gitlab::ClosingIssueExtractor).not_to receive(:new) + + commit.closes_issues + end + end + + context 'with project snippet' do + let(:commit) { project_snippet.commit } + + it 'does not call Gitlab::ClosingIssueExtractor' do + expect(Gitlab::ClosingIssueExtractor).not_to receive(:new) + + commit.closes_issues + end + end end it_behaves_like 'a mentionable' do @@ -597,19 +693,39 @@ eos end describe '.from_hash' do - let(:new_commit) { described_class.from_hash(commit.to_hash, project) } + subject { described_class.from_hash(commit.to_hash, container) } - it 'returns a Commit' do - expect(new_commit).to be_an_instance_of(described_class) + shared_examples 'returns Commit' do + it 'returns a Commit' do + expect(subject).to be_an_instance_of(described_class) + end + + it 'wraps a Gitlab::Git::Commit' do + expect(subject.raw).to be_an_instance_of(Gitlab::Git::Commit) + end + + it 'stores the correct commit fields' do + expect(subject.id).to eq(commit.id) + expect(subject.message).to eq(commit.message) + end + end + + context 'with project' do + let(:container) { project } + + it_behaves_like 'returns Commit' end - it 'wraps a Gitlab::Git::Commit' do - expect(new_commit.raw).to be_an_instance_of(Gitlab::Git::Commit) + context 'with personal snippet' do + let(:container) { personal_snippet } + + it_behaves_like 'returns Commit' end - it 'stores the correct commit fields' do - expect(new_commit.id).to eq(commit.id) - expect(new_commit.message).to eq(commit.message) + context 'with project snippet' do + let(:container) { project_snippet } + + it_behaves_like 'returns Commit' end end @@ -670,6 +786,19 @@ eos expect(commit1.merge_requests).to contain_exactly(merge_request1, merge_request2) expect(commit2.merge_requests).to contain_exactly(merge_request1) end + + context 'with personal snippet' do + it 'returns empty relation' do + expect(personal_snippet.repository.commit.merge_requests).to eq MergeRequest.none + end + end + + context 'with project snippet' do + it 'returns empty relation' do + expect(project_snippet.project).not_to receive(:merge_requests) + expect(project_snippet.repository.commit.merge_requests).to eq MergeRequest.none + end + end end describe 'signed commits' do diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index a9dca27f258..f88d64e2013 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -28,7 +28,7 @@ describe RedisCacheable do end describe '#cached_attribute' do - subject { instance.cached_attribute(payload.keys.first) } + subject { instance.cached_attribute(payload.each_key.first) } it 'gets the cache attribute' do Gitlab::Redis::SharedState.with do |redis| @@ -36,7 +36,7 @@ describe RedisCacheable do .and_return(payload.to_json) end - expect(subject).to eq(payload.values.first) + expect(subject).to eq(payload.each_value.first) end end diff --git a/spec/models/personal_snippet_spec.rb b/spec/models/personal_snippet_spec.rb index 276c8e22731..4a949a75cbd 100644 --- a/spec/models/personal_snippet_spec.rb +++ b/spec/models/personal_snippet_spec.rb @@ -16,4 +16,13 @@ describe PersonalSnippet do end end end + + it_behaves_like 'model with repository' do + let_it_be(:container) { create(:personal_snippet, :repository) } + let(:stubbed_container) { build_stubbed(:personal_snippet) } + let(:expected_full_path) { "@snippets/#{container.id}" } + let(:expected_repository_klass) { Repository } + let(:expected_storage_klass) { Storage::Hashed } + let(:expected_web_url_path) { "snippets/#{container.id}" } + end end diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb index 903671afb13..09b4ec3677c 100644 --- a/spec/models/project_snippet_spec.rb +++ b/spec/models/project_snippet_spec.rb @@ -32,4 +32,13 @@ describe ProjectSnippet do end end end + + it_behaves_like 'model with repository' do + let_it_be(:container) { create(:project_snippet, :repository) } + let(:stubbed_container) { build_stubbed(:project_snippet) } + let(:expected_full_path) { "#{container.project.full_path}/@snippets/#{container.id}" } + let(:expected_repository_klass) { Repository } + let(:expected_storage_klass) { Storage::Hashed } + let(:expected_web_url_path) { "#{container.project.full_path}/snippets/#{container.id}" } + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9dc362594dd..5c56d1aa757 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -113,6 +113,7 @@ describe Project do let(:expected_full_path) { "#{container.namespace.full_path}/somewhere" } let(:expected_repository_klass) { Repository } let(:expected_storage_klass) { Storage::Hashed } + let(:expected_web_url_path) { "#{container.namespace.full_path}/somewhere" } end it 'has an inverse relationship with merge requests' do @@ -5592,6 +5593,24 @@ describe Project do it { is_expected.to be_falsey } end + describe '#self_monitoring?' do + let_it_be(:project) { create(:project) } + + subject { project.self_monitoring? } + + context 'when the project is instance self monitoring' do + before do + stub_application_setting(self_monitoring_project_id: project.id) + end + + it { is_expected.to be true } + end + + context 'when the project is not self monitoring' do + it { is_expected.to be false } + end + end + def rugged_config rugged_repo(project.repository).config end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 77114696fd2..0adf3fc8e85 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -372,7 +372,7 @@ describe Repository do context 'when some commits are not found ' do let(:oids) do - ['deadbeef'] + TestEnv::BRANCH_SHA.values.first(10) + ['deadbeef'] + TestEnv::BRANCH_SHA.each_value.first(10) end it 'returns only found commits' do diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb new file mode 100644 index 00000000000..9befbb02b17 --- /dev/null +++ b/spec/models/snippet_repository_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SnippetRepository do + describe 'associations' do + it { is_expected.to belong_to(:shard) } + it { is_expected.to belong_to(:snippet) } + end + + describe '.find_snippet' do + it 'finds snippet by disk path' do + snippet = create(:snippet) + snippet.track_snippet_repository + + expect(described_class.find_snippet(snippet.disk_path)).to eq(snippet) + end + + it 'returns nil when it does not find the snippet' do + expect(described_class.find_snippet('@@unexisting/path/to/snippet')).to be_nil + end + end +end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index ae43c0d585a..93bc42c144d 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -19,6 +19,7 @@ describe Snippet do it { is_expected.to have_many(:notes).dependent(:destroy) } it { is_expected.to have_many(:award_emoji).dependent(:destroy) } it { is_expected.to have_many(:user_mentions).class_name("SnippetUserMention") } + it { is_expected.to have_one(:snippet_repository) } end describe 'validation' do @@ -525,4 +526,109 @@ describe Snippet do snippet.to_json(params) end end + + describe '#storage' do + let(:snippet) { create(:snippet) } + + it "stores snippet in #{Storage::Hashed::SNIPPET_REPOSITORY_PATH_PREFIX} dir" do + expect(snippet.storage.disk_path).to start_with Storage::Hashed::SNIPPET_REPOSITORY_PATH_PREFIX + end + end + + describe '#track_snippet_repository' do + let(:snippet) { create(:snippet, :repository) } + + context 'when a snippet repository entry does not exist' do + it 'creates a new entry' do + expect { snippet.track_snippet_repository }.to change(snippet, :snippet_repository) + end + + it 'tracks the snippet storage location' do + snippet.track_snippet_repository + + expect(snippet.snippet_repository).to have_attributes( + disk_path: snippet.disk_path, + shard_name: snippet.repository_storage + ) + end + end + + context 'when a tracking entry exists' do + let!(:snippet_repository) { create(:snippet_repository, snippet: snippet) } + let!(:shard) { create(:shard, name: 'foo') } + + it 'does not create a new entry in the database' do + expect { snippet.track_snippet_repository }.not_to change(snippet, :snippet_repository) + end + + it 'updates the snippet storage location' do + allow(snippet).to receive(:disk_path).and_return('fancy/new/path') + allow(snippet).to receive(:repository_storage).and_return('foo') + + snippet.track_snippet_repository + + expect(snippet.snippet_repository).to have_attributes( + disk_path: 'fancy/new/path', + shard_name: 'foo' + ) + end + end + end + + describe '#create_repository' do + let(:snippet) { create(:snippet) } + + it 'creates the repository' do + expect(snippet.repository).to receive(:after_create).and_call_original + + expect(snippet.create_repository).to be_truthy + expect(snippet.repository.exists?).to be_truthy + end + + it 'tracks snippet repository' do + expect do + snippet.create_repository + end.to change(SnippetRepository, :count).by(1) + end + + context 'when repository exists' do + let(:snippet) { create(:snippet, :repository) } + + it 'does not try to create repository' do + expect(snippet.repository).not_to receive(:after_create) + + expect(snippet.create_repository).to be_nil + end + + it 'does not track snippet repository' do + expect do + snippet.create_repository + end.not_to change(SnippetRepository, :count) + end + end + end + + describe '#repository_storage' do + let(:snippet) { create(:snippet) } + + it 'returns default repository storage' do + expect(Gitlab::CurrentSettings).to receive(:pick_repository_storage) + + snippet.repository_storage + end + + context 'when snippet_project is already created' do + let!(:snippet_repository) { create(:snippet_repository, snippet: snippet) } + + before do + allow(snippet_repository).to receive(:shard_name).and_return('foo') + end + + it 'returns repository_storage from snippet_project' do + expect(Gitlab::CurrentSettings).not_to receive(:pick_repository_storage) + + expect(snippet.repository_storage).to eq 'foo' + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ac001ec3118..90795e0a8e4 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4160,7 +4160,7 @@ describe User, :do_not_mock_admin_mode do describe '#dismissed_callout?' do subject(:user) { create(:user) } - let(:feature_name) { UserCallout.feature_names.keys.first } + let(:feature_name) { UserCallout.feature_names.each_key.first } context 'when no callout dismissal record exists' do it 'returns false when no ignore_dismissal_earlier_than provided' do diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb index ece2033f9f8..7cec0867b32 100644 --- a/spec/requests/api/project_container_repositories_spec.rb +++ b/spec/requests/api/project_container_repositories_spec.rb @@ -142,7 +142,8 @@ describe API::ProjectContainerRepositories do let(:worker_params) do { name_regex: 'v10.*', keep_n: 100, - older_than: '1 day' } + older_than: '1 day', + container_expiration_policy: false } end let(:lease_key) { "container_repository:cleanup_tags:#{root_repository.id}" } diff --git a/spec/serializers/test_suite_entity_spec.rb b/spec/serializers/test_suite_entity_spec.rb index 54dca3214b7..6a9653954f3 100644 --- a/spec/serializers/test_suite_entity_spec.rb +++ b/spec/serializers/test_suite_entity_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe TestSuiteEntity do let(:pipeline) { create(:ci_pipeline, :with_test_reports) } - let(:entity) { described_class.new(pipeline.test_reports.test_suites.values.first) } + let(:entity) { described_class.new(pipeline.test_reports.test_suites.each_value.first) } describe '#as_json' do subject(:as_json) { entity.as_json } diff --git a/spec/services/container_expiration_policy_service_spec.rb b/spec/services/container_expiration_policy_service_spec.rb index 1e4899c627f..b2f2b2e1236 100644 --- a/spec/services/container_expiration_policy_service_spec.rb +++ b/spec/services/container_expiration_policy_service_spec.rb @@ -17,7 +17,7 @@ describe ContainerExpirationPolicyService do it 'kicks off a cleanup worker for the container repository' do expect(CleanupContainerRepositoryWorker).to receive(:perform_async) - .with(user.id, container_repository.id, anything) + .with(nil, container_repository.id, hash_including(container_expiration_policy: true)) subject end diff --git a/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb b/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb new file mode 100644 index 00000000000..9ee5b06b410 --- /dev/null +++ b/spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::Dashboard::SelfMonitoringDashboardService, :use_clean_rails_memory_store_caching do + include MetricsDashboardHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:environment) { create(:environment, project: project) } + + before do + project.add_maintainer(user) + stub_application_setting(self_monitoring_project_id: project.id) + end + + describe '#get_dashboard' do + let(:service_params) { [project, user, { environment: environment }] } + let(:service_call) { described_class.new(*service_params).get_dashboard } + + it_behaves_like 'valid dashboard service response' + it_behaves_like 'raises error for users with insufficient permissions' + it_behaves_like 'caches the unprocessed dashboard for subsequent calls' + end + + describe '.all_dashboard_paths' do + it 'returns the dashboard attributes' do + all_dashboards = described_class.all_dashboard_paths(project) + + expect(all_dashboards).to eq( + [{ + path: described_class::DASHBOARD_PATH, + display_name: described_class::DASHBOARD_NAME, + default: true, + system_dashboard: false + }] + ) + end + end + + describe '.valid_params?' do + subject { described_class.valid_params?(params) } + + context 'with environment' do + let(:params) { { environment: environment } } + + it { is_expected.to be_truthy } + end + + context 'with dashboard_path' do + let(:params) { { dashboard_path: self_monitoring_dashboard_path } } + + it { is_expected.to be_truthy } + end + + context 'with a different dashboard selected' do + let(:dashboard_path) { '.gitlab/dashboards/test.yml' } + let(:params) { { dashboard_path: dashboard_path, environment: environment } } + + it { is_expected.to be_falsey } + end + + context 'missing environment and dashboard_path' do + let(:params) { {} } + + it { is_expected.to be_falsey } + end + end +end diff --git a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb index cd4d1e3fe67..ef7e9cda9e0 100644 --- a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb +++ b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb @@ -130,6 +130,38 @@ describe Projects::ContainerRepository::CleanupTagsService do is_expected.to include(status: :success, deleted: %w(Bb Ba C)) end end + + context 'when running a container_expiration_policy' do + let(:user) { nil } + + context 'with valid container_expiration_policy param' do + let(:params) do + { 'name_regex' => '.*', + 'keep_n' => 1, + 'older_than' => '1 day', + 'container_expiration_policy' => true } + end + + it 'succeeds without a user' do + expect_delete('sha256:configB').twice + expect_delete('sha256:configC') + + is_expected.to include(status: :success, deleted: %w(Bb Ba C)) + end + end + + context 'without container_expiration_policy param' do + let(:params) do + { 'name_regex' => '.*', + 'keep_n' => 1, + 'older_than' => '1 day' } + end + + it 'fails' do + is_expected.to include(status: :error, message: 'access denied') + end + end + end end private diff --git a/spec/services/users/block_service_spec.rb b/spec/services/users/block_service_spec.rb new file mode 100644 index 00000000000..c3a65a08c0d --- /dev/null +++ b/spec/services/users/block_service_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Users::BlockService do + let(:current_user) { create(:admin) } + + subject(:service) { described_class.new(current_user) } + + describe '#execute' do + subject(:operation) { service.execute(user) } + + context 'when successful' do + let(:user) { create(:user) } + + it { is_expected.to eq(status: :success) } + + it "change the user's state" do + expect { operation }.to change { user.state }.to('blocked') + end + end + + context 'when failed' do + let(:user) { create(:user, :blocked) } + + it 'returns error result' do + aggregate_failures 'error result' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to match(/State cannot transition/) + end + end + + it "does not change the user's state" do + expect { operation }.not_to change { user.state } + end + end + end +end diff --git a/spec/support/helpers/fake_blob_helpers.rb b/spec/support/helpers/fake_blob_helpers.rb index ef4740638ff..a7eafb0fd23 100644 --- a/spec/support/helpers/fake_blob_helpers.rb +++ b/spec/support/helpers/fake_blob_helpers.rb @@ -37,6 +37,8 @@ module FakeBlobHelpers end def fake_blob(**kwargs) - Blob.decorate(FakeBlob.new(**kwargs), project) + container = kwargs.delete(:container) || project + + Blob.decorate(FakeBlob.new(**kwargs), container) end end diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb index 908a3e1fb09..b8a641d5911 100644 --- a/spec/support/helpers/metrics_dashboard_helpers.rb +++ b/spec/support/helpers/metrics_dashboard_helpers.rb @@ -29,4 +29,8 @@ module MetricsDashboardHelpers def business_metric_title PrometheusMetricEnums.group_details[:business][:group_title] end + + def self_monitoring_dashboard_path + Metrics::Dashboard::SelfMonitoringDashboardService::DASHBOARD_PATH + end end diff --git a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb index 3d88b317417..69ae9339f10 100644 --- a/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb @@ -2,7 +2,7 @@ RSpec.shared_examples 'a repo type' do describe '#identifier_for_container' do - subject { described_class.identifier_for_container(project) } + subject { described_class.identifier_for_container(expected_container) } it { is_expected.to eq(expected_identifier) } end @@ -35,7 +35,7 @@ RSpec.shared_examples 'a repo type' do describe '#repository_for' do it 'finds the repository for the repo type' do - expect(described_class.repository_for(project)).to eq(expected_repository) + expect(described_class.repository_for(expected_container)).to eq(expected_repository) end end end diff --git a/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb index fdea312dfa9..d5606e65981 100644 --- a/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb @@ -18,7 +18,7 @@ RSpec.shared_examples 'model with repository' do let(:only_path) { false } it 'returns the full web URL for this repo' do - expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{expected_full_path}") + expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{expected_web_url_path}") end end @@ -26,7 +26,7 @@ RSpec.shared_examples 'model with repository' do let(:only_path) { true } it 'returns the relative web URL for this repo' do - expect(subject).to eq("/#{expected_full_path}") + expect(subject).to eq("/#{expected_web_url_path}") end end @@ -34,14 +34,14 @@ RSpec.shared_examples 'model with repository' do let(:only_path) { nil } it 'returns the full web URL for this repo' do - expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{expected_full_path}") + expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{expected_web_url_path}") end end end context 'when not given the only_path option' do it 'returns the full web URL for this repo' do - expect(container.web_url).to eq("#{Gitlab.config.gitlab.url}/#{expected_full_path}") + expect(container.web_url).to eq("#{Gitlab.config.gitlab.url}/#{expected_web_url_path}") end end end @@ -72,7 +72,7 @@ RSpec.shared_examples 'model with repository' do let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab/' } it 'returns the url to the repo, with the root replaced with the custom one' do - expect(subject).to eq("#{custom_http_clone_url_root}#{expected_full_path}.git") + expect(subject).to eq("#{custom_http_clone_url_root}#{expected_web_url_path}.git") end end @@ -80,7 +80,7 @@ RSpec.shared_examples 'model with repository' do let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab' } it 'returns the url to the repo, with the root replaced with the custom one' do - expect(subject).to eq("#{custom_http_clone_url_root}/#{expected_full_path}.git") + expect(subject).to eq("#{custom_http_clone_url_root}/#{expected_web_url_path}.git") end end end @@ -90,7 +90,7 @@ RSpec.shared_examples 'model with repository' do let(:custom_http_clone_url_root) { 'https://git.example.com:51234/' } it 'returns the url to the repo, with the root replaced with the custom one' do - expect(subject).to eq("#{custom_http_clone_url_root}#{expected_full_path}.git") + expect(subject).to eq("#{custom_http_clone_url_root}#{expected_web_url_path}.git") end end @@ -98,7 +98,7 @@ RSpec.shared_examples 'model with repository' do let(:custom_http_clone_url_root) { 'https://git.example.com:51234' } it 'returns the url to the repo, with the root replaced with the custom one' do - expect(subject).to eq("#{custom_http_clone_url_root}/#{expected_full_path}.git") + expect(subject).to eq("#{custom_http_clone_url_root}/#{expected_web_url_path}.git") end end end diff --git a/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb b/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb index 1488027201c..8f7c08ed625 100644 --- a/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb @@ -50,5 +50,20 @@ RSpec.shared_examples 'multiple boards list service' do it 'returns boards ordered by name' do expect(service.execute).to eq [board_a, board_B, board_c] end + + context 'when wanting a specific board' do + it 'returns board specified by id' do + service = described_class.new(parent, double, board_id: board_c.id) + + expect(service.execute).to eq [board_c] + end + + it 'raises exception when board is not found' do + outside_board = create(:board, resource_parent: create(:project), name: 'outside board') + service = described_class.new(parent, double, board_id: outside_board.id) + + expect { service.execute }.to raise_exception(ActiveRecord::RecordNotFound) + end + end end end diff --git a/spec/workers/cleanup_container_repository_worker_spec.rb b/spec/workers/cleanup_container_repository_worker_spec.rb index 9be8f882785..1228c2c2d9c 100644 --- a/spec/workers/cleanup_container_repository_worker_spec.rb +++ b/spec/workers/cleanup_container_repository_worker_spec.rb @@ -6,34 +6,49 @@ describe CleanupContainerRepositoryWorker, :clean_gitlab_redis_shared_state do let(:repository) { create(:container_repository) } let(:project) { repository.project } let(:user) { project.owner } - let(:params) { { key: 'value' } } subject { described_class.new } describe '#perform' do let(:service) { instance_double(Projects::ContainerRepository::CleanupTagsService) } - before do - allow(Projects::ContainerRepository::CleanupTagsService).to receive(:new) - .with(project, user, params).and_return(service) + context 'bulk delete api' do + let(:params) { { key: 'value', 'container_expiration_policy' => false } } + + it 'executes the destroy service' do + expect(Projects::ContainerRepository::CleanupTagsService).to receive(:new) + .with(project, user, params.merge('container_expiration_policy' => false)) + .and_return(service) + expect(service).to receive(:execute) + + subject.perform(user.id, repository.id, params) + end + + it 'does not raise error when user could not be found' do + expect do + subject.perform(-1, repository.id, params) + end.not_to raise_error + end + + it 'does not raise error when repository could not be found' do + expect do + subject.perform(user.id, -1, params) + end.not_to raise_error + end end - it 'executes the destroy service' do - expect(service).to receive(:execute) + context 'container expiration policy' do + let(:params) { { key: 'value', 'container_expiration_policy' => true } } - subject.perform(user.id, repository.id, params) - end + it 'executes the destroy service' do + expect(Projects::ContainerRepository::CleanupTagsService).to receive(:new) + .with(project, nil, params.merge('container_expiration_policy' => true)) + .and_return(service) - it 'does not raise error when user could not be found' do - expect do - subject.perform(-1, repository.id, params) - end.not_to raise_error - end + expect(service).to receive(:execute) - it 'does not raise error when repository could not be found' do - expect do - subject.perform(user.id, -1, params) - end.not_to raise_error + subject.perform(nil, repository.id, params) + end end end end |