Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-04-22 12:09:29 +00:00
parent af5fb16f2a
commit ae96e65ee2
77 changed files with 1441 additions and 1374 deletions

View File

@ -475,7 +475,6 @@ Style/MixinUsage:
Style/MultilineIfModifier:
Exclude:
- 'app/helpers/snippets_helper.rb'
- 'app/models/project_wiki.rb'
- 'app/services/ci/process_pipeline_service.rb'
- 'lib/api/commit_statuses.rb'

View File

@ -39,7 +39,11 @@ export const diffCompareDropdownTargetVersions = (state, getters) => {
...v,
};
};
return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion, headVersion];
if (gon.features?.diffCompareWithHead) {
return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion, headVersion];
}
return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion];
};
export const diffCompareDropdownSourceVersions = (state, getters) => {

View File

@ -34,10 +34,21 @@ export default {
required: false,
default: '',
},
defaultAwards: {
type: Array,
required: false,
default: () => [],
},
},
computed: {
groupedDefaultAwards() {
return this.defaultAwards.reduce((obj, key) => Object.assign(obj, { [key]: [] }), {});
},
groupedAwards() {
const { thumbsup, thumbsdown, ...rest } = groupBy(this.awards, x => x.name);
const { thumbsup, thumbsdown, ...rest } = {
...this.groupedDefaultAwards,
...groupBy(this.awards, x => x.name),
};
return [
...(thumbsup ? [this.createAwardList('thumbsup', thumbsup)] : []),
@ -73,6 +84,10 @@ export default {
};
},
getAwardListTitle(awardsList) {
if (!awardsList.length) {
return '';
}
const hasReactionByCurrentUser = this.hasReactionByCurrentUser(awardsList);
const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10;
let awardList = awardsList;

View File

@ -307,7 +307,7 @@
}
.label-name {
width: 150px;
width: 200px;
flex-shrink: 0;
.scoped-label-wrapper,

View File

@ -26,6 +26,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:code_navigation, @project)
push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true)
push_frontend_feature_flag(:merge_ref_head_comments, @project)
push_frontend_feature_flag(:diff_compare_with_head, @project)
end
before_action do

View File

@ -129,7 +129,7 @@ class Blob < SimpleDelegator
def external_storage_error?
if external_storage == :lfs
!project&.lfs_enabled?
!repository.lfs_enabled?
else
false
end

View File

@ -9,7 +9,6 @@
# needs any special behavior.
module HasRepository
extend ActiveSupport::Concern
include AfterCommitQueue
include Referable
include Gitlab::ShellAdapter
include Gitlab::Utils::StrongMemoize

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
module HasWiki
extend ActiveSupport::Concern
included do
validate :check_wiki_path_conflict
end
def create_wiki
wiki.wiki
true
rescue Wiki::CouldNotCreateWikiError
errors.add(:base, _('Failed to create wiki'))
false
end
def wiki
strong_memoize(:wiki) do
Wiki.for_container(self, self.owner)
end
end
def wiki_repository_exists?
wiki.repository_exists?
end
def after_wiki_activity
true
end
private
def check_wiki_path_conflict
return if path.blank?
path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki"
if Project.in_namespace(parent_id).where(path: path_to_check).exists? ||
GroupsFinder.new(nil, parent: parent_id).execute.where(path: path_to_check).exists?
errors.add(:name, _('has already been taken'))
end
end
end

View File

@ -26,7 +26,7 @@ module RedisCacheable
end
def cache_attributes(values)
Gitlab::Redis::SharedState.with do |redis|
Gitlab::Redis::Cache.with do |redis|
redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME)
end
@ -41,7 +41,7 @@ module RedisCacheable
def cached_attributes
strong_memoize(:cached_attributes) do
Gitlab::Redis::SharedState.with do |redis|
Gitlab::Redis::Cache.with do |redis|
data = redis.get(cache_attribute_key)
JSON.parse(data, symbolize_names: true) if data
end

View File

@ -1,11 +0,0 @@
# frozen_string_literal: true
module Storage
module LegacyProjectWiki
extend ActiveSupport::Concern
def disk_path
project.disk_path + '.wiki'
end
end
end

View File

@ -15,6 +15,7 @@ class Group < Namespace
include WithUploads
include Gitlab::Utils::StrongMemoize
include GroupAPICompatibility
include HasWiki
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10

27
app/models/group_wiki.rb Normal file
View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class GroupWiki < Wiki
alias_method :group, :container
override :storage
def storage
@storage ||= Storage::Hashed.new(container, prefix: Storage::Hashed::GROUP_REPOSITORY_PATH_PREFIX)
end
override :repository_storage
def repository_storage
# TODO: Add table to track storage
# https://gitlab.com/gitlab-org/gitlab/-/issues/207865
'default'
end
override :hashed_storage?
def hashed_storage?
true
end
override :disk_path
def disk_path(*args, &block)
storage.disk_path + '.wiki'
end
end

View File

@ -2,4 +2,8 @@
class PersonalSnippet < Snippet
include WithUploads
def skip_project_check?
true
end
end

View File

@ -3,6 +3,7 @@
require 'carrierwave/orm/activerecord'
class Project < ApplicationRecord
extend ::Gitlab::Utils::Override
include Gitlab::ConfigHelper
include Gitlab::VisibilityLevel
include AccessRequestable
@ -18,6 +19,7 @@ class Project < ApplicationRecord
include SelectForProjectAuthorization
include Presentable
include HasRepository
include HasWiki
include Routable
include GroupDescendant
include Gitlab::SQL::Pattern
@ -386,7 +388,6 @@ class Project < ApplicationRecord
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
validate :visibility_level_allowed_by_group, if: :should_validate_visibility_level?
validate :visibility_level_allowed_as_fork, if: :should_validate_visibility_level?
validate :check_wiki_path_conflict
validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) }
validates :repository_storage,
presence: true,
@ -1056,16 +1057,6 @@ class Project < ApplicationRecord
self.errors.add(:visibility_level, _("%{level_name} is not allowed since the fork source project has lower visibility.") % { level_name: level_name })
end
def check_wiki_path_conflict
return if path.blank?
path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki"
if Project.where(namespace_id: namespace_id, path: path_to_check).exists?
errors.add(:name, _('has already been taken'))
end
end
def pages_https_only
return false unless Gitlab.config.pages.external_https
@ -1557,10 +1548,6 @@ class Project < ApplicationRecord
create_repository(force: true) unless repository_exists?
end
def wiki_repository_exists?
wiki.repository_exists?
end
# update visibility_level of forks
def update_forks_visibility_level
return if unlink_forks_upon_visibility_decrease_enabled?
@ -1574,20 +1561,6 @@ class Project < ApplicationRecord
end
end
def create_wiki
ProjectWiki.new(self, self.owner).wiki
true
rescue ProjectWiki::CouldNotCreateWikiError
errors.add(:base, _('Failed create wiki'))
false
end
def wiki
strong_memoize(:wiki) do
ProjectWiki.new(self, self.owner)
end
end
def allowed_to_share_with_group?
!namespace.share_with_group_lock
end
@ -2417,6 +2390,11 @@ class Project < ApplicationRecord
jira_imports.last
end
override :after_wiki_activity
def after_wiki_activity
touch(:last_activity_at, :last_repository_updated_at)
end
private
def find_service(services, name)

View File

@ -1,219 +1,17 @@
# frozen_string_literal: true
class ProjectWiki
include Storage::LegacyProjectWiki
include Gitlab::Utils::StrongMemoize
class ProjectWiki < Wiki
alias_method :project, :container
MARKUPS = {
'Markdown' => :markdown,
'RDoc' => :rdoc,
'AsciiDoc' => :asciidoc,
'Org' => :org
}.freeze unless defined?(MARKUPS)
# Project wikis are tied to the main project storage
delegate :storage, :repository_storage, :hashed_storage?, to: :container
CouldNotCreateWikiError = Class.new(StandardError)
SIDEBAR = '_sidebar'
TITLE_ORDER = 'title'
CREATED_AT_ORDER = 'created_at'
DIRECTION_DESC = 'desc'
DIRECTION_ASC = 'asc'
attr_reader :project, :user
# Returns a string describing what went wrong after
# an operation fails.
attr_reader :error_message
def initialize(project, user = nil)
@project = project
@user = user
end
delegate :repository_storage, :hashed_storage?, to: :project
def path
@project.path + '.wiki'
end
def full_path
@project.full_path + '.wiki'
end
alias_method :id, :full_path
# @deprecated use full_path when you need it for an URL route or disk_path when you want to point to the filesystem
alias_method :path_with_namespace, :full_path
def web_url(only_path: nil)
Gitlab::UrlBuilder.build(self, only_path: only_path)
end
def url_to_repo
ssh_url_to_repo
end
def ssh_url_to_repo
Gitlab::RepositoryUrlBuilder.build(repository.full_path, protocol: :ssh)
end
def http_url_to_repo
Gitlab::RepositoryUrlBuilder.build(repository.full_path, protocol: :http)
end
def wiki_base_path
[Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/-', '/wikis'].join('')
end
# Returns the Gitlab::Git::Wiki object.
def wiki
strong_memoize(:wiki) do
repository.create_if_not_exists
raise CouldNotCreateWikiError unless repository_exists?
Gitlab::Git::Wiki.new(repository.raw)
end
rescue => err
Gitlab::ErrorTracking.track_exception(err, project_wiki: { project_id: project.id, full_path: full_path, disk_path: disk_path })
raise CouldNotCreateWikiError
end
def repository_exists?
!!repository.exists?
end
def has_home_page?
!!find_page('home')
end
def empty?
list_pages(limit: 1).empty?
end
def exists?
!empty?
end
# Lists wiki pages of the repository.
#
# limit - max number of pages returned by the method.
# sort - criterion by which the pages are sorted.
# direction - order of the sorted pages.
# load_content - option, which specifies whether the content inside the page
# will be loaded.
#
# Returns an Array of GitLab WikiPage instances or an
# empty Array if this Wiki has no pages.
def list_pages(limit: 0, sort: nil, direction: DIRECTION_ASC, load_content: false)
wiki.list_pages(
limit: limit,
sort: sort,
direction_desc: direction == DIRECTION_DESC,
load_content: load_content
).map do |page|
WikiPage.new(self, page)
end
end
# Finds a page within the repository based on a tile
# or slug.
#
# title - The human readable or parameterized title of
# the page.
#
# Returns an initialized WikiPage instance or nil
def find_page(title, version = nil)
page_title, page_dir = page_title_and_dir(title)
if page = wiki.page(title: page_title, version: version, dir: page_dir)
WikiPage.new(self, page)
end
end
def find_sidebar(version = nil)
find_page(SIDEBAR, version)
end
def find_file(name, version = nil)
wiki.file(name, version)
end
def create_page(title, content, format = :markdown, message = nil)
commit = commit_details(:created, message, title)
wiki.write_page(title, format.to_sym, content, commit)
update_project_activity
rescue Gitlab::Git::Wiki::DuplicatePageError => e
@error_message = "Duplicate page: #{e.message}"
false
end
def update_page(page, content:, title: nil, format: :markdown, message: nil)
commit = commit_details(:updated, message, page.title)
wiki.update_page(page.path, title || page.name, format.to_sym, content, commit)
update_project_activity
end
def delete_page(page, message = nil)
return unless page
wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
update_project_activity
end
def page_title_and_dir(title)
return unless title
title_array = title.split("/")
title = title_array.pop
[title, title_array.join("/")]
end
def repository
@repository ||= Repository.new(full_path, @project, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI)
end
def default_branch
wiki.class.default_ref
end
def ensure_repository
raise CouldNotCreateWikiError unless wiki.repository_exists?
end
def hook_attrs
{
web_url: web_url,
git_ssh_url: ssh_url_to_repo,
git_http_url: http_url_to_repo,
path_with_namespace: full_path,
default_branch: default_branch
}
end
private
def commit_details(action, message = nil, title = nil)
commit_message = message.presence || default_message(action, title)
git_user = Gitlab::Git::User.from_gitlab(user)
Gitlab::Git::Wiki::CommitDetails.new(user.id,
git_user.username,
git_user.name,
git_user.email,
commit_message)
end
def default_message(action, title)
"#{user.username} #{action} page: #{title}"
end
def update_project_activity
@project.touch(:last_activity_at, :last_repository_updated_at)
override :disk_path
def disk_path(*args, &block)
container.disk_path + '.wiki'
end
end
# TODO: Remove this once we implement ES support for group wikis.
# https://gitlab.com/gitlab-org/gitlab/-/issues/207889
ProjectWiki.prepend_if_ee('EE::ProjectWiki')

View File

@ -1120,6 +1120,17 @@ class Repository
end
end
# TODO: pass this in directly to `Blob` rather than delegating it to here
#
# https://gitlab.com/gitlab-org/gitlab/-/issues/201886
def lfs_enabled?
if container.is_a?(Project)
container.lfs_enabled?
else
false # LFS is not supported for snippet or group repositories
end
end
private
# TODO Genericize finder, later split this on finders by Ref or Oid

View File

@ -15,6 +15,7 @@ class Snippet < ApplicationRecord
include FromUnion
include IgnorableColumns
include HasRepository
include AfterCommitQueue
extend ::Gitlab::Utils::Override
MAX_FILE_COUNT = 1

View File

@ -6,6 +6,7 @@ module Storage
delegate :gitlab_shell, :repository_storage, to: :container
REPOSITORY_PATH_PREFIX = '@hashed'
GROUP_REPOSITORY_PATH_PREFIX = '@groups'
SNIPPET_REPOSITORY_PATH_PREFIX = '@snippets'
POOL_PATH_PREFIX = '@pools'

221
app/models/wiki.rb Normal file
View File

@ -0,0 +1,221 @@
# frozen_string_literal: true
class Wiki
extend ::Gitlab::Utils::Override
include HasRepository
include Gitlab::Utils::StrongMemoize
MARKUPS = { # rubocop:disable Style/MultilineIfModifier
'Markdown' => :markdown,
'RDoc' => :rdoc,
'AsciiDoc' => :asciidoc,
'Org' => :org
}.freeze unless defined?(MARKUPS)
CouldNotCreateWikiError = Class.new(StandardError)
HOMEPAGE = 'home'
SIDEBAR = '_sidebar'
TITLE_ORDER = 'title'
CREATED_AT_ORDER = 'created_at'
DIRECTION_DESC = 'desc'
DIRECTION_ASC = 'asc'
attr_reader :container, :user
# Returns a string describing what went wrong after
# an operation fails.
attr_reader :error_message
def self.for_container(container, user = nil)
"#{container.class.name}Wiki".constantize.new(container, user)
end
def initialize(container, user = nil)
@container = container
@user = user
end
def path
container.path + '.wiki'
end
# Returns the Gitlab::Git::Wiki object.
def wiki
strong_memoize(:wiki) do
repository.create_if_not_exists
raise CouldNotCreateWikiError unless repository_exists?
Gitlab::Git::Wiki.new(repository.raw)
end
rescue => err
Gitlab::ErrorTracking.track_exception(err, wiki: {
container_type: container.class.name,
container_id: container.id,
full_path: full_path,
disk_path: disk_path
})
raise CouldNotCreateWikiError
end
def has_home_page?
!!find_page(HOMEPAGE)
end
def empty?
list_pages(limit: 1).empty?
end
def exists?
!empty?
end
# Lists wiki pages of the repository.
#
# limit - max number of pages returned by the method.
# sort - criterion by which the pages are sorted.
# direction - order of the sorted pages.
# load_content - option, which specifies whether the content inside the page
# will be loaded.
#
# Returns an Array of GitLab WikiPage instances or an
# empty Array if this Wiki has no pages.
def list_pages(limit: 0, sort: nil, direction: DIRECTION_ASC, load_content: false)
wiki.list_pages(
limit: limit,
sort: sort,
direction_desc: direction == DIRECTION_DESC,
load_content: load_content
).map do |page|
WikiPage.new(self, page)
end
end
# Finds a page within the repository based on a tile
# or slug.
#
# title - The human readable or parameterized title of
# the page.
#
# Returns an initialized WikiPage instance or nil
def find_page(title, version = nil)
page_title, page_dir = page_title_and_dir(title)
if page = wiki.page(title: page_title, version: version, dir: page_dir)
WikiPage.new(self, page)
end
end
def find_sidebar(version = nil)
find_page(SIDEBAR, version)
end
def find_file(name, version = nil)
wiki.file(name, version)
end
def create_page(title, content, format = :markdown, message = nil)
commit = commit_details(:created, message, title)
wiki.write_page(title, format.to_sym, content, commit)
update_container_activity
rescue Gitlab::Git::Wiki::DuplicatePageError => e
@error_message = "Duplicate page: #{e.message}"
false
end
def update_page(page, content:, title: nil, format: :markdown, message: nil)
commit = commit_details(:updated, message, page.title)
wiki.update_page(page.path, title || page.name, format.to_sym, content, commit)
update_container_activity
end
def delete_page(page, message = nil)
return unless page
wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
update_container_activity
end
def page_title_and_dir(title)
return unless title
title_array = title.split("/")
title = title_array.pop
[title, title_array.join("/")]
end
def ensure_repository
raise CouldNotCreateWikiError unless wiki.repository_exists?
end
def hook_attrs
{
web_url: web_url,
git_ssh_url: ssh_url_to_repo,
git_http_url: http_url_to_repo,
path_with_namespace: full_path,
default_branch: default_branch
}
end
override :repository
def repository
@repository ||= Repository.new(full_path, container, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI)
end
def repository_storage
raise NotImplementedError
end
def hashed_storage?
raise NotImplementedError
end
override :full_path
def full_path
container.full_path + '.wiki'
end
alias_method :id, :full_path
# @deprecated use full_path when you need it for an URL route or disk_path when you want to point to the filesystem
alias_method :path_with_namespace, :full_path
override :default_branch
def default_branch
wiki.class.default_ref
end
def wiki_base_path
Gitlab.config.gitlab.relative_url_root + web_url(only_path: true).sub(%r{/#{Wiki::HOMEPAGE}\z}, '')
end
private
def commit_details(action, message = nil, title = nil)
commit_message = message.presence || default_message(action, title)
git_user = Gitlab::Git::User.from_gitlab(user)
Gitlab::Git::Wiki::CommitDetails.new(user.id,
git_user.username,
git_user.name,
git_user.email,
commit_message)
end
def default_message(action, title)
"#{user.username} #{action} page: #{title}"
end
def update_container_activity
container.after_wiki_activity
end
end
Wiki.prepend_if_ee('EE::Wiki')

View File

@ -26,7 +26,7 @@ class WikiPage
def eql?(other)
return false unless other.present? && other.is_a?(self.class)
slug == other.slug && wiki.project == other.wiki.project
slug == other.slug && wiki.container == other.wiki.container
end
alias_method :==, :eql?
@ -66,9 +66,9 @@ class WikiPage
validates :content, presence: true
validate :validate_path_limits, if: :title_changed?
# The GitLab ProjectWiki instance.
# The GitLab Wiki instance.
attr_reader :wiki
delegate :project, to: :wiki
delegate :container, to: :wiki
# The raw Gitlab::Git::WikiPage instance.
attr_reader :page
@ -83,7 +83,7 @@ class WikiPage
# Construct a new WikiPage
#
# @param [ProjectWiki] wiki
# @param [Wiki] wiki
# @param [Gitlab::Git::WikiPage] page
def initialize(wiki, page = nil)
@wiki = wiki
@ -195,7 +195,7 @@ class WikiPage
# :content - The raw markup content.
# :format - Optional symbol representing the
# content format. Can be any type
# listed in the ProjectWiki::MARKUPS
# listed in the Wiki::MARKUPS
# Hash.
# :message - Optional commit message to set on
# the new page.
@ -215,7 +215,7 @@ class WikiPage
# attrs - Hash of attributes to be updated on the page.
# :content - The raw markup content to replace the existing.
# :format - Optional symbol representing the content format.
# See ProjectWiki::MARKUPS Hash for available formats.
# See Wiki::MARKUPS Hash for available formats.
# :message - Optional commit message to set on the new version.
# :last_commit_sha - Optional last commit sha to validate the page unchanged.
# :title - The Title (optionally including dir) to replace existing title
@ -261,6 +261,7 @@ class WikiPage
# Relative path to the partial to be used when rendering collections
# of this object.
def to_partial_path
# TODO: Move into shared/ with https://gitlab.com/gitlab-org/gitlab/-/issues/196054
'projects/wikis/wiki_page'
end
@ -303,7 +304,7 @@ class WikiPage
end
def update_front_matter(attrs)
return unless Gitlab::WikiPages::FrontMatterParser.enabled?(project)
return unless Gitlab::WikiPages::FrontMatterParser.enabled?(container)
return unless attrs.has_key?(:front_matter)
fm_yaml = serialize_front_matter(attrs[:front_matter])
@ -314,7 +315,7 @@ class WikiPage
def parsed_content
strong_memoize(:parsed_content) do
Gitlab::WikiPages::FrontMatterParser.new(raw_content, project).parse
Gitlab::WikiPages::FrontMatterParser.new(raw_content, container).parse
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class WikiPagePolicy < BasePolicy
delegate { @subject.wiki.project }
delegate { @subject.wiki.container }
rule { can?(:read_wiki) }.enable :read_wiki_page
end

View File

@ -0,0 +1,5 @@
---
title: Create cluster annotations API endpoint
merge_request: 29502
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Increase label list label column width
merge_request: 29963
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Route to feature flags based on internal id
merge_request: 29740
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix bug in personal snippets when somebody is mentioned
merge_request: 29835
author: Sashi Kumar
type: fixed

View File

@ -180,7 +180,7 @@ Plan.default.limits.update!(ci_pipeline_schedules: 100)
### Incident Management inbound alert limits
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14932) in GitLab 12.5.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17859) in GitLab 12.5.
Limiting inbound alerts for an incident reduces the number of alerts (issues)
that can be created within a period of time, which can help prevent overloading
@ -192,7 +192,7 @@ alerts in the following ways:
### Prometheus Alert JSON payloads
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14929) in GitLab 12.6.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19940) in GitLab 12.6.
Prometheus alert payloads sent to the `notify.json` endpoint are limited to 1 MB in size.

View File

@ -1,5 +1,8 @@
# Troubleshooting Elasticsearch
To install and configure Elasticsearch, and for common and known issues,
visit the [administrator documentation](../../integration/elasticsearch.md).
Troubleshooting Elasticsearch requires:
- Knowledge of common terms.

View File

@ -20,7 +20,7 @@ You will need to replace the `vault.example.com` URL below with the URL of your
## How it works
Each job has JSON Web Token (JWT) provided as environment variable named `CI_JOB_JWT`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt/#jwt-authentication) method.
Each job has JSON Web Token (JWT) provided as environment variable named `CI_JOB_JWT`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication) method.
The JWT's payload looks like this:
@ -51,7 +51,7 @@ The JWT is encoded by using RS256 and signed with your GitLab instance's OpenID
You can use this JWT and your instance's JWKS endpoint (`https://gitlab.example.com/-/jwks`) to authenticate with a Vault server that is configured to allow the JWT Authentication method for authentication.
When configuring roles in Vault, you can use [bound_claims](https://www.vaultproject.io/docs/auth/jwt/#bound-claims) to match against the JWT's claims and restrict which secrets each CI job has access to.
When configuring roles in Vault, you can use [bound_claims](https://www.vaultproject.io/docs/auth/jwt#bound-claims) to match against the JWT's claims and restrict which secrets each CI job has access to.
To communicate with Vault, you can use either its CLI client or perform API requests (using `curl` or another client).
@ -70,7 +70,7 @@ $ vault kv get -field=password secret/myproject/production/db
real-pa$$w0rd
```
To configure your Vault server, start by enabling the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt/) method:
To configure your Vault server, start by enabling the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt) method:
```shell
$ vault auth enable jwt

View File

@ -113,7 +113,7 @@ There are some important differences in the way Runners work in comparison to ag
If you are using `gitlab.com`, you can take advantage of our [shared Runner fleet](../../user/gitlab_com/index.md#shared-runners)
to run jobs without provisioning your own Runners. We are investigating making them
[available for self-managed instances](https://gitlab.com/gitlab-org/customers-gitlab-com/issues/414)
[available for self-managed instances](https://gitlab.com/groups/gitlab-org/-/epics/835)
as well.
## Groovy vs. YAML

View File

@ -12,7 +12,7 @@ which is exposed as an API endpoint at `/api/graphql`.
## Deep Dive
In March 2019, Nick Thomas hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1)
In March 2019, Nick Thomas hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
on GitLab's [GraphQL API](../api/graphql/index.md) to share his domain specific knowledge
with anyone who may work in this part of the code base in the future. You can find the
[recording on YouTube](https://www.youtube.com/watch?v=-9L_1MWrjkg), and the slides on
@ -102,7 +102,7 @@ be `id` fields.
Further reading:
- [GraphQL Best Practices Guide](https://graphql.org/learn/best-practices/#nullability)
- [Using nullability in GraphQL](https://blog.apollographql.com/using-nullability-in-graphql-2254f84c4ed7)
- [Using nullability in GraphQL](https://www.apollographql.com/blog/using-nullability-in-graphql-2254f84c4ed7)
### Exposing Global IDs

View File

@ -142,7 +142,7 @@ data](https://gitlab.com/gitlab-org/gitlab/blob/19f74903240e209736c7668132e6a5a7
for `Todo` _targets_ when returned in the Todos API.
For more context and discussion about preloading see
[this merge request](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/25711)
[this merge request](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25711)
which introduced the scope.
### Verifying with tests

View File

@ -8,7 +8,7 @@ Currently we rely on different sources to present diffs, these include:
## Deep Dive
In January 2019, Oswaldo Ferreira hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1) on GitLab's Diffs and Commenting on Diffs functionality to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=K6G3gMcFyek), and the slides on [Google Slides](https://docs.google.com/presentation/d/1bGutFH2AT3bxOPZuLMGl1ANWHqFnrxwQwjiwAZkF-TU/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/b5ad2f336e0afcfe0f99db0af0ccc71a/). Everything covered in this deep dive was accurate as of GitLab 11.7, and while specific details may have changed since then, it should still serve as a good introduction.
In January 2019, Oswaldo Ferreira hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's Diffs and Commenting on Diffs functionality to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=K6G3gMcFyek), and the slides on [Google Slides](https://docs.google.com/presentation/d/1bGutFH2AT3bxOPZuLMGl1ANWHqFnrxwQwjiwAZkF-TU/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/b5ad2f336e0afcfe0f99db0af0ccc71a/). Everything covered in this deep dive was accurate as of GitLab 11.7, and while specific details may have changed since then, it should still serve as a good introduction.
## Architecture overview

View File

@ -7,7 +7,7 @@ the [Elasticsearch integration documentation](../integration/elasticsearch.md#en
## Deep Dive
In June 2019, Mario de la Ossa hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1) on GitLab's [Elasticsearch integration](../integration/elasticsearch.md) to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=vrvl-tN2EaA), and the slides on [Google Slides](https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf). Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details may have changed since then, it should still serve as a good introduction.
In June 2019, Mario de la Ossa hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's [Elasticsearch integration](../integration/elasticsearch.md) to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=vrvl-tN2EaA), and the slides on [Google Slides](https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf). Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details may have changed since then, it should still serve as a good introduction.
## Supported Versions

View File

@ -5,7 +5,7 @@ Workhorse and GitLab-Shell.
## Deep Dive
In May 2019, Bob Van Landuyt hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1)
In May 2019, Bob Van Landuyt hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
on GitLab's [Gitaly project](https://gitlab.com/gitlab-org/gitaly) and how to contribute to it as a
Ruby developer, to share his domain specific knowledge with anyone who may work in this part of the
code base in the future.

View File

@ -87,7 +87,7 @@ are very appreciative of the work done by translators and proofreaders!
- Mark Minakou - [GitLab](https://gitlab.com/sandzhaj), [Crowdin](https://crowdin.com/profile/sandzhaj)
- NickVolynkin - [Crowdin](https://crowdin.com/profile/NickVolynkin)
- Andrey Komarov - [GitLab](https://gitlab.com/elkamarado), [Crowdin](https://crowdin.com/profile/kamarado)
- Iaroslav Postovalov - [GitLab](https://gitlab/CMDR_Tvis), [Crowdin](https://crowdin.com/profile/CMDR_Tvis)
- Iaroslav Postovalov - [GitLab](https://gitlab.com/CMDR_Tvis), [Crowdin](https://crowdin.com/profile/CMDR_Tvis)
- Serbian (Cyrillic)
- Proofreaders needed.
- Serbian (Latin)

View File

@ -2,7 +2,7 @@
## Deep Dive
In April 2019, Francisco Javier López hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1)
In April 2019, Francisco Javier López hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
on GitLab's [Git LFS](../topics/git/lfs/index.md) implementation to share his domain
specific knowledge with anyone who may work in this part of the code base in the future.
You can find the [recording on YouTube](https://www.youtube.com/watch?v=Yyxwcksr0Qc),

View File

@ -2,7 +2,7 @@
## Deep Dive
In December 2018, Tiago Botelho hosted [a Deep Dive](`https://gitlab.com/gitlab-org/create-stage/issues/1`)
In December 2018, Tiago Botelho hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
on GitLab's [Pull Repository Mirroring functionality](../user/project/repository/repository_mirroring.md#pulling-from-a-remote-repository-starter)
to share his domain specific knowledge with anyone who may work in this part of the
code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=sSZq0fpdY-Y),

View File

@ -26,7 +26,7 @@ Improper permission handling can have significant impacts on the security of an
Some situations may reveal [sensitive data](https://gitlab.com/gitlab-com/gl-infra/production/issues/477) or allow a malicious actor to perform [harmful actions](https://gitlab.com/gitlab-org/gitlab/issues/8180).
The overall impact depends heavily on what resources can be accessed or modified improperly.
A common vulnerability when permission checks are missing is called [IDOR](https://www.owasp.org/index.php/Testing_for_Insecure_Direct_Object_References_(OTG-AUTHZ-004)) for Insecure Direct Object References.
A common vulnerability when permission checks are missing is called [IDOR](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/05-Authorization_Testing/04-Testing_for_Insecure_Direct_Object_References) for Insecure Direct Object References.
### When to Consider
@ -49,8 +49,8 @@ Be careful to **also test [visibility levels](https://gitlab.com/gitlab-org/gitl
Some example of well implemented access controls and tests:
1. [example1](https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/710/diffs?diff_id=13750#af40ef0eaae3c1e018809e1d88086e32bccaca40_43_43)
1. [example2](https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2511/diffs#ed3aaab1510f43b032ce345909a887e5b167e196_142_155)
1. [example3](https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3170/diffs?diff_id=17494)
1. [example2](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/2511/diffs#ed3aaab1510f43b032ce345909a887e5b167e196_142_155)
1. [example3](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/3170/diffs?diff_id=17494)
**NB:** any input from development team is welcome, e.g. about rubocop rules.
@ -209,7 +209,7 @@ In some cases, it has been possible to configure GitLab::HTTP as the HTTP
connection library for 3rd-party gems. This is preferrable over re-implementing
the mitigations for a new feature.
- [More details](https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2530/diffs)
- [More details](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/2530/diffs)
#### Feature-specific Mitigations
@ -279,7 +279,7 @@ For any and all input fields, ensure to define expectations on the type/format o
- Validate the input using a [whitelist approach](https://youtu.be/2VFavqfDS6w?t=7816) to only allow characters through which you are expecting to receive for the field.
- Input which fails validation should be **rejected**, and not sanitized.
Note that blacklists should be avoided, as it is near impossible to block all [variations of XSS](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet).
Note that blacklists should be avoided, as it is near impossible to block all [variations of XSS](https://owasp.org/www-community/xss-filter-evasion-cheatsheet).
#### Output encoding

View File

@ -66,6 +66,7 @@ We follow a simple formula roughly based on hungarian notation.
- `_placeholder`: a temporary element that appears while content is loading. For example, the elements that are displayed instead of discussions while the discussions are being fetched.
- `_radio`
- `_tab`
- `_menu_item`
*Note: If none of the listed types are suitable, please open a merge request to add an appropriate type to the list.*

View File

@ -497,6 +497,8 @@ However, some larger installations may wish to tune the merge policy settings:
## Troubleshooting
### Common issues
Here are some common pitfalls and how to overcome them:
- **How can I verify my GitLab instance is using Elasticsearch?**
@ -625,6 +627,10 @@ Here are some common pitfalls and how to overcome them:
You probably have not used either `http://` or `https://` as part of your value in the **"URL"** field of the Elasticseach Integration Menu. Please make sure you are using either `http://` or `https://` in this field as the [Elasticsearch client for Go](https://github.com/olivere/elastic) that we are using [needs the prefix for the URL to be accepted as valid](https://github.com/olivere/elastic/commit/a80af35aa41856dc2c986204e2b64eab81ccac3a).
Once you have corrected the formatting of the URL, delete the index (via the [dedicated Rake task](#gitlab-elasticsearch-rake-tasks)) and [reindex the content of your instance](#adding-gitlabs-data-to-the-elasticsearch-index).
### Low level troubleshooting
There is more [low level troubleshooting documentation](../administration/troubleshooting/elasticsearch.md) for when you experience other issues, including poor performance.
### Known Issues
- **[Elasticsearch `code_analyzer` doesn't account for all code cases](https://gitlab.com/gitlab-org/gitlab/issues/10693)**

View File

@ -4,7 +4,7 @@ type: reference
# Rate limits on issue creation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/55241) in GitLab 12.10.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28129) in GitLab 12.10.
This setting allows you to rate limit the requests to the issue creation endpoint.
It defaults to 300 requests per minute.

View File

@ -67,6 +67,26 @@ current default comparison.
![Merge request versions compare HEAD](img/versions_compare_head_v12_10.png)
### Enable or disable `HEAD` comparison mode **(CORE ONLY)**
`HEAD` comparison mode is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session)
can enable it for your instance. You're welcome to test it, but use it at your
own risk.
To enable it:
```ruby
Feature.enable(:diff_compare_with_head)
```
To disable it:
```ruby
Feature.disable(:diff_compare_with_head)
```
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -1,15 +1,17 @@
# Advanced Global Search **(STARTER ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109) in GitLab [Starter](https://about.gitlab.com/pricing/) 8.4.
> - This is the user documentation. To install and configure Elasticsearch,
> visit the [administrator documentation](../../integration/elasticsearch.md).
NOTE: **Note**
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it.
[Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
Leverage Elasticsearch for faster, more advanced code search across your entire
GitLab instance.
This is the user documentation. To install and configure Elasticsearch,
visit the [administrator documentation](../../integration/elasticsearch.md).
## Overview
The Advanced Global Search in GitLab is a powerful search service that saves

View File

@ -3,14 +3,16 @@
> **Notes:**
>
> - Introduced in [GitLab Enterprise Starter](https://about.gitlab.com/pricing/) 9.2
> - This is the user documentation. To install and configure Elasticsearch,
> visit the [administrator documentation](../../integration/elasticsearch.md).
NOTE: **Note**
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it.
[Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
Use advanced queries for more targeted search results.
This is the user documentation. To install and configure Elasticsearch,
visit the [administrator documentation](../../integration/elasticsearch.md).
## Overview
The Advanced Syntax Search is a subset of the

View File

@ -18,20 +18,29 @@ module API
requires :description, type: String, desc: 'The description of the annotation'
end
resource :environments do
post ':id/metrics_dashboard/annotations' do
environment = ::Environment.find(params[:id])
ANNOTATIONS_SOURCES = [
{ class: ::Environment, resource: :environments, create_service_param_key: :environment },
{ class: Clusters::Cluster, resource: :clusters, create_service_param_key: :cluster }
].freeze
not_found! unless Feature.enabled?(:metrics_dashboard_annotations, environment.project)
ANNOTATIONS_SOURCES.each do |annotations_source|
resource annotations_source[:resource] do
post ':id/metrics_dashboard/annotations' do
annotations_source_object = annotations_source[:class].find(params[:id])
forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, environment)
not_found! unless Feature.enabled?(:metrics_dashboard_annotations, annotations_source_object.project)
result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, declared(params).merge(environment: environment)).execute
forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, annotations_source_object)
if result[:status] == :success
present result[:annotation], with: Entities::Metrics::Dashboard::Annotation
else
error!(result, 400)
create_service_params = declared(params).merge(annotations_source[:create_service_param_key] => annotations_source_object)
result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, create_service_params).execute
if result[:status] == :success
present result[:annotation], with: Entities::Metrics::Dashboard::Annotation
else
error!(result, 400)
end
end
end
end

View File

@ -34,8 +34,8 @@ module Gitlab
snippet_url(object, **options)
when User
instance.user_url(object, **options)
when ProjectWiki
instance.project_wiki_url(object.project, :home, **options)
when Wiki
wiki_url(object, **options)
when WikiPage
instance.project_wiki_url(object.wiki.project, object.slug, **options)
else
@ -70,6 +70,19 @@ module Gitlab
instance.gitlab_snippet_url(snippet, **options)
end
end
def wiki_url(object, **options)
case object.container
when Project
instance.project_wiki_url(object.container, Wiki::HOMEPAGE, **options)
when Group
# TODO: Use the new route for group wikis once we add it.
# https://gitlab.com/gitlab-org/gitlab/-/issues/211360
instance.group_canonical_url(object.container, **options) + "/-/wikis/#{Wiki::HOMEPAGE}"
else
raise NotImplementedError.new("No URL builder defined for #{object.inspect}")
end
end
end
end
end

View File

@ -8723,9 +8723,6 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
msgid "Failed create wiki"
msgstr ""
msgid "Failed to add a Zoom meeting"
msgstr ""
@ -8765,6 +8762,9 @@ msgstr ""
msgid "Failed to create resources"
msgstr ""
msgid "Failed to create wiki"
msgstr ""
msgid "Failed to delete board. Please try again."
msgstr ""

View File

@ -25,11 +25,11 @@ FactoryBot.define do
factory :wiki_page_event do
action { Event::CREATED }
project { @overrides[:wiki_page]&.project || create(:project, :wiki_repo) }
project { @overrides[:wiki_page]&.container || create(:project, :wiki_repo) }
target { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) }
transient do
wiki_page { create(:wiki_page, project: project) }
wiki_page { create(:wiki_page, container: project) }
end
end
end

View File

@ -51,5 +51,11 @@ FactoryBot.define do
trait :owner_subgroup_creation_only do
subgroup_creation_level { ::Gitlab::Access::OWNER_SUBGROUP_ACCESS}
end
trait :wiki_repo do
after(:create) do |group|
raise 'Failed to create wiki repository!' unless group.create_wiki
end
end
end
end

View File

@ -1,11 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :project_wiki do
skip_create
association :project, :wiki_repo
user { project.creator }
initialize_with { new(project, user) }
end
end

View File

@ -8,7 +8,8 @@ FactoryBot.define do
title { generate(:wiki_page_title) }
content { 'Content for wiki page' }
format { 'markdown' }
project { create(:project) }
project { association(:project, :wiki_repo) }
container { project }
attrs do
{
title: title,
@ -19,7 +20,7 @@ FactoryBot.define do
end
page { OpenStruct.new(url_path: 'some-name') }
wiki { build(:project_wiki, project: project) }
wiki { association(:wiki, container: container) }
initialize_with { new(wiki, page) }
@ -32,8 +33,6 @@ FactoryBot.define do
end
trait :with_real_page do
project { create(:project, :repository) }
page do
wiki.create_page(title, content)
page_title, page_dir = wiki.page_title_and_dir(title)
@ -48,10 +47,10 @@ FactoryBot.define do
trait :for_wiki_page do
transient do
wiki_page { create(:wiki_page, project: project) }
wiki_page { create(:wiki_page, container: project) }
end
project { @overrides[:wiki_page]&.project || create(:project) }
project { @overrides[:wiki_page]&.container || create(:project) }
title { wiki_page.title }
initialize_with do

25
spec/factories/wikis.rb Normal file
View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
FactoryBot.define do
factory :wiki do
transient do
container { association(:project, :wiki_repo) }
user { association(:user) }
end
initialize_with { Wiki.for_container(container, user) }
skip_create
factory :project_wiki do
transient do
project { association(:project, :wiki_repo) }
end
container { project }
end
factory :group_wiki do
container { association(:group, :wiki_repo) }
end
end
end

View File

@ -18,6 +18,7 @@ describe('Compare diff version dropdowns', () => {
};
localState.targetBranchName = 'baseVersion';
localState.mergeRequestDiffs = diffsMockData;
gon.features = { diffCompareWithHead: true };
});
describe('selectedTargetIndex', () => {
@ -128,6 +129,14 @@ describe('Compare diff version dropdowns', () => {
});
assertVersions(targetVersions);
});
it('does not list head version if feature flag is not enabled', () => {
gon.features = { diffCompareWithHead: false };
setupTest();
const targetVersions = getters.diffCompareDropdownTargetVersions(localState, getters);
expect(targetVersions.find(version => version.isHead)).toBeUndefined();
});
});
it('diffCompareDropdownSourceVersions', () => {

View File

@ -210,4 +210,46 @@ describe('vue_shared/components/awards_list', () => {
expect(buttons.wrappers.every(x => x.classes('disabled'))).toBe(true);
});
});
describe('with default awards', () => {
beforeEach(() => {
createComponent({
awards: [createAward(EMOJI_SMILE, USERS.marie), createAward(EMOJI_100, USERS.marie)],
canAwardEmoji: true,
currentUserId: USERS.root.id,
// Let's assert that it puts thumbsup and thumbsdown in the right order still
defaultAwards: [EMOJI_THUMBSDOWN, EMOJI_100, EMOJI_THUMBSUP],
});
});
it('shows awards in correct order', () => {
expect(findAwardsData()).toEqual([
{
classes: ['btn', 'award-control'],
count: 0,
html: matchingEmojiTag(EMOJI_THUMBSUP),
title: '',
},
{
classes: ['btn', 'award-control'],
count: 0,
html: matchingEmojiTag(EMOJI_THUMBSDOWN),
title: '',
},
// We expect the EMOJI_100 before the EMOJI_SMILE because it was given as a defaultAward
{
classes: ['btn', 'award-control'],
count: 1,
html: matchingEmojiTag(EMOJI_100),
title: 'Marie',
},
{
classes: ['btn', 'award-control'],
count: 1,
html: matchingEmojiTag(EMOJI_SMILE),
title: 'Marie',
},
]);
});
});
});

View File

@ -3,6 +3,8 @@
require 'spec_helper'
describe EventsHelper do
include Gitlab::Routing
describe '#event_commit_title' do
let(:message) { 'foo & bar ' + 'A' * 70 + '\n' + 'B' * 80 }

View File

@ -59,8 +59,8 @@ describe Banzai::Pipeline::WikiPipeline do
let(:project_wiki) { ProjectWiki.new(project, double(:user)) }
let(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) }
{ "when GitLab is hosted at a root URL" => '/',
"when GitLab is hosted at a relative URL" => '/nested/relative/gitlab' }.each do |test_name, relative_url_root|
{ 'when GitLab is hosted at a root URL' => '',
'when GitLab is hosted at a relative URL' => '/nested/relative/gitlab' }.each do |test_name, relative_url_root|
context test_name do
before do
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return(relative_url_root)

View File

@ -52,14 +52,10 @@ describe Gitlab::GitAccessWiki do
end
context 'when the wiki repository does not exist' do
it 'returns not found' do
wiki_repo = project.wiki.repository
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
FileUtils.rm_rf(wiki_repo.path)
end
let(:project) { create(:project) }
# Sanity check for rm_rf
expect(wiki_repo.exists?).to eq(false)
it 'returns not found' do
expect(project.wiki_repository_exists?).to eq(false)
expect { subject }.to raise_error(Gitlab::GitAccess::NotFoundError, 'A repository for this project does not exist yet.')
end

View File

@ -9,7 +9,8 @@ describe Gitlab::RepositoryUrlBuilder do
where(:factory, :path_generator) do
:project | ->(project) { project.full_path }
:project_snippet | ->(snippet) { "#{snippet.project.full_path}/snippets/#{snippet.id}" }
:project_wiki | ->(wiki) { "#{wiki.project.full_path}.wiki" }
:project_wiki | ->(wiki) { "#{wiki.container.full_path}.wiki" }
:group_wiki | ->(wiki) { "#{wiki.container.full_path}.wiki" }
:personal_snippet | ->(snippet) { "snippets/#{snippet.id}" }
end

View File

@ -23,11 +23,12 @@ describe Gitlab::UrlBuilder do
:merge_request | ->(merge_request) { "/#{merge_request.project.full_path}/-/merge_requests/#{merge_request.iid}" }
:project_milestone | ->(milestone) { "/#{milestone.project.full_path}/-/milestones/#{milestone.iid}" }
:project_snippet | ->(snippet) { "/#{snippet.project.full_path}/snippets/#{snippet.id}" }
:project_wiki | ->(wiki) { "/#{wiki.project.full_path}/-/wikis/home" }
:project_wiki | ->(wiki) { "/#{wiki.container.full_path}/-/wikis/home" }
:ci_build | ->(build) { "/#{build.project.full_path}/-/jobs/#{build.id}" }
:group | ->(group) { "/groups/#{group.full_path}" }
:group_milestone | ->(milestone) { "/groups/#{milestone.group.full_path}/-/milestones/#{milestone.iid}" }
:group_wiki | ->(wiki) { "/groups/#{wiki.container.full_path}/-/wikis/home" }
:user | ->(user) { "/#{user.full_path}" }
:personal_snippet | ->(snippet) { "/snippets/#{snippet.id}" }

View File

@ -5,12 +5,17 @@ require 'spec_helper'
describe Blob do
include FakeBlobHelpers
let(:project) { build(:project, lfs_enabled: true) }
using RSpec::Parameterized::TableSyntax
let(:project) { build(:project) }
let(:personal_snippet) { build(:personal_snippet) }
let(:project_snippet) { build(:project_snippet, project: project) }
let(:repository) { project.repository }
let(:lfs_enabled) { true }
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
allow(repository).to receive(:lfs_enabled?) { lfs_enabled }
end
describe '.decorate' do
@ -128,399 +133,84 @@ describe Blob do
end
describe '#external_storage_error?' do
shared_examples 'no error' do
it do
expect(blob.external_storage_error?).to be_falsey
end
end
subject { blob.external_storage_error? }
shared_examples 'returns error' do
it do
expect(blob.external_storage_error?).to be_truthy
end
end
context 'if the blob is stored in LFS' do
let(:blob) { fake_blob(path: 'file.pdf', lfs: true, container: container) }
context 'when the project has LFS enabled' do
context 'with project' do
let(:container) { project }
it_behaves_like 'no error'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns error'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'no error'
end
end
context 'when the project does not have LFS enabled' do
before do
project.lfs_enabled = false
end
context 'with project' do
let(:container) { project }
it_behaves_like 'returns error'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns error'
end
end
end
context 'if the blob is not stored in LFS' do
let(:blob) { fake_blob(path: 'file.md', container: container) }
context 'with project' do
let(:container) { project }
it_behaves_like 'no error'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'no error'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'no error'
end
end
end
describe '#stored_externally?' do
context 'if the blob is stored in LFS' do
let(:blob) { fake_blob(path: 'file.pdf', lfs: true) }
shared_examples 'returns true' do
it do
expect(blob.stored_externally?).to be_truthy
end
context 'when LFS is enabled' do
let(:lfs_enabled) { true }
it { is_expected.to be_falsy }
end
shared_examples 'returns false' do
it do
expect(blob.stored_externally?).to be_falsey
end
end
context 'when LFS is not enabled' do
let(:lfs_enabled) { false }
context 'when the project has LFS enabled' do
context 'with project' do
let(:container) { project }
it_behaves_like 'returns true'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns true'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns true'
end
end
context 'when the project does not have LFS enabled' do
before do
project.lfs_enabled = false
end
context 'with project' do
let(:container) { project }
it_behaves_like 'returns false'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns false'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns false'
end
it { is_expected.to be_truthy }
end
end
context 'if the blob is not stored in LFS' do
let(:blob) { fake_blob(path: 'file.md') }
it 'returns false' do
expect(blob.stored_externally?).to be_falsey
it { is_expected.to be_falsy }
end
end
describe '#stored_externally?' do
subject { blob.stored_externally? }
context 'if the blob is stored in LFS' do
let(:blob) { fake_blob(path: 'file.pdf', lfs: true) }
context 'when LFS is enabled' do
let(:lfs_enabled) { true }
it { is_expected.to be_truthy }
end
context 'when LFS is not enabled' do
let(:lfs_enabled) { false }
it { is_expected.to be_falsy }
end
end
context 'if the blob is not stored in LFS' do
let(:blob) { fake_blob(path: 'file.md') }
it { is_expected.to be_falsy }
end
end
describe '#binary?' do
shared_examples 'returns true' do
it do
expect(blob.binary?).to be_truthy
context 'an lfs object' do
where(:filename, :is_binary) do
'file.pdf' | true
'file.md' | false
'file.txt' | false
'file.ics' | false
'file.rb' | false
'file.exe' | true
'file.ini' | false
'file.wtf' | true
end
with_them do
let(:blob) { fake_blob(path: filename, lfs: true, container: project) }
it { expect(blob.binary?).to eq(is_binary) }
end
end
shared_examples 'returns false' do
it do
expect(blob.binary?).to be_falsey
end
end
context 'a non-lfs object' do
let(:blob) { fake_blob(path: 'anything', container: project) }
context 'if the blob is stored externally' do
let(:blob) { fake_blob(path: file, lfs: true) }
it 'delegates to binary_in_repo?' do
expect(blob).to receive(:binary_in_repo?) { :result }
context 'if the extension has a rich viewer' do
context 'if the viewer is binary' do
let(:file) { 'file.pdf' }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns true'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns true'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns true'
end
end
context 'if the viewer is text-based' do
let(:file) { 'file.md' }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns false'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns false'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns false'
end
end
end
context "if the extension doesn't have a rich viewer" do
context 'if the extension has a text mime type' do
context 'if the extension is for a programming language' do
let(:file) { 'file.txt' }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns false'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns false'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns false'
end
end
context 'if the extension is not for a programming language' do
let(:file) { 'file.ics' }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns false'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns false'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns false'
end
end
end
context 'if the extension has a binary mime type' do
context 'if the extension is for a programming language' do
let(:file) { 'file.rb' }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns false'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns false'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns false'
end
end
context 'if the extension is not for a programming language' do
let(:file) { 'file.exe' }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns true'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns true'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns true'
end
end
end
context 'if the extension has an unknown mime type' do
context 'if the extension is for a programming language' do
let(:file) { 'file.ini' }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns false'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns false'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns false'
end
end
context 'if the extension is not for a programming language' do
let(:file) { 'file.wtf' }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns true'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns true'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns true'
end
end
end
end
end
context 'if the blob is not stored externally' do
context 'if the blob is binary' do
let(:blob) { fake_blob(path: 'file.pdf', binary: true, container: container) }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns true'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns true'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns true'
end
end
context 'if the blob is text-based' do
let(:blob) { fake_blob(path: 'file.md', container: container) }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns false'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns false'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns false'
end
expect(blob.binary?).to eq(:result)
end
end
end
@ -569,9 +259,7 @@ describe Blob do
describe '#rich_viewer' do
context 'when the blob has an external storage error' do
before do
project.lfs_enabled = false
end
let(:lfs_enabled) { false }
it 'returns nil' do
blob = fake_blob(path: 'file.pdf', lfs: true)
@ -631,9 +319,7 @@ describe Blob do
describe '#auxiliary_viewer' do
context 'when the blob has an external storage error' do
before do
project.lfs_enabled = false
end
let(:lfs_enabled) { false }
it 'returns nil' do
blob = fake_blob(path: 'LICENSE', lfs: true)
@ -676,63 +362,21 @@ describe Blob do
end
describe '#rendered_as_text?' do
shared_examples 'returns true' do
it do
expect(blob.rendered_as_text?(ignore_errors: ignore_errors)).to be_truthy
end
end
shared_examples 'returns false' do
it do
expect(blob.rendered_as_text?(ignore_errors: ignore_errors)).to be_falsey
end
end
subject { blob.rendered_as_text?(ignore_errors: ignore_errors) }
context 'when ignoring errors' do
let(:ignore_errors) { true }
context 'when the simple viewer is text-based' do
let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes, container: container) }
let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes) }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns true'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns true'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns true'
end
it { is_expected.to be_truthy }
end
context 'when the simple viewer is binary' do
let(:blob) { fake_blob(path: 'file.pdf', binary: true, size: 100.megabytes, container: container) }
let(:blob) { fake_blob(path: 'file.pdf', binary: true, size: 100.megabytes) }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns false'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns false'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns false'
end
it { is_expected.to be_falsy }
end
end
@ -740,47 +384,15 @@ describe Blob do
let(:ignore_errors) { false }
context 'when the viewer has render errors' do
let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes, container: container) }
let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes) }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns false'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns false'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns false'
end
it { is_expected.to be_falsy }
end
context "when the viewer doesn't have render errors" do
let(:blob) { fake_blob(path: 'file.md', container: container) }
let(:blob) { fake_blob(path: 'file.md') }
context 'with project' do
let(:container) { project }
it_behaves_like 'returns true'
end
context 'with personal snippet' do
let(:container) { personal_snippet }
it_behaves_like 'returns true'
end
context 'with project snippet' do
let(:container) { project_snippet }
it_behaves_like 'returns true'
end
it { is_expected.to be_truthy }
end
end
end

View File

@ -270,7 +270,7 @@ describe Ci::Runner do
it { is_expected.to eq([@runner2])}
end
describe '#online?' do
describe '#online?', :clean_gitlab_redis_cache do
let(:runner) { create(:ci_runner, :instance) }
subject { runner.online? }
@ -332,7 +332,7 @@ describe Ci::Runner do
end
def stub_redis_runner_contacted_at(value)
Gitlab::Redis::SharedState.with do |redis|
Gitlab::Redis::Cache.with do |redis|
cache_key = runner.send(:cache_attribute_key)
expect(redis).to receive(:get).with(cache_key)
.and_return({ contacted_at: value }.to_json).at_least(:once)
@ -640,7 +640,7 @@ describe Ci::Runner do
end
def expect_redis_update
Gitlab::Redis::SharedState.with do |redis|
Gitlab::Redis::Cache.with do |redis|
redis_key = runner.send(:cache_attribute_key)
expect(redis).to receive(:set).with(redis_key, anything, any_args)
end
@ -664,7 +664,7 @@ describe Ci::Runner do
end
it 'cleans up the queue' do
Gitlab::Redis::SharedState.with do |redis|
Gitlab::Redis::Cache.with do |redis|
expect(redis.get(queue_key)).to be_nil
end
end

View File

@ -325,3 +325,20 @@ describe Snippet, 'Mentionable' do
end
end
end
describe PersonalSnippet, 'Mentionable' do
describe '#store_mentions!' do
it_behaves_like 'mentions in description', :personal_snippet
it_behaves_like 'mentions in notes', :personal_snippet do
let(:note) { create(:note_on_personal_snippet) }
let(:mentionable) { note.noteable }
end
end
describe 'load mentions' do
it_behaves_like 'load mentions from DB', :personal_snippet do
let(:note) { create(:note_on_personal_snippet) }
let(:mentionable) { note.noteable }
end
end
end

View File

@ -31,7 +31,7 @@ describe RedisCacheable do
subject { instance.cached_attribute(payload.each_key.first) }
it 'gets the cache attribute' do
Gitlab::Redis::SharedState.with do |redis|
Gitlab::Redis::Cache.with do |redis|
expect(redis).to receive(:get).with(cache_key)
.and_return(payload.to_json)
end
@ -44,7 +44,7 @@ describe RedisCacheable do
subject { instance.cache_attributes(payload) }
it 'sets the cache attributes' do
Gitlab::Redis::SharedState.with do |redis|
Gitlab::Redis::Cache.with do |redis|
expect(redis).to receive(:set).with(cache_key, payload.to_json, anything)
end
@ -52,7 +52,7 @@ describe RedisCacheable do
end
end
describe '#cached_attr_reader', :clean_gitlab_redis_shared_state do
describe '#cached_attr_reader', :clean_gitlab_redis_cache do
subject { instance.name }
before do

View File

@ -25,6 +25,11 @@ describe Group do
it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
it { is_expected.to have_many(:container_repositories) }
it_behaves_like 'model with wiki' do
let(:container) { create(:group, :nested, :wiki_repo) }
let(:container_without_wiki) { create(:group, :nested) }
end
describe '#members & #requesters' do
let(:requester) { create(:user) }
let(:developer) { create(:user) }

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'spec_helper'
describe GroupWiki do
it_behaves_like 'wiki model' do
let(:wiki_container) { create(:group, :wiki_repo) }
let(:wiki_container_without_repo) { create(:group) }
before do
wiki_container.add_owner(user)
end
describe '#storage' do
it 'uses the group repository prefix' do
expect(subject.storage.base_dir).to start_with('@groups/')
end
end
describe '#repository_storage' do
it 'returns the default storage' do
expect(subject.repository_storage).to eq('default')
end
end
describe '#hashed_storage?' do
it 'returns true' do
expect(subject.hashed_storage?).to be(true)
end
end
describe '#disk_path' do
it 'returns the repository storage path' do
expect(subject.disk_path).to eq("#{subject.storage.disk_path}.wiki")
end
end
end
end

View File

@ -22,5 +22,6 @@ describe PersonalSnippet do
let(:stubbed_container) { build_stubbed(:personal_snippet) }
let(:expected_full_path) { "@snippets/#{container.id}" }
let(:expected_web_url_path) { "snippets/#{container.id}" }
let(:expected_repo_url_path) { expected_web_url_path }
end
end

View File

@ -38,5 +38,6 @@ describe ProjectSnippet do
let(:stubbed_container) { build_stubbed(:project_snippet) }
let(:expected_full_path) { "#{container.project.full_path}/@snippets/#{container.id}" }
let(:expected_web_url_path) { "#{container.project.full_path}/snippets/#{container.id}" }
let(:expected_repo_url_path) { expected_web_url_path }
end
end

View File

@ -118,6 +118,11 @@ describe Project do
let(:expected_full_path) { "#{container.namespace.full_path}/somewhere" }
end
it_behaves_like 'model with wiki' do
let(:container) { create(:project, :wiki_repo) }
let(:container_without_wiki) { create(:project) }
end
it 'has an inverse relationship with merge requests' do
expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
end
@ -263,27 +268,6 @@ describe Project do
create(:project)
end
describe 'wiki path conflict' do
context "when the new path has been used by the wiki of other Project" do
it 'has an error on the name attribute' do
new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
expect(new_project).not_to be_valid
expect(new_project.errors[:name].first).to eq(_('has already been taken'))
end
end
context "when the new wiki path has been used by the path of other Project" do
it 'has an error on the name attribute' do
project_with_wiki_suffix = create(:project, path: 'foo.wiki')
new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
expect(new_project).not_to be_valid
expect(new_project.errors[:name].first).to eq(_('has already been taken'))
end
end
end
context 'repository storages inclusion' do
let(:project2) { build(:project, repository_storage: 'missing') }
@ -4716,20 +4700,6 @@ describe Project do
end
end
describe '#wiki_repository_exists?' do
it 'returns true when the wiki repository exists' do
project = create(:project, :wiki_repo)
expect(project.wiki_repository_exists?).to eq(true)
end
it 'returns false when the wiki repository does not exist' do
project = create(:project)
expect(project.wiki_repository_exists?).to eq(false)
end
end
describe '#write_repository_config' do
let_it_be(:project) { create(:project, :repository) }

View File

@ -1,448 +1,35 @@
# frozen_string_literal: true
require "spec_helper"
require 'spec_helper'
describe ProjectWiki do
let(:user) { create(:user, :commit_email) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:repository) { project.repository }
let(:gitlab_shell) { Gitlab::Shell.new }
let(:project_wiki) { described_class.new(project, user) }
let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo', 'group/project.wiki') }
let(:commit) { project_wiki.repository.head_commit }
it_behaves_like 'wiki model' do
let(:wiki_container) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:wiki_container_without_repo) { create(:project, namespace: user.namespace) }
subject { project_wiki }
it { is_expected.to delegate_method(:storage).to(:container) }
it { is_expected.to delegate_method(:repository_storage).to(:container) }
it { is_expected.to delegate_method(:hashed_storage?).to(:container) }
it { is_expected.to delegate_method(:repository_storage).to :project }
it { is_expected.to delegate_method(:hashed_storage?).to :project }
describe "#full_path" do
it "returns the project path with namespace with the .wiki extension" do
expect(subject.full_path).to eq(project.full_path + '.wiki')
end
it 'returns the same value as #full_path' do
expect(subject.full_path).to eq(subject.full_path)
end
end
describe '#web_url' do
it 'returns the full web URL to the wiki' do
expect(subject.web_url).to eq(Gitlab::UrlBuilder.build(subject))
end
end
describe "#url_to_repo" do
it "returns the correct ssh url to the repo" do
expect(subject.url_to_repo).to eq(Gitlab::RepositoryUrlBuilder.build(subject.repository.full_path, protocol: :ssh))
end
end
describe "#ssh_url_to_repo" do
it "equals #url_to_repo" do
expect(subject.ssh_url_to_repo).to eq(subject.url_to_repo)
end
end
describe "#http_url_to_repo" do
it "returns the correct http url to the repo" do
expect(subject.http_url_to_repo).to eq(Gitlab::RepositoryUrlBuilder.build(subject.repository.full_path, protocol: :http))
end
end
describe "#wiki_base_path" do
it "returns the wiki base path" do
wiki_base_path = "#{Gitlab.config.gitlab.relative_url_root}/#{project.full_path}/-/wikis"
expect(subject.wiki_base_path).to eq(wiki_base_path)
end
end
describe "#wiki" do
it "contains a Gitlab::Git::Wiki instance" do
expect(subject.wiki).to be_a Gitlab::Git::Wiki
end
it "creates a new wiki repo if one does not yet exist" do
expect(project_wiki.create_page("index", "test content")).to be_truthy
end
it "creates a new wiki repo with a default commit message" do
expect(project_wiki.create_page("index", "test content", :markdown, "")).to be_truthy
page = project_wiki.find_page('index')
expect(page.last_version.message).to eq("#{user.username} created page: index")
end
it "raises CouldNotCreateWikiError if it can't create the wiki repository" do
# Create a fresh project which will not have a wiki
project_wiki = described_class.new(create(:project), user)
expect(project_wiki.repository).to receive(:create_if_not_exists) { false }
expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError)
end
end
describe "#empty?" do
context "when the wiki repository is empty" do
describe '#empty?' do
subject { super().empty? }
it { is_expected.to be_truthy }
describe '#disk_path' do
it 'returns the repository storage path' do
expect(subject.disk_path).to eq("#{subject.container.disk_path}.wiki")
end
end
context "when the wiki has pages" do
before do
project_wiki.create_page("index", "This is an awesome new Gollum Wiki")
project_wiki.create_page("another-page", "This is another page")
end
describe '#update_container_activity' do
it 'updates project activity' do
wiki_container.update!(
last_activity_at: nil,
last_repository_updated_at: nil
)
describe '#empty?' do
subject { super().empty? }
subject.create_page('Test Page', 'This is content')
wiki_container.reload
it { is_expected.to be_falsey }
it 'only instantiates a Wiki page once' do
expect(WikiPage).to receive(:new).once.and_call_original
subject
end
expect(wiki_container.last_activity_at).to be_within(1.minute).of(Time.now)
expect(wiki_container.last_repository_updated_at).to be_within(1.minute).of(Time.now)
end
end
end
describe "#list_pages" do
let(:wiki_pages) { subject.list_pages }
before do
create_page("index", "This is an index")
create_page("index2", "This is an index2")
create_page("an index3", "This is an index3")
end
after do
wiki_pages.each do |wiki_page|
destroy_page(wiki_page.page)
end
end
it "returns an array of WikiPage instances" do
expect(wiki_pages.first).to be_a WikiPage
end
it 'does not load WikiPage content by default' do
wiki_pages.each do |page|
expect(page.content).to be_empty
end
end
it 'returns all pages by default' do
expect(wiki_pages.count).to eq(3)
end
context "with limit option" do
it 'returns limited set of pages' do
expect(subject.list_pages(limit: 1).count).to eq(1)
end
end
context "with sorting options" do
it 'returns pages sorted by title by default' do
pages = ['an index3', 'index', 'index2']
expect(subject.list_pages.map(&:title)).to eq(pages)
expect(subject.list_pages(direction: "desc").map(&:title)).to eq(pages.reverse)
end
it 'returns pages sorted by created_at' do
pages = ['index', 'index2', 'an index3']
expect(subject.list_pages(sort: 'created_at').map(&:title)).to eq(pages)
expect(subject.list_pages(sort: 'created_at', direction: "desc").map(&:title)).to eq(pages.reverse)
end
end
context "with load_content option" do
let(:pages) { subject.list_pages(load_content: true) }
it 'loads WikiPage content' do
expect(pages.first.content).to eq("This is an index3")
expect(pages.second.content).to eq("This is an index")
expect(pages.third.content).to eq("This is an index2")
end
end
end
describe "#find_page" do
before do
create_page("index page", "This is an awesome Gollum Wiki")
end
after do
subject.list_pages.each { |page| destroy_page(page.page) }
end
it "returns the latest version of the page if it exists" do
page = subject.find_page("index page")
expect(page.title).to eq("index page")
end
it "returns nil if the page does not exist" do
expect(subject.find_page("non-existent")).to eq(nil)
end
it "can find a page by slug" do
page = subject.find_page("index-page")
expect(page.title).to eq("index page")
end
it "returns a WikiPage instance" do
page = subject.find_page("index page")
expect(page).to be_a WikiPage
end
context 'pages with multibyte-character title' do
before do
create_page("autre pagé", "C'est un génial Gollum Wiki")
end
it "can find a page by slug" do
page = subject.find_page("autre pagé")
expect(page.title).to eq("autre pagé")
end
end
context 'pages with invalidly-encoded content' do
before do
create_page("encoding is fun", "f\xFCr".b)
end
it "can find the page" do
page = subject.find_page("encoding is fun")
expect(page.content).to eq("fr")
end
end
end
describe '#find_sidebar' do
before do
create_page(described_class::SIDEBAR, 'This is an awesome Sidebar')
end
after do
subject.list_pages.each { |page| destroy_page(page.page) }
end
it 'finds the page defined as _sidebar' do
page = subject.find_page('_sidebar')
expect(page.content).to eq('This is an awesome Sidebar')
end
end
describe '#find_file' do
let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
before do
subject.wiki # Make sure the wiki repo exists
repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
subject.repository.path_to_repo
end
BareRepoOperations.new(repo_path).commit_file(image, 'image.png')
end
it 'returns the latest version of the file if it exists' do
file = subject.find_file('image.png')
expect(file.mime_type).to eq('image/png')
end
it 'returns nil if the page does not exist' do
expect(subject.find_file('non-existent')).to eq(nil)
end
it 'returns a Gitlab::Git::WikiFile instance' do
file = subject.find_file('image.png')
expect(file).to be_a Gitlab::Git::WikiFile
end
it 'returns the whole file' do
file = subject.find_file('image.png')
image.rewind
expect(file.raw_data.b).to eq(image.read.b)
end
end
describe "#create_page" do
after do
destroy_page(subject.list_pages.first.page)
end
it "creates a new wiki page" do
expect(subject.create_page("test page", "this is content")).not_to eq(false)
expect(subject.list_pages.count).to eq(1)
end
it "returns false when a duplicate page exists" do
subject.create_page("test page", "content")
expect(subject.create_page("test page", "content")).to eq(false)
end
it "stores an error message when a duplicate page exists" do
2.times { subject.create_page("test page", "content") }
expect(subject.error_message).to match(/Duplicate page:/)
end
it "sets the correct commit message" do
subject.create_page("test page", "some content", :markdown, "commit message")
expect(subject.list_pages.first.page.version.message).to eq("commit message")
end
it 'sets the correct commit email' do
subject.create_page('test page', 'content')
expect(user.commit_email).not_to eq(user.email)
expect(commit.author_email).to eq(user.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
end
it 'updates project activity' do
subject.create_page('Test Page', 'This is content')
project.reload
expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
end
end
describe "#update_page" do
before do
create_page("update-page", "some content")
@gitlab_git_wiki_page = subject.wiki.page(title: "update-page")
subject.update_page(
@gitlab_git_wiki_page,
content: "some other content",
format: :markdown,
message: "updated page"
)
@page = subject.list_pages(load_content: true).first.page
end
after do
destroy_page(@page)
end
it "updates the content of the page" do
expect(@page.raw_data).to eq("some other content")
end
it "sets the correct commit message" do
expect(@page.version.message).to eq("updated page")
end
it 'sets the correct commit email' do
expect(user.commit_email).not_to eq(user.email)
expect(commit.author_email).to eq(user.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
end
it 'updates project activity' do
subject.update_page(
@gitlab_git_wiki_page,
content: 'Yet more content',
format: :markdown,
message: 'Updated page again'
)
project.reload
expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
end
end
describe "#delete_page" do
before do
create_page("index", "some content")
@page = subject.wiki.page(title: "index")
end
it "deletes the page" do
subject.delete_page(@page)
expect(subject.list_pages.count).to eq(0)
end
it 'sets the correct commit email' do
subject.delete_page(@page)
expect(user.commit_email).not_to eq(user.email)
expect(commit.author_email).to eq(user.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
end
it 'updates project activity' do
subject.delete_page(@page)
project.reload
expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
end
end
describe '#ensure_repository' do
let(:project) { create(:project) }
it 'creates the repository if it not exist' do
expect(raw_repository.exists?).to eq(false)
subject.ensure_repository
expect(raw_repository.exists?).to eq(true)
end
it 'does not create the repository if it exists' do
subject.wiki
expect(raw_repository.exists?).to eq(true)
expect(subject).not_to receive(:create_repo!)
subject.ensure_repository
end
end
describe '#hook_attrs' do
it 'returns a hash with values' do
expect(subject.hook_attrs).to be_a Hash
expect(subject.hook_attrs.keys).to contain_exactly(:web_url, :git_ssh_url, :git_http_url, :path_with_namespace, :default_branch)
end
end
private
def create_temp_repo(path)
FileUtils.mkdir_p path
system(*%W(#{Gitlab.config.git.bin_path} init --quiet --bare -- #{path}))
end
def remove_temp_repo(path)
FileUtils.rm_rf path
end
def commit_details
Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.commit_email, "test commit")
end
def create_page(name, content)
subject.wiki.write_page(name, :markdown, content, commit_details)
end
def destroy_page(page)
subject.delete_page(page, "test commit")
end
end

View File

@ -2874,4 +2874,64 @@ describe Repository do
expect(repository.submodule_links).to be_a(Gitlab::SubmoduleLinks)
end
end
describe '#lfs_enabled?' do
let_it_be(:project) { create(:project, :repository, lfs_enabled: true) }
subject { repository.lfs_enabled? }
context 'for a project repository' do
let(:repository) { project.repository }
it 'returns true when LFS is enabled' do
stub_lfs_setting(enabled: true)
is_expected.to be_truthy
end
it 'returns false when LFS is disabled' do
stub_lfs_setting(enabled: false)
is_expected.to be_falsy
end
end
context 'for a project wiki repository' do
let(:repository) { project.wiki.repository }
it 'returns true when LFS is enabled' do
stub_lfs_setting(enabled: true)
is_expected.to be_truthy
end
it 'returns false when LFS is disabled' do
stub_lfs_setting(enabled: false)
is_expected.to be_falsy
end
end
context 'for a project snippet repository' do
let(:snippet) { create(:project_snippet, project: project) }
let(:repository) { snippet.repository }
it 'returns false when LFS is enabled' do
stub_lfs_setting(enabled: true)
is_expected.to be_falsy
end
end
context 'for a personal snippet repository' do
let(:snippet) { create(:personal_snippet) }
let(:repository) { snippet.repository }
it 'returns false when LFS is enabled' do
stub_lfs_setting(enabled: true)
is_expected.to be_falsy
end
end
end
end

View File

@ -3,9 +3,9 @@
require "spec_helper"
describe WikiPage do
let(:project) { create(:project, :wiki_repo) }
let(:user) { project.owner }
let(:wiki) { ProjectWiki.new(project, user) }
let_it_be(:user) { create(:user) }
let(:container) { create(:project, :wiki_repo) }
let(:wiki) { Wiki.for_container(container, user) }
let(:new_page) do
described_class.new(wiki).tap do |page|
@ -24,9 +24,9 @@ describe WikiPage do
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => false)
end
def enable_front_matter_for_project
def enable_front_matter_for(thing)
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => {
thing: project,
thing: thing,
enabled: true
})
end
@ -114,7 +114,8 @@ describe WikiPage do
describe '#front_matter' do
let_it_be(:project) { create(:project) }
let(:wiki_page) { create(:wiki_page, project: project, content: content) }
let(:container) { project }
let(:wiki_page) { create(:wiki_page, container: container, content: content) }
shared_examples 'a page without front-matter' do
it { expect(wiki_page).to have_attributes(front_matter: {}, content: content) }
@ -153,12 +154,20 @@ describe WikiPage do
it_behaves_like 'a page without front-matter'
context 'but enabled for the project' do
context 'but enabled for the container' do
before do
enable_front_matter_for_project
enable_front_matter_for(container)
end
it_behaves_like 'a page with front-matter'
context 'with a project container' do
it_behaves_like 'a page with front-matter'
end
context 'with a group container' do
let(:container) { create(:group) }
it_behaves_like 'a page with front-matter'
end
end
end
end
@ -514,12 +523,20 @@ describe WikiPage do
expect([subject, page]).to all(have_attributes(front_matter: be_empty, content: content))
end
context 'but it is enabled for the project' do
context 'but it is enabled for the container' do
before do
enable_front_matter_for_project
enable_front_matter_for(container)
end
it_behaves_like 'able to update front-matter'
context 'with a project container' do
it_behaves_like 'able to update front-matter'
end
context 'with a group container' do
let(:container) { create(:group) }
it_behaves_like 'able to update front-matter'
end
end
end
@ -812,23 +829,32 @@ describe WikiPage do
other_page = create(:wiki_page)
expect(subject.slug).not_to eq(other_page.slug)
expect(subject.project).not_to eq(other_page.project)
expect(subject.container).not_to eq(other_page.container)
expect(subject).not_to eq(other_page)
end
it 'returns false for page with different slug on same project' do
other_page = create(:wiki_page, project: subject.project)
it 'returns false for page with different slug on same container' do
other_page = create(:wiki_page, container: subject.container)
expect(subject.slug).not_to eq(other_page.slug)
expect(subject.project).to eq(other_page.project)
expect(subject.container).to eq(other_page.container)
expect(subject).not_to eq(other_page)
end
it 'returns false for page with the same slug on a different project' do
it 'returns false for page with the same slug on a different container of the same type' do
other_page = create(:wiki_page, title: existing_page.slug)
expect(subject.slug).to eq(other_page.slug)
expect(subject.project).not_to eq(other_page.project)
expect(subject.container).not_to eq(other_page.container)
expect(subject).not_to eq(other_page)
end
it 'returns false for page with the same slug on a different container type' do
group = create(:group, name: container.name)
other_page = create(:wiki_page, title: existing_page.slug, container: group)
expect(subject.slug).to eq(other_page.slug)
expect(subject.container).not_to eq(other_page.container)
expect(subject).not_to eq(other_page)
end
end

View File

@ -11,77 +11,110 @@ describe API::Metrics::Dashboard::Annotations do
let(:ending_at) { 1.hour.from_now.iso8601 }
let(:params) { attributes_for(:metrics_dashboard_annotation, environment: environment, starting_at: starting_at, ending_at: ending_at, dashboard_path: dashboard)}
describe 'POST /environments/:environment_id/metrics_dashboard/annotations' do
before :all do
shared_examples 'POST /:source_type/:id/metrics_dashboard/annotations' do |source_type|
let(:url) { "/#{source_type.pluralize}/#{source.id}/metrics_dashboard/annotations" }
before do
project.add_developer(user)
end
context 'feature flag metrics_dashboard_annotations' do
context 'is on' do
before do
stub_feature_flags(metrics_dashboard_annotations: { enabled: true, thing: project })
end
context 'with correct permissions' do
context 'with valid parameters' do
it 'creates a new annotation', :aggregate_failures do
post api("/environments/#{environment.id}/metrics_dashboard/annotations", user), params: params
expect(response).to have_gitlab_http_status(:created)
expect(json_response['environment_id']).to eq(environment.id)
expect(json_response['starting_at'].to_time).to eq(starting_at.to_time)
expect(json_response['ending_at'].to_time).to eq(ending_at.to_time)
expect(json_response['description']).to eq(params[:description])
expect(json_response['dashboard_path']).to eq(dashboard)
end
end
context 'with invalid parameters' do
it 'returns error messsage' do
post api("/environments/#{environment.id}/metrics_dashboard/annotations", user),
params: { dashboard_path: nil, starting_at: nil, description: nil }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to include({ "starting_at" => ["can't be blank"], "description" => ["can't be blank"], "dashboard_path" => ["can't be blank"] })
end
end
context 'with undeclared params' do
before do
params[:undeclared_param] = 'xyz'
end
it 'filters out undeclared params' do
expect(::Metrics::Dashboard::Annotations::CreateService).to receive(:new).with(user, hash_excluding(:undeclared_param))
post api("/environments/#{environment.id}/metrics_dashboard/annotations", user), params: params
end
end
end
context 'without correct permissions' do
let_it_be(:guest) { create(:user) }
context "with :source_type == #{source_type.pluralize}" do
context 'feature flag metrics_dashboard_annotations' do
context 'is on' do
before do
project.add_guest(guest)
stub_feature_flags(metrics_dashboard_annotations: { enabled: true, thing: project })
end
context 'with correct permissions' do
context 'with valid parameters' do
it 'creates a new annotation', :aggregate_failures do
post api(url, user), params: params
expect(response).to have_gitlab_http_status(:created)
expect(json_response["#{source_type}_id"]).to eq(source.id)
expect(json_response['starting_at'].to_time).to eq(starting_at.to_time)
expect(json_response['ending_at'].to_time).to eq(ending_at.to_time)
expect(json_response['description']).to eq(params[:description])
expect(json_response['dashboard_path']).to eq(dashboard)
end
end
context 'with invalid parameters' do
it 'returns error messsage' do
post api(url, user), params: { dashboard_path: nil, starting_at: nil, description: nil }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to include({ "starting_at" => ["can't be blank"], "description" => ["can't be blank"], "dashboard_path" => ["can't be blank"] })
end
end
context 'with undeclared params' do
before do
params[:undeclared_param] = 'xyz'
end
it 'filters out undeclared params' do
expect(::Metrics::Dashboard::Annotations::CreateService).to receive(:new).with(user, hash_excluding(:undeclared_param))
post api(url, user), params: params
end
end
end
context 'without correct permissions' do
let_it_be(:guest) { create(:user) }
before do
project.add_guest(guest)
end
it 'returns error messsage' do
post api(url, guest), params: params
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'is off' do
before do
stub_feature_flags(metrics_dashboard_annotations: { enabled: false })
end
it 'returns error messsage' do
post api("/environments/#{environment.id}/metrics_dashboard/annotations", guest), params: params
post api(url, user), params: params
expect(response).to have_gitlab_http_status(:forbidden)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'is off' do
before do
stub_feature_flags(metrics_dashboard_annotations: { enabled: false, thing: project })
end
it 'returns error messsage' do
post api("/environments/#{environment.id}/metrics_dashboard/annotations", user), params: params
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
describe 'environment' do
it_behaves_like 'POST /:source_type/:id/metrics_dashboard/annotations', 'environment' do
let(:source) { environment }
end
end
describe 'group cluster' do
it_behaves_like 'POST /:source_type/:id/metrics_dashboard/annotations', 'cluster' do
let_it_be(:group) { create(:group) }
let_it_be(:cluster) { create(:cluster_for_group, groups: [group]) }
before do
group.add_developer(user)
end
let(:source) { cluster }
end
end
describe 'project cluster' do
it_behaves_like 'POST /:source_type/:id/metrics_dashboard/annotations', 'cluster' do
let_it_be(:cluster) { create(:cluster, projects: [project]) }
let(:source) { cluster }
end
end
end

View File

@ -252,12 +252,12 @@ describe Snippets::CreateService do
end
end
shared_examples 'after_save callback to store_mentions' do
shared_examples 'after_save callback to store_mentions' do |mentionable_class|
context 'when mentionable attributes change' do
let(:extra_opts) { { description: "Description with #{user.to_reference}" } }
it 'saves mentions' do
expect_next_instance_of(Snippet) do |instance|
expect_next_instance_of(mentionable_class) do |instance|
expect(instance).to receive(:store_mentions!).and_call_original
end
expect(snippet.user_mentions.count).to eq 1
@ -266,7 +266,7 @@ describe Snippets::CreateService do
context 'when mentionable attributes do not change' do
it 'does not call store_mentions' do
expect_next_instance_of(Snippet) do |instance|
expect_next_instance_of(mentionable_class) do |instance|
expect(instance).not_to receive(:store_mentions!)
end
expect(snippet.user_mentions.count).to eq 0
@ -277,7 +277,7 @@ describe Snippets::CreateService do
it 'does not call store_mentions' do
base_opts.delete(:title)
expect_next_instance_of(Snippet) do |instance|
expect_next_instance_of(mentionable_class) do |instance|
expect(instance).not_to receive(:store_mentions!)
end
expect(snippet.valid?).to be false
@ -298,7 +298,7 @@ describe Snippets::CreateService do
it_behaves_like 'snippet create data is tracked'
it_behaves_like 'an error service response when save fails'
it_behaves_like 'creates repository and files'
it_behaves_like 'after_save callback to store_mentions'
it_behaves_like 'after_save callback to store_mentions', ProjectSnippet
end
context 'when PersonalSnippet' do
@ -310,9 +310,7 @@ describe Snippets::CreateService do
it_behaves_like 'snippet create data is tracked'
it_behaves_like 'an error service response when save fails'
it_behaves_like 'creates repository and files'
pending('See https://gitlab.com/gitlab-org/gitlab/issues/30742') do
it_behaves_like 'after_save callback to store_mentions'
end
it_behaves_like 'after_save callback to store_mentions', PersonalSnippet
end
end
end

View File

@ -5,6 +5,7 @@ RSpec.shared_examples 'model with repository' do
let(:stubbed_container) { raise NotImplementedError }
let(:expected_full_path) { raise NotImplementedError }
let(:expected_web_url_path) { expected_full_path }
let(:expected_repo_url_path) { expected_full_path }
describe '#commits_by' do
let(:commits) { container.repository.commits('HEAD', limit: 3).commits }
@ -53,19 +54,19 @@ RSpec.shared_examples 'model with repository' do
describe '#url_to_repo' do
it 'returns the SSH URL to the repository' do
expect(container.url_to_repo).to eq("#{Gitlab.config.gitlab_shell.ssh_path_prefix}#{expected_web_url_path}.git")
expect(container.url_to_repo).to eq(container.ssh_url_to_repo)
end
end
describe '#ssh_url_to_repo' do
it 'returns the SSH URL to the repository' do
expect(container.ssh_url_to_repo).to eq(container.url_to_repo)
expect(container.ssh_url_to_repo).to eq("#{Gitlab.config.gitlab_shell.ssh_path_prefix}#{expected_repo_url_path}.git")
end
end
describe '#http_url_to_repo' do
it 'returns the HTTP URL to the repository' do
expect(container.http_url_to_repo).to eq("#{Gitlab.config.gitlab.url}/#{expected_web_url_path}.git")
expect(container.http_url_to_repo).to eq("#{Gitlab.config.gitlab.url}/#{expected_repo_url_path}.git")
end
end
@ -95,12 +96,8 @@ RSpec.shared_examples 'model with repository' do
end
context 'when the repo exists' do
it { expect(container.empty_repo?).to be(false) }
it 'returns true when repository is empty' do
allow(container.repository).to receive(:empty?).and_return(true)
expect(container.empty_repo?).to be(true)
it 'returns the empty state of the repository' do
expect(container.empty_repo?).to be(container.repository.empty?)
end
end
end
@ -146,15 +143,14 @@ RSpec.shared_examples 'model with repository' do
end
it 'picks storage from ApplicationSetting' do
expect_next_instance_of(ApplicationSetting) do |instance|
expect(instance).to receive(:pick_repository_storage).and_return('picked')
end
expect(Gitlab::CurrentSettings).to receive(:pick_repository_storage).and_return('picked')
expect(subject).to eq('picked')
end
it 'picks from the latest available storage', :request_store do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
Gitlab::CurrentSettings.expire_current_application_settings
Gitlab::CurrentSettings.current_application_settings
settings = ApplicationSetting.last

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
RSpec.shared_examples 'model with wiki' do
describe '#create_wiki' do
it 'returns true if the wiki repository already exists' do
expect(container.wiki_repository_exists?).to be(true)
expect(container.create_wiki).to be(true)
end
it 'returns true if the wiki repository was created' do
expect(container_without_wiki.wiki_repository_exists?).to be(false)
expect(container_without_wiki.create_wiki).to be(true)
expect(container_without_wiki.wiki_repository_exists?).to be(true)
end
context 'when the repository cannot be created' do
before do
expect(container.wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError }
end
it 'returns false and adds a validation error' do
expect(container.create_wiki).to be(false)
expect(container.errors[:base]).to contain_exactly('Failed to create wiki')
end
end
end
describe '#wiki_repository_exists?' do
it 'returns true when the wiki repository exists' do
expect(container.wiki_repository_exists?).to eq(true)
end
it 'returns false when the wiki repository does not exist' do
expect(container_without_wiki.wiki_repository_exists?).to eq(false)
end
end
describe 'wiki path conflict' do
context 'when the new path has been used by the wiki of other Project' do
it 'has an error on the name attribute' do
create(:project, namespace: container.parent, path: 'existing')
container.path = 'existing.wiki'
expect(container).not_to be_valid
expect(container.errors[:name].first).to eq(_('has already been taken'))
end
end
context 'when the new wiki path has been used by the path of other Project' do
it 'has an error on the name attribute' do
create(:project, namespace: container.parent, path: 'existing.wiki')
container.path = 'existing'
expect(container).not_to be_valid
expect(container.errors[:name].first).to eq(_('has already been taken'))
end
end
context 'when the new path has been used by the wiki of other Group' do
it 'has an error on the name attribute' do
create(:group, parent: container.parent, path: 'existing')
container.path = 'existing.wiki'
expect(container).not_to be_valid
expect(container.errors[:name].first).to eq(_('has already been taken'))
end
end
context 'when the new wiki path has been used by the path of other Group' do
it 'has an error on the name attribute' do
create(:group, parent: container.parent, path: 'existing.wiki')
container.path = 'existing'
expect(container).not_to be_valid
expect(container.errors[:name].first).to eq(_('has already been taken'))
end
end
end
end

View File

@ -210,6 +210,10 @@ RSpec.shared_examples 'mentions in description' do |mentionable_type|
it 'stores no mentions' do
expect(mentionable.user_mentions.count).to eq 0
end
it 'renders description_html correctly' do
expect(mentionable.description_html).to include("<a href=\"/#{user.username}\" data-user=\"#{user.id}\"")
end
end
end

View File

@ -0,0 +1,400 @@
# frozen_string_literal: true
RSpec.shared_examples 'wiki model' do
let_it_be(:user) { create(:user, :commit_email) }
let(:wiki_container) { raise NotImplementedError }
let(:wiki_container_without_repo) { raise NotImplementedError }
let(:wiki) { described_class.new(wiki_container, user) }
let(:commit) { subject.repository.head_commit }
subject { wiki }
it_behaves_like 'model with repository' do
let(:container) { wiki }
let(:stubbed_container) { described_class.new(wiki_container_without_repo, user) }
let(:expected_full_path) { "#{container.container.full_path}.wiki" }
let(:expected_web_url_path) { "#{container.container.web_url(only_path: true).sub(%r{^/}, '')}/-/wikis/home" }
end
describe '#repository' do
it 'returns a wiki repository' do
expect(subject.repository.repo_type).to be_wiki
end
end
describe '#full_path' do
it 'returns the container path with the .wiki extension' do
expect(subject.full_path).to eq(wiki_container.full_path + '.wiki')
end
end
describe '#wiki_base_path' do
it 'returns the wiki base path' do
expect(subject.wiki_base_path).to eq("#{wiki_container.web_url(only_path: true)}/-/wikis")
end
end
describe '#wiki' do
it 'contains a Gitlab::Git::Wiki instance' do
expect(subject.wiki).to be_a Gitlab::Git::Wiki
end
it 'creates a new wiki repo if one does not yet exist' do
expect(subject.create_page('index', 'test content')).to be_truthy
end
it 'creates a new wiki repo with a default commit message' do
expect(subject.create_page('index', 'test content', :markdown, '')).to be_truthy
page = subject.find_page('index')
expect(page.last_version.message).to eq("#{user.username} created page: index")
end
context 'when the repository cannot be created' do
let(:wiki_container) { wiki_container_without_repo }
before do
expect(subject.repository).to receive(:create_if_not_exists) { false }
end
it 'raises CouldNotCreateWikiError' do
expect { subject.wiki }.to raise_exception(Wiki::CouldNotCreateWikiError)
end
end
end
describe '#empty?' do
context 'when the wiki repository is empty' do
it 'returns true' do
expect(subject.empty?).to be(true)
end
end
context 'when the wiki has pages' do
before do
subject.create_page('index', 'This is an awesome new Gollum Wiki')
subject.create_page('another-page', 'This is another page')
end
describe '#empty?' do
it 'returns false' do
expect(subject.empty?).to be(false)
end
it 'only instantiates a Wiki page once' do
expect(WikiPage).to receive(:new).once.and_call_original
subject.empty?
end
end
end
end
describe '#list_pages' do
let(:wiki_pages) { subject.list_pages }
before do
create_page('index', 'This is an index')
create_page('index2', 'This is an index2')
create_page('an index3', 'This is an index3')
end
it 'returns an array of WikiPage instances' do
expect(wiki_pages.first).to be_a WikiPage
end
it 'does not load WikiPage content by default' do
wiki_pages.each do |page|
expect(page.content).to be_empty
end
end
it 'returns all pages by default' do
expect(wiki_pages.count).to eq(3)
end
context 'with limit option' do
it 'returns limited set of pages' do
expect(subject.list_pages(limit: 1).count).to eq(1)
end
end
context 'with sorting options' do
it 'returns pages sorted by title by default' do
pages = ['an index3', 'index', 'index2']
expect(subject.list_pages.map(&:title)).to eq(pages)
expect(subject.list_pages(direction: 'desc').map(&:title)).to eq(pages.reverse)
end
it 'returns pages sorted by created_at' do
pages = ['index', 'index2', 'an index3']
expect(subject.list_pages(sort: 'created_at').map(&:title)).to eq(pages)
expect(subject.list_pages(sort: 'created_at', direction: 'desc').map(&:title)).to eq(pages.reverse)
end
end
context 'with load_content option' do
let(:pages) { subject.list_pages(load_content: true) }
it 'loads WikiPage content' do
expect(pages.first.content).to eq('This is an index3')
expect(pages.second.content).to eq('This is an index')
expect(pages.third.content).to eq('This is an index2')
end
end
end
describe '#find_page' do
before do
create_page('index page', 'This is an awesome Gollum Wiki')
end
it 'returns the latest version of the page if it exists' do
page = subject.find_page('index page')
expect(page.title).to eq('index page')
end
it 'returns nil if the page does not exist' do
expect(subject.find_page('non-existent')).to eq(nil)
end
it 'can find a page by slug' do
page = subject.find_page('index-page')
expect(page.title).to eq('index page')
end
it 'returns a WikiPage instance' do
page = subject.find_page('index page')
expect(page).to be_a WikiPage
end
context 'pages with multibyte-character title' do
before do
create_page('autre pagé', "C'est un génial Gollum Wiki")
end
it 'can find a page by slug' do
page = subject.find_page('autre pagé')
expect(page.title).to eq('autre pagé')
end
end
context 'pages with invalidly-encoded content' do
before do
create_page('encoding is fun', "f\xFCr".b)
end
it 'can find the page' do
page = subject.find_page('encoding is fun')
expect(page.content).to eq('fr')
end
end
end
describe '#find_sidebar' do
before do
create_page(described_class::SIDEBAR, 'This is an awesome Sidebar')
end
it 'finds the page defined as _sidebar' do
page = subject.find_page('_sidebar')
expect(page.content).to eq('This is an awesome Sidebar')
end
end
describe '#find_file' do
let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
before do
subject.wiki # Make sure the wiki repo exists
subject.repository.create_file(user, 'image.png', image, branch_name: subject.default_branch, message: 'add image')
end
it 'returns the latest version of the file if it exists' do
file = subject.find_file('image.png')
expect(file.mime_type).to eq('image/png')
end
it 'returns nil if the page does not exist' do
expect(subject.find_file('non-existent')).to eq(nil)
end
it 'returns a Gitlab::Git::WikiFile instance' do
file = subject.find_file('image.png')
expect(file).to be_a Gitlab::Git::WikiFile
end
it 'returns the whole file' do
file = subject.find_file('image.png')
image.rewind
expect(file.raw_data.b).to eq(image.read.b)
end
end
describe '#create_page' do
it 'creates a new wiki page' do
expect(subject.create_page('test page', 'this is content')).not_to eq(false)
expect(subject.list_pages.count).to eq(1)
end
it 'returns false when a duplicate page exists' do
subject.create_page('test page', 'content')
expect(subject.create_page('test page', 'content')).to eq(false)
end
it 'stores an error message when a duplicate page exists' do
2.times { subject.create_page('test page', 'content') }
expect(subject.error_message).to match(/Duplicate page:/)
end
it 'sets the correct commit message' do
subject.create_page('test page', 'some content', :markdown, 'commit message')
expect(subject.list_pages.first.page.version.message).to eq('commit message')
end
it 'sets the correct commit email' do
subject.create_page('test page', 'content')
expect(user.commit_email).not_to eq(user.email)
expect(commit.author_email).to eq(user.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
end
it 'updates container activity' do
expect(subject).to receive(:update_container_activity)
subject.create_page('Test Page', 'This is content')
end
end
describe '#update_page' do
before do
create_page('update-page', 'some content')
@gitlab_git_wiki_page = subject.wiki.page(title: 'update-page')
subject.update_page(
@gitlab_git_wiki_page,
content: 'some other content',
format: :markdown,
message: 'updated page'
)
@page = subject.list_pages(load_content: true).first.page
end
it 'updates the content of the page' do
expect(@page.raw_data).to eq('some other content')
end
it 'sets the correct commit message' do
expect(@page.version.message).to eq('updated page')
end
it 'sets the correct commit email' do
expect(user.commit_email).not_to eq(user.email)
expect(commit.author_email).to eq(user.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
end
it 'updates container activity' do
expect(subject).to receive(:update_container_activity)
subject.update_page(
@gitlab_git_wiki_page,
content: 'Yet more content',
format: :markdown,
message: 'Updated page again'
)
end
end
describe '#delete_page' do
before do
create_page('index', 'some content')
@page = subject.wiki.page(title: 'index')
end
it 'deletes the page' do
subject.delete_page(@page)
expect(subject.list_pages.count).to eq(0)
end
it 'sets the correct commit email' do
subject.delete_page(@page)
expect(user.commit_email).not_to eq(user.email)
expect(commit.author_email).to eq(user.commit_email)
expect(commit.committer_email).to eq(user.commit_email)
end
it 'updates container activity' do
expect(subject).to receive(:update_container_activity)
subject.delete_page(@page)
end
end
describe '#ensure_repository' do
context 'if the repository exists' do
it 'does not create the repository' do
expect(subject.repository.exists?).to eq(true)
expect(subject.repository.raw).not_to receive(:create_repository)
subject.ensure_repository
end
end
context 'if the repository does not exist' do
let(:wiki_container) { wiki_container_without_repo }
it 'creates the repository' do
expect(subject.repository.exists?).to eq(false)
subject.ensure_repository
expect(subject.repository.exists?).to eq(true)
end
end
end
describe '#hook_attrs' do
it 'returns a hash with values' do
expect(subject.hook_attrs).to be_a Hash
expect(subject.hook_attrs.keys).to contain_exactly(:web_url, :git_ssh_url, :git_http_url, :path_with_namespace, :default_branch)
end
end
private
def create_temp_repo(path)
FileUtils.mkdir_p path
system(*%W(#{Gitlab.config.git.bin_path} init --quiet --bare -- #{path}))
end
def remove_temp_repo(path)
FileUtils.rm_rf path
end
def commit_details
Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.commit_email, 'test commit')
end
def create_page(name, content)
subject.wiki.write_page(name, :markdown, content, commit_details)
end
end