Commit c792263e authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 6f9edd1a
......@@ -116,6 +116,9 @@ export default class ProjectFindFile {
html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
results.push(this.element.find('.tree-table > tbody').append(html));
}
this.element.find('.empty-state').toggleClass('hidden', Boolean(results.length));
return results;
}
......
......@@ -42,6 +42,12 @@ export default {
commit() {
return this.release.commit || {};
},
commitUrl() {
return this.release.commit_path;
},
tagUrl() {
return this.release.tag_path;
},
assets() {
return this.release.assets || {};
},
......@@ -81,12 +87,18 @@ export default {
<div class="card-subtitle d-flex flex-wrap text-secondary">
<div class="append-right-8">
<icon name="commit" class="align-middle" />
<span v-gl-tooltip.bottom :title="commit.title">{{ commit.short_id }}</span>
<gl-link v-if="commitUrl" v-gl-tooltip.bottom :title="commit.title" :href="commitUrl">
{{ commit.short_id }}
</gl-link>
<span v-else v-gl-tooltip.bottom :title="commit.title">{{ commit.short_id }}</span>
</div>
<div class="append-right-8">
<icon name="tag" class="align-middle" />
<span v-gl-tooltip.bottom :title="__('Tag')">{{ release.tag_name }}</span>
<gl-link v-if="tagUrl" v-gl-tooltip.bottom :title="__('Tag')" :href="tagUrl">
{{ release.tag_name }}
</gl-link>
<span v-else v-gl-tooltip.bottom :title="__('Tag')">{{ release.tag_name }}</span>
</div>
<milestone-list
......
......@@ -176,7 +176,7 @@ class Blob < SimpleDelegator
end
def video?
UploaderHelper::VIDEO_EXT.include?(extension)
UploaderHelper::SAFE_VIDEO_EXT.include?(extension)
end
def readable_text?
......
......@@ -6,7 +6,7 @@ module BlobViewer
include ClientSide
self.partial_name = 'image'
self.extensions = UploaderHelper::IMAGE_EXT
self.extensions = UploaderHelper::SAFE_IMAGE_EXT
self.binary = true
self.switcher_icon = 'picture-o'
self.switcher_title = 'image'
......
......@@ -6,7 +6,7 @@ module BlobViewer
include ClientSide
self.partial_name = 'video'
self.extensions = UploaderHelper::VIDEO_EXT
self.extensions = UploaderHelper::SAFE_VIDEO_EXT
self.binary = true
self.switcher_icon = 'film'
self.switcher_title = 'video'
......
......@@ -38,7 +38,7 @@ module Avatarable
def avatar_type
unless self.avatar.image?
errors.add :avatar, "file format is not supported. Please try one of the following supported formats: #{AvatarUploader::IMAGE_EXT.join(', ')}"
errors.add :avatar, "file format is not supported. Please try one of the following supported formats: #{AvatarUploader::SAFE_IMAGE_EXT.join(', ')}"
end
end
......
......@@ -75,6 +75,10 @@ class DiffNote < Note
self.original_position.diff_refs == diff_refs
end
# Checks if the current `position` line in the diff
# exists and is suggestible (not a deletion).
#
# Avoid using in iterations as it requests Gitaly.
def supports_suggestion?
return false unless noteable&.supports_suggestion? && on_text?
# We don't want to trigger side-effects of `diff_file` call.
......
......@@ -6,7 +6,7 @@ module DiffViewer
include ClientSide
self.partial_name = 'image'
self.extensions = UploaderHelper::IMAGE_EXT
self.extensions = UploaderHelper::SAFE_IMAGE_EXT
self.binary = true
self.switcher_icon = 'picture-o'
self.switcher_title = _('image diff')
......
......@@ -249,13 +249,13 @@ class Repository
def branch_exists?(branch_name)
return false unless raw_repository
branch_names.include?(branch_name)
branch_names_include?(branch_name)
end
def tag_exists?(tag_name)
return false unless raw_repository
tag_names.include?(tag_name)
tag_names_include?(tag_name)
end
def ref_exists?(ref)
......@@ -559,10 +559,10 @@ class Repository
end
delegate :branch_names, to: :raw_repository
cache_method :branch_names, fallback: []
cache_method_as_redis_set :branch_names, fallback: []
delegate :tag_names, to: :raw_repository
cache_method :tag_names, fallback: []
cache_method_as_redis_set :tag_names, fallback: []
delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
cache_method :branch_count, fallback: 0
......
......@@ -41,7 +41,6 @@ class Suggestion < ApplicationRecord
!applied? &&
noteable.opened? &&
!outdated?(cached: cached) &&
note.supports_suggestion? &&
different_content? &&
note.active?
end
......
......@@ -15,4 +15,12 @@
.table-holder
%table.table.files-slider{ class: "table_#{@hex_path} tree-table" }
%tbody
.col-12.empty-state.hidden
.svg-250.svg-content
= image_tag('illustrations/profile-page/personal-projects.svg', alt: 'No files svg', lazy: true)
.text-center
%h4
= _('There are no matching files')
%p.text-secondary
= _('Try using a different search term to find the file you are looking for.')
= spinner nil, true
---
title: Links on Releases page to commits and tags
merge_request: 16128
author:
type: changed
---
title: Add empty state in file search
merge_request: 16851
author:
type: changed
---
title: Cache branch and tag names as Redis sets
merge_request: 30476
author:
type: performance
---
title: Move SMAU usage counters to the UsageData count field
merge_request: 17074
author:
type: fixed
---
title: Adjust unnapliable suggestions in expanded lines
merge_request: 17286
author:
type: fixed
{
"ignored_warnings": [
{
"warning_type": "Cross-Site Request Forgery",
"warning_code": 7,
"fingerprint": "dc562678129557cdb8b187217da304044547a3605f05fe678093dcb4b4d8bbe4",
"message": "'protect_from_forgery' should be called in Oauth::GeoAuthController",
"file": "app/controllers/oauth/geo_auth_controller.rb",
"line": 1,
"link": "http://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/",
"code": null,
"render_path": null,
"location": {
"type": "controller",
"controller": "Oauth::GeoAuthController"
},
"user_input": null,
"confidence": "High",
"note": ""
}
],
"updated": "2017-01-20 02:06:54 +0000",
"brakeman_version": "3.4.1"
}
......@@ -117,6 +117,35 @@ on adding these events into GitLab:
- [Group settings and activity](https://gitlab.com/groups/gitlab-org/-/epics/475)
- [Instance-level settings and activity](https://gitlab.com/groups/gitlab-org/-/epics/476)
### Disabled events
#### Repository push
The current architecture of audit events is not prepared to receive a very high amount of records.
It may make your project/admin audit logs UI very busy and the disk space consumed by the
`audit_events` Postgres table will increase considerably. Thus, it's disabled by default
to prevent performance degradations on GitLab instances with very high Git write traffic.
In an upcoming release, Audit Logs for Git push events will be enabled
by default. Follow [#7865](https://gitlab.com/gitlab-org/gitlab/issues/7865) for updates.
If you still wish to enable **Repository push** events in your instance, follow
the steps bellow.
**In Omnibus installations:**
1. Enter the Rails console:
```sh
sudo gitlab-rails console
```
1. Flip the switch and enable the feature flag:
```ruby
Feature.enable(:repository_push_audit_event)
```
[ee-2336]: https://gitlab.com/gitlab-org/gitlab/issues/2336
[ee]: https://about.gitlab.com/pricing/
[permissions]: ../user/permissions.md
......@@ -85,6 +85,8 @@ Example response:
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/2"
}
],
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
"tag_path":"/root/awesome-app/-/tags/v0.11.1",
"assets":{
"count":6,
"sources":[
......@@ -261,6 +263,8 @@ Example response:
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/2"
}
],
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
"tag_path":"/root/awesome-app/-/tags/v0.11.1",
"assets":{
"count":4,
"sources":[
......@@ -379,6 +383,8 @@ Example response:
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/2"
}
],
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
"tag_path":"/root/awesome-app/-/tags/v0.11.1",
"assets":{
"count":5,
"sources":[
......@@ -483,6 +489,8 @@ Example response:
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/3"
}
],
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
"tag_path":"/root/awesome-app/-/tags/v0.11.1",
"assets":{
"count":4,
"sources":[
......@@ -563,6 +571,8 @@ Example response:
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:53:28.000Z"
},
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
"tag_path":"/root/awesome-app/-/tags/v0.11.1",
"assets":{
"count":4,
"sources":[
......
......@@ -375,6 +375,7 @@ timestamps with timezones:
- `add_timestamps_with_timezone`
- `timestamps_with_timezone`
- `datetime_with_timezone`
This ensures all timestamps have a time zone specified. This, in turn, means
existing timestamps won't suddenly use a different timezone when the system's
......
......@@ -87,7 +87,7 @@ $ cat -- -l
hello
```
In the GitLab codebase, we avoid the option/argument ambiguity by _always_ using `--`.
In the GitLab codebase, we avoid the option/argument ambiguity by _always_ using `--` for commands that support it.
```ruby
# Wrong
......
......@@ -64,8 +64,8 @@ The following quick actions are applicable to descriptions, discussions and thre
| `/create_merge_request <branch name>` | ✓ | | | Create a new merge request starting from the current issue |
| `/relate #issue1 #issue2` | ✓ | | | Mark issues as related **(STARTER)** |
| `/move <path/to/project>` | ✓ | | | Move this issue to another project |
| `/zoom <Zoom URL>` | ✓ | | | Add Zoom meeting to this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/merge_requests/16609) enabled by feature flag `issue_zoom_integration`) |
| `/remove_zoom` | ✓ | | | Remove Zoom meeting from this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/merge_requests/16609) enabled by feature flag `issue_zoom_integration`) |
| `/zoom <Zoom URL>` | ✓ | | | Add Zoom meeting to this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/merge_requests/16609). Must be enabled by feature flag `issue_zoom_integration` for self-hosted. Feature flag to be removed and available by default in 12.4.) |
| `/remove_zoom` | ✓ | | | Remove Zoom meeting from this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/merge_requests/16609). Must be enabled by feature flag `issue_zoom_integration` for self-hosted. Feature flag to be removed and available by default in 12.4.) |
| `/target_branch <local branch name>` | | ✓ | | Set target branch |
| `/wip` | | ✓ | | Toggle the Work In Progress status |
| `/approve` | | ✓ | | Approve the merge request |
......
......@@ -1276,7 +1276,7 @@ module API
class Release < Grape::Entity
expose :name
expose :tag, as: :tag_name, if: lambda { |_, _| can_download_code? }
expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? }
expose :description
expose :description_html do |entity|
MarkupHelper.markdown_field(entity, :description)
......@@ -1284,16 +1284,17 @@ module API
expose :created_at
expose :released_at
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? }
expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? }
expose :upcoming_release?, as: :upcoming_release
expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? }
expose :commit_path, if: ->(_, _) { can_download_code? }
expose :tag_path, if: ->(_, _) { can_download_code? }
expose :assets do
expose :assets_count, as: :count do |release, _|
assets_to_exclude = can_download_code? ? [] : [:sources]
release.assets_count(except: assets_to_exclude)
end
expose :sources, using: Entities::Releases::Source, if: lambda { |_, _| can_download_code? }
expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_download_code? }
expose :links, using: Entities::Releases::Link do |release, options|
release.links.sorted
end
......@@ -1304,6 +1305,16 @@ module API
def can_download_code?
Ability.allowed?(options[:current_user], :download_code, object.project)
end
def commit_path
return unless object.commit
Gitlab::Routing.url_helpers.project_commit_path(object.project, object.commit.id)
end
def tag_path
Gitlab::Routing.url_helpers.project_tag_path(object.project, object.tag)
end
end
class Tag < Grape::Entity
......
......@@ -127,7 +127,7 @@ module Backup
end
tar_file = if ENV['BACKUP'].present?
"#{ENV['BACKUP']}#{FILE_NAME_SUFFIX}"
File.basename(ENV['BACKUP']) + FILE_NAME_SUFFIX
else
backup_file_list.first
end
......@@ -235,8 +235,8 @@ module Backup
end
def tar_file
@tar_file ||= if ENV['BACKUP']
ENV['BACKUP'] + "#{FILE_NAME_SUFFIX}"
@tar_file ||= if ENV['BACKUP'].present?
File.basename(ENV['BACKUP']) + FILE_NAME_SUFFIX
else
"#{backup_information[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{backup_information[:gitlab_version]}#{FILE_NAME_SUFFIX}"
end
......
......@@ -19,13 +19,13 @@ module Banzai
def query
@query ||= begin
src_query = UploaderHelper::VIDEO_EXT.map do |ext|
src_query = UploaderHelper::SAFE_VIDEO_EXT.map do |ext|
"'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
end
if context[:asset_proxy_enabled].present?
src_query.concat(
UploaderHelper::VIDEO_EXT.map do |ext|
UploaderHelper::SAFE_VIDEO_EXT.map do |ext|
"'.#{ext}' = substring(@data-canonical-src, string-length(@data-canonical-src) - #{ext.size})"
end
)
......
......@@ -118,8 +118,14 @@ module Gitlab
path: file_path
}
# Takes action when creating diff notes (multiple calls are
# submitted to this method).
Gitlab::SafeRequestStore.fetch(key) { find_diff_file(repository) }
end
# We need to unfold diff lines according to the position in order
# to correctly calculate the line code and trace position changes.
@diff_file&.tap { |file| file.unfold_diff_lines(self) }
end
def diff_options
......@@ -152,13 +158,7 @@ module Gitlab
return unless diff_refs.complete?
return unless comparison = diff_refs.compare_in(repository.project)
file = comparison.diffs(diff_options).diff_files.first
# We need to unfold diff lines according to the position in order
# to correctly calculate the line code and trace position changes.
file&.unfold_diff_lines(self)
file
comparison.diffs(diff_options).diff_files.first
end
def get_formatter_class(type)
......
......@@ -10,7 +10,7 @@ module Gitlab
return unless name = markdown_name
markdown = "[#{name.gsub(']', '\\]')}](#{secure_url})"
markdown = "!#{markdown}" if image_or_video? || dangerous?
markdown = "!#{markdown}" if image_or_video? || dangerous_image_or_video?
markdown
end
......
# frozen_string_literal: true
# File helpers methods.
# It needs the method filename to be defined.
# The method `filename` must be defined in classes that use this module.
#
# This module is intended to be used as a helper and not a security gate
# to validate that a file is safe, as it identifies files only by the
# file extension and not its actual contents.
#
# An example useage of this module is in `FileMarkdownLinkBuilder` that
# renders markdown depending on a file name.
#
# We use Workhorse to detect the real extension when we serve files with
# the `SendsBlob` helper methods, and ask Workhorse to set the content
# type when it serves the file:
# https://gitlab.com/gitlab-org/gitlab-ce/blob/33e5955/app/helpers/workhorse_helper.rb#L48.
#
# Because Workhorse has access to the content when it is downloaded, if
# the type/extension doesn't match the real type, we adjust the
# `Content-Type` and `Content-Disposition` to the one we get from the detection.
module Gitlab
module FileTypeDetection
IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
SAFE_IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
# We recommend using the .mp4 format over .mov. Videos in .mov format can
# still be used but you really need to make sure they are served with the
# proper MIME type video/mp4 and not video/quicktime or your videos won't play
# on IE >= 9.
# http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html
VIDEO_EXT = %w[mp4 m4v mov webm ogv].freeze
SAFE_VIDEO_EXT = %w[mp4 m4v mov webm ogv].freeze
# These extension types can contain dangerous code and should only be embedded inline with
# proper filtering. They should always be tagged as "Content-Disposition: attachment", not "inline".
DANGEROUS_EXT = %w[svg].freeze
DANGEROUS_IMAGE_EXT = %w[svg].freeze
DANGEROUS_VIDEO_EXT = [].freeze # None, yet
def image?
extension_match?(IMAGE_EXT)
extension_match?(SAFE_IMAGE_EXT)
end
def video?
extension_match?(VIDEO_EXT)
extension_match?(SAFE_VIDEO_EXT)
end
def image_or_video?
image? || video?
end
def dangerous?
extension_match?(DANGEROUS_EXT)
def dangerous_image?
extension_match?(DANGEROUS_IMAGE_EXT)
end
def dangerous_video?
extension_match?(DANGEROUS_VIDEO_EXT)
end
def dangerous_image_or_video?
dangerous_image? || dangerous_video?
end
private
......
......@@ -17,7 +17,6 @@ module Gitlab
.merge(features_usage_data)
.merge(components_usage_data)
.merge(cycle_analytics_usage_data)
.merge(usage_counters)
end
def to_json(force_refresh: false)
......@@ -99,6 +98,7 @@ module Gitlab
web_hooks: count(WebHook)
}.merge(services_usage)
.merge(approximate_counts)
.merge(usage_counters)
}.tap do |data|
data[:counts][:user_preferences] = user_preferences_usage
end
......
......@@ -15647,6 +15647,9 @@ msgstr ""
msgid "There are no labels yet"
msgstr ""
msgid "There are no matching files"
msgstr ""
msgid "There are no open issues"
msgstr ""
......@@ -16569,6 +16572,9 @@ msgstr ""
msgid "Try to fork again"
msgstr ""
msgid "Try using a different search term to find the file you are looking for."
msgstr ""
msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
msgstr ""
......
......@@ -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]}']"))
......
......@@ -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": {
......
......@@ -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" }]
},
......
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);
});
});
});
});
......@@ -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', () => {
......
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();
});
});
});
});
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');
});
});
});
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......@@ -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
......
......@@ -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
......
......@@ -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) }
......
......@@ -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
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment