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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-13 18:08:52 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-13 18:08:52 +0300
commit0ab47b994caa80c5587f33dc818626b66cfdafe2 (patch)
tree5ef3976d2f84e3368903a67ba2dbd87a74b9a43c /spec
parent1308dc5eb484ab0f8064989fc551ebdb4b1a7976 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/user_callouts_controller_spec.rb4
-rw-r--r--spec/factories/snippet_repositories.rb12
-rw-r--r--spec/factories/snippets.rb15
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb20
-rw-r--r--spec/features/snippets/spam_snippets_spec.rb8
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb10
-rw-r--r--spec/frontend/diffs/components/diff_table_cell_spec.js (renamed from spec/frontend/diffs/components/diff_line_gutter_content_spec.js)44
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js75
-rw-r--r--spec/frontend/snippet/collapsible_input_spec.js104
-rw-r--r--spec/javascripts/diffs/components/diff_table_cell_spec.js37
-rw-r--r--spec/lib/gitlab/git_access_snippet_spec.rb85
-rw-r--r--spec/lib/gitlab/gl_repository/repo_type_spec.rb38
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/hash_util_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb34
-rw-r--r--spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb6
-rw-r--r--spec/lib/gitlab/shell_spec.rb2
-rw-r--r--spec/models/blob_spec.rb543
-rw-r--r--spec/models/commit_spec.rb229
-rw-r--r--spec/models/concerns/redis_cacheable_spec.rb4
-rw-r--r--spec/models/personal_snippet_spec.rb9
-rw-r--r--spec/models/project_snippet_spec.rb9
-rw-r--r--spec/models/project_spec.rb19
-rw-r--r--spec/models/repository_spec.rb2
-rw-r--r--spec/models/snippet_repository_spec.rb23
-rw-r--r--spec/models/snippet_spec.rb106
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/requests/api/project_container_repositories_spec.rb3
-rw-r--r--spec/serializers/test_suite_entity_spec.rb2
-rw-r--r--spec/services/container_expiration_policy_service_spec.rb2
-rw-r--r--spec/services/metrics/dashboard/self_monitoring_dashboard_service_spec.rb69
-rw-r--r--spec/services/projects/container_repository/cleanup_tags_service_spec.rb32
-rw-r--r--spec/services/users/block_service_spec.rb38
-rw-r--r--spec/support/helpers/fake_blob_helpers.rb4
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb4
-rw-r--r--spec/support/shared_examples/lib/gitlab/repo_type_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb16
-rw-r--r--spec/support/shared_examples/services/boards/boards_list_service_shared_examples.rb15
-rw-r--r--spec/workers/cleanup_container_repository_worker_spec.rb49
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