summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-09-23 18:06:14 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-09-23 18:06:14 +0000
commitc792263edfaf826c58f4aa41d26904464a17a3e7 (patch)
treeb57ae96c9eeaf0a1432a29f7f50f2fce9529818d /spec
parent6f9edd1a4c4942d3d13ec54793cfae56164b1a0a (diff)
downloadgitlab-ce-c792263edfaf826c58f4aa41d26904464a17a3e7.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/merge_request/user_suggests_changes_on_diff_spec.rb58
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json6
-rw-r--r--spec/frontend/ide/components/file_templates/dropdown_spec.js175
-rw-r--r--spec/frontend/releases/components/release_block_spec.js7
-rw-r--r--spec/javascripts/ide/components/file_templates/dropdown_spec.js201
-rw-r--r--spec/javascripts/releases/components/release_block_spec.js170
-rw-r--r--spec/lib/backup/manager_spec.rb45
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb20
-rw-r--r--spec/lib/gitlab/file_type_detection_spec.rb273
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb43
-rw-r--r--spec/models/repository_spec.rb52
-rw-r--r--spec/models/suggestion_spec.rb10
-rw-r--r--spec/requests/api/releases_spec.rb21
-rw-r--r--spec/support/helpers/repo_helpers.rb4
16 files changed, 638 insertions, 452 deletions
diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
index 4363b359038..3d26ff3ed94 100644
--- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
+++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
@@ -14,6 +14,10 @@ describe 'User comments on a diff', :js do
expect(suggested_content).to eq(expected_suggested_content)
end
+ def expect_appliable_suggestions(amount)
+ expect(all('button', text: 'Apply suggestion').size).to eq(amount)
+ end
+
let(:project) { create(:project, :repository) }
let(:merge_request) do
create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test')
@@ -89,6 +93,60 @@ describe 'User comments on a diff', :js do
end
end
+ context 'multiple suggestions in expanded lines' do
+ it 'suggestions are appliable' do
+ diff_file = merge_request.diffs(paths: ['files/ruby/popen.rb']).diff_files.first
+ hash = Digest::SHA1.hexdigest(diff_file.file_path)
+
+ expanded_changes = [
+ {
+ line_code: "#{hash}_1_1",
+ file_path: diff_file.file_path
+ },
+ {
+ line_code: "#{hash}_5_5",
+ file_path: diff_file.file_path
+ }
+ ]
+ changes = sample_compare(expanded_changes).changes.last(expanded_changes.size)
+
+ page.within("[id='#{hash}']") do
+ find("button[data-original-title='Show full file']").click
+ wait_for_requests
+
+ click_diff_line(find("[id='#{changes.first[:line_code]}']"))
+
+ page.within('.js-discussion-note-form') do
+ fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
+ click_button('Comment')
+ wait_for_requests
+ end
+
+ click_diff_line(find("[id='#{changes.last[:line_code]}']"))
+
+ page.within('.js-discussion-note-form') do
+ fill_in('note_note', with: "```suggestion\n# 2nd change to a comment\n```")
+ click_button('Comment')
+ wait_for_requests
+ end
+
+ expect_appliable_suggestions(2)
+ end
+
+ # Making sure it's not a Front-end cache.
+ visit(diffs_project_merge_request_path(project, merge_request))
+
+ expect_appliable_suggestions(2)
+
+ page.within("[id='#{hash}']") do
+ all('button', text: 'Apply suggestion').last.click
+ wait_for_requests
+
+ expect(page).to have_content('Applied')
+ end
+ end
+ end
+
context 'multiple suggestions in a single note' do
it 'suggestions are presented' do
click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json
index 662e61a9c06..3ca6167d0c6 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release.json
@@ -19,6 +19,9 @@
"type": "array",
"items": { "$ref": "milestone.json" }
},
+ "commit_path": { "type": "string" },
+ "tag_path": { "type": "string" },
+ "name": { "type": "string" },
"assets": {
"required": ["count", "links", "sources"],
"properties": {
diff --git a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
index 0c1e8fd5fb3..57814b8bf73 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json
@@ -8,6 +8,12 @@
"created_at": { "type": "date" },
"released_at": { "type": "date" },
"upcoming_release": { "type": "boolean" },
+ "milestones": {
+ "type": "array",
+ "items": { "$ref": "../milestone.json" }
+ },
+ "commit_path": { "type": "string" },
+ "tag_path": { "type": "string" },
"author": {
"oneOf": [{ "type": "null" }, { "$ref": "../user/basic.json" }]
},
diff --git a/spec/frontend/ide/components/file_templates/dropdown_spec.js b/spec/frontend/ide/components/file_templates/dropdown_spec.js
new file mode 100644
index 00000000000..83d797469ad
--- /dev/null
+++ b/spec/frontend/ide/components/file_templates/dropdown_spec.js
@@ -0,0 +1,175 @@
+import Vuex from 'vuex';
+import $ from 'jquery';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Dropdown from '~/ide/components/file_templates/dropdown.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('IDE file templates dropdown component', () => {
+ let wrapper;
+ let element;
+ let fetchTemplateTypesMock;
+
+ const defaultProps = {
+ label: 'label',
+ };
+
+ const findItemButtons = () => wrapper.findAll('button');
+ const findSearch = () => wrapper.find('input[type="search"]');
+ const triggerDropdown = () => $(element).trigger('show.bs.dropdown');
+
+ const createComponent = ({ props, state } = {}) => {
+ fetchTemplateTypesMock = jest.fn();
+ const fakeStore = new Vuex.Store({
+ modules: {
+ fileTemplates: {
+ namespaced: true,
+ state: {
+ templates: [],
+ isLoading: false,
+ ...state,
+ },
+ actions: {
+ fetchTemplateTypes: fetchTemplateTypesMock,
+ },
+ },
+ },
+ });
+
+ wrapper = shallowMount(Dropdown, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ store: fakeStore,
+ localVue,
+ sync: false,
+ });
+
+ ({ element } = wrapper);
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('calls clickItem on click', () => {
+ const itemData = { name: 'test.yml ' };
+ createComponent({ props: { data: [itemData] } });
+ const item = findItemButtons().at(0);
+ item.trigger('click');
+
+ expect(wrapper.emitted().click[0][0]).toBe(itemData);
+ });
+
+ it('renders dropdown title', () => {
+ const title = 'Test title';
+ createComponent({ props: { title } });
+
+ expect(wrapper.find('.dropdown-title').text()).toContain(title);
+ });
+
+ describe('in async mode', () => {
+ const defaultAsyncProps = { ...defaultProps, isAsyncData: true };
+
+ it('calls `fetchTemplateTypes` on dropdown event', () => {
+ createComponent({ props: defaultAsyncProps });
+
+ triggerDropdown();
+
+ expect(fetchTemplateTypesMock).toHaveBeenCalled();
+ });
+
+ it('does not call `fetchTemplateTypes` on dropdown event if destroyed', () => {
+ createComponent({ props: defaultAsyncProps });
+ wrapper.destroy();
+
+ triggerDropdown();
+
+ expect(fetchTemplateTypesMock).not.toHaveBeenCalled();
+ });
+
+ it('shows loader when isLoading is true', () => {
+ createComponent({ props: defaultAsyncProps, state: { isLoading: true } });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('renders templates', () => {
+ const templates = [{ name: 'file-1' }, { name: 'file-2' }];
+ createComponent({
+ props: { ...defaultAsyncProps, data: [{ name: 'should-never-appear ' }] },
+ state: {
+ templates,
+ },
+ });
+ const items = findItemButtons();
+
+ expect(items.wrappers.map(x => x.text())).toEqual(templates.map(x => x.name));
+ });
+
+ it('searches template data', () => {
+ const templates = [{ name: 'match 1' }, { name: 'other' }, { name: 'match 2' }];
+ const matches = ['match 1', 'match 2'];
+ createComponent({
+ props: { ...defaultAsyncProps, data: matches, searchable: true },
+ state: { templates },
+ });
+ findSearch().setValue('match');
+ return wrapper.vm.$nextTick().then(() => {
+ const items = findItemButtons();
+
+ expect(items.length).toBe(matches.length);
+ expect(items.wrappers.map(x => x.text())).toEqual(matches);
+ });
+ });
+
+ it('does not render input when `searchable` is true & `showLoading` is true', () => {
+ createComponent({
+ props: { ...defaultAsyncProps, searchable: true },
+ state: { isLoading: true },
+ });
+
+ expect(findSearch().exists()).toBe(false);
+ });
+ });
+
+ describe('in sync mode', () => {
+ it('renders props data', () => {
+ const data = [{ name: 'file-1' }, { name: 'file-2' }];
+ createComponent({
+ props: { data },
+ state: {
+ templates: [{ name: 'should-never-appear ' }],
+ },
+ });
+
+ const items = findItemButtons();
+
+ expect(items.length).toBe(data.length);
+ expect(items.wrappers.map(x => x.text())).toEqual(data.map(x => x.name));
+ });
+
+ it('renders input when `searchable` is true', () => {
+ createComponent({ props: { searchable: true } });
+
+ expect(findSearch().exists()).toBe(true);
+ });
+
+ it('searches data', () => {
+ const data = [{ name: 'match 1' }, { name: 'other' }, { name: 'match 2' }];
+ const matches = ['match 1', 'match 2'];
+ createComponent({ props: { searchable: true, data } });
+ findSearch().setValue('match');
+ return wrapper.vm.$nextTick().then(() => {
+ const items = findItemButtons();
+
+ expect(items.length).toBe(matches.length);
+ expect(items.wrappers.map(x => x.text())).toEqual(matches);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js
index 4be5d500fd9..229d3799ee1 100644
--- a/spec/frontend/releases/components/release_block_spec.js
+++ b/spec/frontend/releases/components/release_block_spec.js
@@ -12,7 +12,6 @@ describe('Release block', () => {
propsData: {
release: releaseProp,
},
- sync: false,
});
};
@@ -37,10 +36,16 @@ describe('Release block', () => {
it('renders commit sha', () => {
expect(wrapper.text()).toContain(release.commit.short_id);
+
+ wrapper.setProps({ release: { ...release, commit_path: '/commit/example' } });
+ expect(wrapper.find('a[href="/commit/example"]').exists()).toBe(true);
});
it('renders tag name', () => {
expect(wrapper.text()).toContain(release.tag_name);
+
+ wrapper.setProps({ release: { ...release, tag_path: '/tag/example' } });
+ expect(wrapper.find('a[href="/tag/example"]').exists()).toBe(true);
});
it('renders release date', () => {
diff --git a/spec/javascripts/ide/components/file_templates/dropdown_spec.js b/spec/javascripts/ide/components/file_templates/dropdown_spec.js
deleted file mode 100644
index 898796f4fa0..00000000000
--- a/spec/javascripts/ide/components/file_templates/dropdown_spec.js
+++ /dev/null
@@ -1,201 +0,0 @@
-import $ from 'jquery';
-import Vue from 'vue';
-import { createStore } from '~/ide/stores';
-import Dropdown from '~/ide/components/file_templates/dropdown.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../../helpers';
-
-describe('IDE file templates dropdown component', () => {
- let Component;
- let vm;
-
- beforeAll(() => {
- Component = Vue.extend(Dropdown);
- });
-
- beforeEach(() => {
- const store = createStore();
-
- vm = createComponentWithStore(Component, store, {
- label: 'Test',
- }).$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- resetStore(vm.$store);
- });
-
- describe('async', () => {
- beforeEach(() => {
- vm.isAsyncData = true;
- });
-
- it('calls async store method on Bootstrap dropdown event', () => {
- spyOn(vm, 'fetchTemplateTypes').and.stub();
-
- $(vm.$el).trigger('show.bs.dropdown');
-
- expect(vm.fetchTemplateTypes).toHaveBeenCalled();
- });
-
- it('renders templates when async', done => {
- vm.$store.state.fileTemplates.templates = [
- {
- name: 'test',
- },
- ];
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test');
-
- done();
- });
- });
-
- it('renders loading icon when isLoading is true', done => {
- vm.$store.state.fileTemplates.isLoading = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
-
- done();
- });
- });
-
- it('searches template data', () => {
- vm.$store.state.fileTemplates.templates = [
- {
- name: 'test',
- },
- ];
- vm.searchable = true;
- vm.search = 'hello';
-
- expect(vm.outputData).toEqual([]);
- });
-
- it('does not filter data is searchable is false', () => {
- vm.$store.state.fileTemplates.templates = [
- {
- name: 'test',
- },
- ];
- vm.search = 'hello';
-
- expect(vm.outputData).toEqual([
- {
- name: 'test',
- },
- ]);
- });
-
- it('calls clickItem on click', done => {
- spyOn(vm, 'clickItem').and.stub();
-
- vm.$store.state.fileTemplates.templates = [
- {
- name: 'test',
- },
- ];
-
- vm.$nextTick(() => {
- vm.$el.querySelector('.dropdown-content button').click();
-
- expect(vm.clickItem).toHaveBeenCalledWith({
- name: 'test',
- });
-
- done();
- });
- });
-
- it('renders input when searchable is true', done => {
- vm.searchable = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null);
-
- done();
- });
- });
-
- it('does not render input when searchable is true & showLoading is true', done => {
- vm.searchable = true;
- vm.$store.state.fileTemplates.isLoading = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-input')).toBe(null);
-
- done();
- });
- });
- });
-
- describe('sync', () => {
- beforeEach(done => {
- vm.data = [
- {
- name: 'test sync',
- },
- ];
-
- vm.$nextTick(done);
- });
-
- it('renders props data', () => {
- expect(vm.$el.querySelector('.dropdown-content').textContent).toContain('test sync');
- });
-
- it('renders input when searchable is true', done => {
- vm.searchable = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-input')).not.toBe(null);
-
- done();
- });
- });
-
- it('calls clickItem on click', done => {
- spyOn(vm, 'clickItem').and.stub();
-
- vm.$nextTick(() => {
- vm.$el.querySelector('.dropdown-content button').click();
-
- expect(vm.clickItem).toHaveBeenCalledWith({
- name: 'test sync',
- });
-
- done();
- });
- });
-
- it('searches template data', () => {
- vm.searchable = true;
- vm.search = 'hello';
-
- expect(vm.outputData).toEqual([]);
- });
-
- it('does not filter data is searchable is false', () => {
- vm.search = 'hello';
-
- expect(vm.outputData).toEqual([
- {
- name: 'test sync',
- },
- ]);
- });
-
- it('renders dropdown title', done => {
- vm.title = 'Test title';
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dropdown-title').textContent).toContain('Test title');
-
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js
deleted file mode 100644
index 11a385fa64d..00000000000
--- a/spec/javascripts/releases/components/release_block_spec.js
+++ /dev/null
@@ -1,170 +0,0 @@
-import Vue from 'vue';
-import component from '~/releases/components/release_block.vue';
-import timeagoMixin from '~/vue_shared/mixins/timeago';
-
-import mountComponent from '../../helpers/vue_mount_component_helper';
-
-describe('Release block', () => {
- const Component = Vue.extend(component);
-
- const release = {
- name: 'Bionic Beaver',
- tag_name: '18.04',
- description: '## changelog\n\n* line 1\n* line2',
- description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
- author_name: 'Release bot',
- author_email: 'release-bot@example.com',
- released_at: '2012-05-28T05:00:00-07:00',
- author: {
- avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
- id: 482476,
- name: 'John Doe',
- path: '/johndoe',
- state: 'active',
- status_tooltip_html: null,
- username: 'johndoe',
- web_url: 'https://gitlab.com/johndoe',
- },
- commit: {
- id: '2695effb5807a22ff3d138d593fd856244e155e7',
- short_id: '2695effb',
- title: 'Initial commit',
- created_at: '2017-07-26T11:08:53.000+02:00',
- parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'],
- message: 'Initial commit',
- author_name: 'John Smith',
- author_email: 'john@example.com',
- authored_date: '2012-05-28T04:42:42-07:00',
- committer_name: 'Jack Smith',
- committer_email: 'jack@example.com',
- committed_date: '2012-05-28T04:42:42-07:00',
- },
- assets: {
- count: 6,
- sources: [
- {
- format: 'zip',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip',
- },
- {
- format: 'tar.gz',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz',
- },
- {
- format: 'tar.bz2',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2',
- },
- {
- format: 'tar',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar',
- },
- ],
- links: [
- {
- name: 'release-18.04.dmg',
- url: 'https://my-external-hosting.example.com/scrambled-url/',
- external: true,
- },
- {
- name: 'binary-linux-amd64',
- url:
- 'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
- external: false,
- },
- ],
- },
- };
- let vm;
-
- const factory = props => mountComponent(Component, { release: props });
-
- beforeEach(() => {
- vm = factory(release);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it("renders the block with an id equal to the release's tag name", () => {
- expect(vm.$el.id).toBe('18.04');
- });
-
- it('renders release name', () => {
- expect(vm.$el.textContent).toContain(release.name);
- });
-
- it('renders commit sha', () => {
- expect(vm.$el.textContent).toContain(release.commit.short_id);
- });
-
- it('renders tag name', () => {
- expect(vm.$el.textContent).toContain(release.tag_name);
- });
-
- it('renders release date', () => {
- expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at));
- });
-
- it('renders number of assets provided', () => {
- expect(vm.$el.querySelector('.js-assets-count').textContent).toContain(release.assets.count);
- });
-
- it('renders dropdown with the sources', () => {
- expect(vm.$el.querySelectorAll('.js-sources-dropdown li').length).toEqual(
- release.assets.sources.length,
- );
-
- expect(vm.$el.querySelector('.js-sources-dropdown li a').getAttribute('href')).toEqual(
- release.assets.sources[0].url,
- );
-
- expect(vm.$el.querySelector('.js-sources-dropdown li a').textContent).toContain(
- release.assets.sources[0].format,
- );
- });
-
- it('renders list with the links provided', () => {
- expect(vm.$el.querySelectorAll('.js-assets-list li').length).toEqual(
- release.assets.links.length,
- );
-
- expect(vm.$el.querySelector('.js-assets-list li a').getAttribute('href')).toEqual(
- release.assets.links[0].url,
- );
-
- expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain(
- release.assets.links[0].name,
- );
- });
-
- it('renders author avatar', () => {
- expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull();
- });
-
- describe('external label', () => {
- it('renders external label when link is external', () => {
- expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain('external source');
- });
-
- it('does not render external label when link is not external', () => {
- expect(vm.$el.querySelector('.js-assets-list li:nth-child(2) a').textContent).not.toContain(
- 'external source',
- );
- });
- });
-
- describe('with upcoming_release flag', () => {
- beforeEach(() => {
- vm = factory(Object.assign({}, release, { upcoming_release: true }));
- });
-
- it('renders upcoming release badge', () => {
- expect(vm.$el.textContent).toContain('Upcoming Release');
- });
- });
-});
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index fee7ffc60ee..35594cd2fb8 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -21,6 +21,49 @@ describe Backup::Manager do
$progress = @old_progress # rubocop:disable Style/GlobalVars
end
+ describe '#pack' do
+ let(:backup_contents) { ['backup_contents'] }
+ let(:tar_system_options) { { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] } }
+ let(:tar_cmdline) { ['tar', '-cf', '-', *backup_contents, tar_system_options] }
+
+ let(:backup_information) do
+ {
+ backup_created_at: Time.zone.parse('2019-01-01'),
+ gitlab_version: '12.3'
+ }
+ end
+
+ before do
+ allow(ActiveRecord::Base.connection).to receive(:reconnect!)
+ allow(Kernel).to receive(:system).and_return(true)
+
+ allow(subject).to receive(:backup_contents).and_return(backup_contents)
+ allow(subject).to receive(:backup_information).and_return(backup_information)
+ allow(subject).to receive(:upload)
+ end
+
+ context 'when BACKUP is not set' do
+ let(:tar_file) { '1546300800_2019_01_01_12.3_gitlab_backup.tar' }
+
+ it 'uses the default tar file name' do
+ subject.pack
+
+ expect(Kernel).to have_received(:system).with(*tar_cmdline)
+ end
+ end
+
+ context 'when BACKUP is set' do
+ let(:tar_file) { 'custom_gitlab_backup.tar' }
+
+ it 'uses the given value as tar file name' do
+ stub_env('BACKUP', '/ignored/path/custom')
+ subject.pack
+
+ expect(Kernel).to have_received(:system).with(*tar_cmdline)
+ end
+ end
+ end
+
describe '#remove_old' do
let(:files) do
[
@@ -238,7 +281,7 @@ describe Backup::Manager do
allow(Kernel).to receive(:system).and_return(true)
allow(YAML).to receive(:load_file).and_return(gitlab_version: Gitlab::VERSION)
- stub_env('BACKUP', '1451606400_2016_01_01_1.2.3')
+ stub_env('BACKUP', '/ignored/path/1451606400_2016_01_01_1.2.3')
end
it 'unpacks the file' do
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index cd932f502f3..b5be204d680 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -18,7 +18,7 @@ describe Banzai::Filter::VideoLinkFilter do
let(:project) { create(:project, :repository) }
context 'when the element src has a video extension' do
- UploaderHelper::VIDEO_EXT.each do |ext|
+ UploaderHelper::SAFE_VIDEO_EXT.each do |ext|
it "replaces the image tag 'path/video.#{ext}' with a video tag" do
container = filter(link_to_image("/path/video.#{ext}")).children.first
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index 399787635c0..839780b53fe 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -130,6 +130,26 @@ describe Gitlab::Diff::Position do
expect(diff_file.new_path).to eq(subject.new_path)
expect(diff_file.diff_refs).to eq(subject.diff_refs)
end
+
+ context 'different folded positions in the same diff file' do
+ def diff_file(args = {})
+ described_class
+ .new(args_for_text.merge(args))
+ .diff_file(project.repository)
+ end
+
+ it 'expands the diff file', :request_store do
+ expect_any_instance_of(Gitlab::Diff::File)
+ .to receive(:unfold_diff_lines).and_call_original
+
+ diff_file(old_line: 1, new_line: 1, diff_refs: commit.diff_refs)
+
+ expect_any_instance_of(Gitlab::Diff::File)
+ .to receive(:unfold_diff_lines).and_call_original
+
+ diff_file(old_line: 5, new_line: 5, diff_refs: commit.diff_refs)
+ end
+ end
end
describe "#diff_line" do
diff --git a/spec/lib/gitlab/file_type_detection_spec.rb b/spec/lib/gitlab/file_type_detection_spec.rb
index 22ec7d414e8..1edf882afe2 100644
--- a/spec/lib/gitlab/file_type_detection_spec.rb
+++ b/spec/lib/gitlab/file_type_detection_spec.rb
@@ -2,38 +2,103 @@
require 'spec_helper'
describe Gitlab::FileTypeDetection do
- def upload_fixture(filename)
- fixture_file_upload(File.join('spec', 'fixtures', filename))
- end
+ context 'when class is an uploader' do
+ shared_examples '#image? for an uploader' do
+ it 'returns true for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
- describe '#image_or_video?' do
- context 'when class is an uploader' do
- let(:uploader) do
- example_uploader = Class.new(CarrierWave::Uploader::Base) do
- include Gitlab::FileTypeDetection
+ expect(uploader).to be_image
+ end
- storage :file
- end
+ it 'returns false if filename has a dangerous image extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
- example_uploader.new
+ expect(uploader).to be_dangerous_image
+ expect(uploader).not_to be_image
end
- it 'returns true for an image file' do
+ it 'returns false for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).not_to be_image
+ end
+
+ it 'returns false if filename is blank' do
uploader.store!(upload_fixture('dk.png'))
- expect(uploader).to be_image_or_video
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_image
end
+ end
+ shared_examples '#video? for an uploader' do
it 'returns true for a video file' do
uploader.store!(upload_fixture('video_sample.mp4'))
- expect(uploader).to be_image_or_video
+ expect(uploader).to be_video
+ end
+
+ it 'returns false for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).not_to be_video
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_video
+ end
+ end
+
+ shared_examples '#dangerous_image? for an uploader' do
+ it 'returns true if filename has a dangerous extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
+
+ expect(uploader).to be_dangerous_image
+ end
+
+ it 'returns false for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).not_to be_dangerous_image
+ end
+
+ it 'returns false for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).not_to be_dangerous_image
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_dangerous_image
+ end
+ end
+
+ shared_examples '#dangerous_video? for an uploader' do
+ it 'returns false for a safe video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).not_to be_dangerous_video
+ end
+
+ it 'returns false if filename is a dangerous image extension' do
+ uploader.store!(upload_fixture('unsanitized.svg'))
+
+ expect(uploader).not_to be_dangerous_video
end
- it 'returns false for other extensions' do
- uploader.store!(upload_fixture('doc_sample.txt'))
+ it 'returns false for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
- expect(uploader).not_to be_image_or_video
+ expect(uploader).not_to be_dangerous_video
end
it 'returns false if filename is blank' do
@@ -41,42 +106,190 @@ describe Gitlab::FileTypeDetection do
allow(uploader).to receive(:filename).and_return(nil)
- expect(uploader).not_to be_image_or_video
+ expect(uploader).not_to be_dangerous_video
end
end
- context 'when class is a regular class' do
- let(:custom_class) do
- custom_class = Class.new do
- include Gitlab::FileTypeDetection
- end
+ let(:uploader) do
+ example_uploader = Class.new(CarrierWave::Uploader::Base) do
+ include Gitlab::FileTypeDetection
- custom_class.new
+ storage :file
end
+ example_uploader.new
+ end
+
+ def upload_fixture(filename)
+ fixture_file_upload(File.join('spec', 'fixtures', filename))
+ end
+
+ describe '#image?' do
+ include_examples '#image? for an uploader'
+ end
+
+ describe '#video?' do
+ include_examples '#video? for an uploader'
+ end
+
+ describe '#image_or_video?' do
+ include_examples '#image? for an uploader'
+ include_examples '#video? for an uploader'
+ end
+
+ describe '#dangerous_image?' do
+ include_examples '#dangerous_image? for an uploader'
+ end
+
+ describe '#dangerous_video?' do
+ include_examples '#dangerous_video? for an uploader'
+ end
+
+ describe '#dangerous_image_or_video?' do
+ include_examples '#dangerous_image? for an uploader'
+ include_examples '#dangerous_video? for an uploader'
+ end
+ end
+
+ context 'when class is a regular class' do
+ shared_examples '#image? for a regular class' do
it 'returns true for an image file' do
allow(custom_class).to receive(:filename).and_return('dk.png')
- expect(custom_class).to be_image_or_video
+ expect(custom_class).to be_image
end
+ it 'returns false if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).to be_dangerous_image
+ expect(custom_class).not_to be_image
+ end
+
+ it 'returns false for any non image file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).not_to be_image
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_image
+ end
+ end
+
+ shared_examples '#video? for a regular class' do
it 'returns true for a video file' do
allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
- expect(custom_class).to be_image_or_video
+ expect(custom_class).to be_video
+ end
+
+ it 'returns false for any non-video file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).not_to be_video
+ end
+
+ it 'returns false if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).to be_dangerous_image
+ expect(custom_class).not_to be_video
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_video
+ end
+ end
+
+ shared_examples '#dangerous_image? for a regular class' do
+ it 'returns true if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
+
+ expect(custom_class).to be_dangerous_image
+ end
+
+ it 'returns false for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).not_to be_dangerous_image
+ end
+
+ it 'returns false for any non image file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).not_to be_dangerous_image
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_dangerous_image
+ end
+ end
+
+ shared_examples '#dangerous_video? for a regular class' do
+ it 'returns false for a safe video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).not_to be_dangerous_video
+ end
+
+ it 'returns false for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).not_to be_dangerous_video
end
- it 'returns false for other extensions' do
- allow(custom_class).to receive(:filename).and_return('doc_sample.txt')
+ it 'returns false if file has a dangerous image extension' do
+ allow(custom_class).to receive(:filename).and_return('unsanitized.svg')
- expect(custom_class).not_to be_image_or_video
+ expect(custom_class).not_to be_dangerous_video
end
it 'returns false if filename is blank' do
allow(custom_class).to receive(:filename).and_return(nil)
- expect(custom_class).not_to be_image_or_video
+ expect(custom_class).not_to be_dangerous_video
end
end
+
+ let(:custom_class) do
+ custom_class = Class.new do
+ include Gitlab::FileTypeDetection
+ end
+
+ custom_class.new
+ end
+
+ describe '#image?' do
+ include_examples '#image? for a regular class'
+ end
+
+ describe '#video?' do
+ include_examples '#video? for a regular class'
+ end
+
+ describe '#image_or_video?' do
+ include_examples '#image? for a regular class'
+ include_examples '#video? for a regular class'
+ end
+
+ describe '#dangerous_image?' do
+ include_examples '#dangerous_image? for a regular class'
+ end
+
+ describe '#dangerous_video?' do
+ include_examples '#dangerous_video? for a regular class'
+ end
+
+ describe '#dangerous_image_or_video?' do
+ include_examples '#dangerous_image? for a regular class'
+ include_examples '#dangerous_video? for a regular class'
+ end
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 842dc4d511c..e09390a0047 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -64,31 +64,29 @@ describe Gitlab::UsageData do
avg_cycle_analytics
influxdb_metrics_enabled
prometheus_metrics_enabled
- cycle_analytics_views
- productivity_analytics_views
))
-
- expect(subject).to include(
- snippet_create: a_kind_of(Integer),
- snippet_update: a_kind_of(Integer),
- snippet_comment: a_kind_of(Integer),
- merge_request_comment: a_kind_of(Integer),
- merge_request_create: a_kind_of(Integer),
- commit_comment: a_kind_of(Integer),
- wiki_pages_create: a_kind_of(Integer),
- wiki_pages_update: a_kind_of(Integer),
- wiki_pages_delete: a_kind_of(Integer),
- web_ide_views: a_kind_of(Integer),
- web_ide_commits: a_kind_of(Integer),
- web_ide_merge_requests: a_kind_of(Integer),
- navbar_searches: a_kind_of(Integer),
- cycle_analytics_views: a_kind_of(Integer),
- productivity_analytics_views: a_kind_of(Integer),
- source_code_pushes: a_kind_of(Integer)
- )
end
it 'gathers usage counts' do
+ smau_keys = %i(
+ snippet_create
+ snippet_update
+ snippet_comment
+ merge_request_comment
+ merge_request_create
+ commit_comment
+ wiki_pages_create
+ wiki_pages_update
+ wiki_pages_delete
+ web_ide_views
+ web_ide_commits
+ web_ide_merge_requests
+ navbar_searches
+ cycle_analytics_views
+ productivity_analytics_views
+ source_code_pushes
+ )
+
expected_keys = %i(
assignee_lists
boards
@@ -154,12 +152,13 @@ describe Gitlab::UsageData do
uploads
web_hooks
user_preferences
- )
+ ).push(*smau_keys)
count_data = subject[:counts]
expect(count_data[:boards]).to eq(1)
expect(count_data[:projects]).to eq(4)
+ expect(count_data.values_at(*smau_keys)).to all(be_an(Integer))
expect(count_data.keys).to include(*expected_keys)
expect(expected_keys - count_data.keys).to be_empty
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 011b46c7f1a..28be8056993 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1223,36 +1223,66 @@ describe Repository do
end
describe '#branch_exists?' do
- it 'uses branch_names' do
- allow(repository).to receive(:branch_names).and_return(['foobar'])
+ let(:branch) { repository.root_ref }
- expect(repository.branch_exists?('foobar')).to eq(true)
- expect(repository.branch_exists?('master')).to eq(false)
+ subject { repository.branch_exists?(branch) }
+
+ it 'delegates to branch_names when the cache is empty' do
+ repository.expire_branches_cache
+
+ expect(repository).to receive(:branch_names).and_call_original
+ is_expected.to eq(true)
+ end
+
+ it 'uses redis set caching when the cache is filled' do
+ repository.branch_names # ensure the branch name cache is filled
+
+ expect(repository)
+ .to receive(:branch_names_include?)
+ .with(branch)
+ .and_call_original
+
+ is_expected.to eq(true)
end
end
describe '#tag_exists?' do
- it 'uses tag_names' do
- allow(repository).to receive(:tag_names).and_return(['foobar'])
+ let(:tag) { repository.tags.first.name }
+
+ subject { repository.tag_exists?(tag) }
+
+ it 'delegates to tag_names when the cache is empty' do
+ repository.expire_tags_cache
+
+ expect(repository).to receive(:tag_names).and_call_original
+ is_expected.to eq(true)
+ end
+
+ it 'uses redis set caching when the cache is filled' do
+ repository.tag_names # ensure the tag name cache is filled
+
+ expect(repository)
+ .to receive(:tag_names_include?)
+ .with(tag)
+ .and_call_original
- expect(repository.tag_exists?('foobar')).to eq(true)
- expect(repository.tag_exists?('master')).to eq(false)
+ is_expected.to eq(true)
end
end
- describe '#branch_names', :use_clean_rails_memory_store_caching do
+ describe '#branch_names', :clean_gitlab_redis_cache do
let(:fake_branch_names) { ['foobar'] }
it 'gets cached across Repository instances' do
allow(repository.raw_repository).to receive(:branch_names).once.and_return(fake_branch_names)
- expect(repository.branch_names).to eq(fake_branch_names)
+ expect(repository.branch_names).to match_array(fake_branch_names)
fresh_repository = Project.find(project.id).repository
expect(fresh_repository.object_id).not_to eq(repository.object_id)
expect(fresh_repository.raw_repository).not_to receive(:branch_names)
- expect(fresh_repository.branch_names).to eq(fake_branch_names)
+ expect(fresh_repository.branch_names).to match_array(fake_branch_names)
end
end
diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb
index 8d4e9070b19..2ac3ae0a5ad 100644
--- a/spec/models/suggestion_spec.rb
+++ b/spec/models/suggestion_spec.rb
@@ -38,16 +38,6 @@ describe Suggestion do
end
describe '#appliable?' do
- context 'when note does not support suggestions' do
- it 'returns false' do
- expect_next_instance_of(DiffNote) do |note|
- allow(note).to receive(:supports_suggestion?) { false }
- end
-
- expect(suggestion).not_to be_appliable
- end
- end
-
context 'when patch is already applied' do
let(:suggestion) { create(:suggestion, :applied) }
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index 206e898381d..0bb238d08c0 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -54,6 +54,15 @@ describe API::Releases do
expect(response).to match_response_schema('public_api/v4/releases')
end
+
+ it 'returns rendered helper paths' do
+ get api("/projects/#{project.id}/releases", maintainer)
+
+ expect(json_response.first['commit_path']).to eq("/#{release_2.project.full_path}/commit/#{release_2.commit.id}")
+ expect(json_response.first['tag_path']).to eq("/#{release_2.project.full_path}/-/tags/#{release_2.tag}")
+ expect(json_response.second['commit_path']).to eq("/#{release_1.project.full_path}/commit/#{release_1.commit.id}")
+ expect(json_response.second['tag_path']).to eq("/#{release_1.project.full_path}/-/tags/#{release_1.tag}")
+ end
end
it 'returns an upcoming_release status for a future release' do
@@ -103,11 +112,13 @@ describe API::Releases do
expect(response).to have_gitlab_http_status(:ok)
end
- it "does not expose tag, commit and source code" do
+ it "does not expose tag, commit, source code or helper paths" do
get api("/projects/#{project.id}/releases", guest)
expect(response).to match_response_schema('public_api/v4/release/releases_for_guest')
expect(json_response[0]['assets']['count']).to eq(release.links.count)
+ expect(json_response[0]['commit_path']).to be_nil
+ expect(json_response[0]['tag_path']).to be_nil
end
context 'when project is public' do
@@ -119,11 +130,13 @@ describe API::Releases do
expect(response).to have_gitlab_http_status(:ok)
end
- it "exposes tag, commit and source code" do
+ it "exposes tag, commit, source code and helper paths" do
get api("/projects/#{project.id}/releases", guest)
expect(response).to match_response_schema('public_api/v4/releases')
- expect(json_response[0]['assets']['count']).to eq(release.links.count + release.sources.count)
+ expect(json_response.first['assets']['count']).to eq(release.links.count + release.sources.count)
+ expect(json_response.first['commit_path']).to eq("/#{release.project.full_path}/commit/#{release.commit.id}")
+ expect(json_response.first['tag_path']).to eq("/#{release.project.full_path}/-/tags/#{release.tag}")
end
end
end
@@ -172,6 +185,8 @@ describe API::Releases do
expect(json_response['author']['name']).to eq(maintainer.name)
expect(json_response['commit']['id']).to eq(commit.id)
expect(json_response['assets']['count']).to eq(4)
+ expect(json_response['commit_path']).to eq("/#{release.project.full_path}/commit/#{release.commit.id}")
+ expect(json_response['tag_path']).to eq("/#{release.project.full_path}/-/tags/#{release.tag}")
end
it 'matches response schema' do
diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb
index b5defba332a..255a15b1ab0 100644
--- a/spec/support/helpers/repo_helpers.rb
+++ b/spec/support/helpers/repo_helpers.rb
@@ -92,7 +92,7 @@ eos
)
end
- def sample_compare
+ def sample_compare(extra_changes = [])
changes = [
{
line_code: 'a5cc2925ca8258af241be7e5b0381edf30266302_20_20',
@@ -102,7 +102,7 @@ eos
line_code: '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_4_6',
file_path: '.gitmodules'
}
- ]
+ ] + extra_changes
commits = %w(
5937ac0a7beb003549fc5fd26fc247adbce4a52e