Commit fe5533a7 authored by Markus Koller's avatar Markus Koller

Add group wiki model

- Refactor existing `ProjectWiki` into `Wiki`, and add new child classes
  `ProjectWiki` and `GroupWiki`.
- Rename `project` to `container` in the wiki classes.
- Add `HasWiki` concern for use in `Project` and `Group`.
- Add `@groups` prefix in `Storage::Hashed`.
- Refactor existing specs into shared examples.
parent 61cdcb8f
......@@ -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'
......
......@@ -9,7 +9,6 @@
# needs any special behavior.
module HasRepository
extend ActiveSupport::Concern
include AfterCommitQueue
include Referable
include Gitlab::ShellAdapter
include Gitlab::Utils::StrongMemoize
......
# 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
# frozen_string_literal: true
module Storage
module LegacyProjectWiki
extend ActiveSupport::Concern
def disk_path
project.disk_path + '.wiki'
end
end
end
......@@ -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
......
# 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
......@@ -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,
......@@ -1051,16 +1052,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
......@@ -1560,10 +1551,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?
......@@ -1577,20 +1564,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
......@@ -2420,6 +2393,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)
......
# 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')
......@@ -15,6 +15,7 @@ class Snippet < ApplicationRecord
include FromUnion
include IgnorableColumns
include HasRepository
include AfterCommitQueue
extend ::Gitlab::Utils::Override
MAX_FILE_COUNT = 1
......
......@@ -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'
......
# 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')
......@@ -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
......
# frozen_string_literal: true
class WikiPagePolicy < BasePolicy
delegate { @subject.wiki.project }
delegate { @subject.wiki.container }
rule { can?(:read_wiki) }.enable :read_wiki_page
end
......@@ -3,22 +3,11 @@
module EE
module ProjectWiki
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
prepended do
# TODO: Move this into EE::Wiki once we implement ES support for group wikis.
# https://gitlab.com/gitlab-org/gitlab/-/issues/207889
include Elastic::WikiRepositoriesSearch
end
# No need to have a Kerberos Web url. Kerberos URL will be used only to
# clone
def kerberos_url_to_repo
[::Gitlab.config.build_gitlab_kerberos_url, '/', full_path, '.git'].join('')
end
def path_to_repo
@path_to_repo ||=
File.join(::Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path,
"#{disk_path}.git")
end
end
end
# frozen_string_literal: true
module EE
module Wiki
extend ActiveSupport::Concern
# No need to have a Kerberos Web url. Kerberos URL will be used only to
# clone
def kerberos_url_to_repo
[::Gitlab.config.build_gitlab_kerberos_url, '/', full_path, '.git'].join('')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GroupWiki do
it_behaves_like 'EE wiki model' do
let(:wiki_container) { create(:group, :wiki_repo) }
before do
wiki_container.add_owner(user)
end
it 'does not use Elasticsearch' do
expect(subject).not_to be_a(Elastic::WikiRepositoriesSearch)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ProjectWiki do
it_behaves_like 'EE wiki model' do
let(:wiki_container) { create(:project, :wiki_repo, namespace: user.namespace) }
it 'uses Elasticsearch' do
expect(subject).to be_a(Elastic::WikiRepositoriesSearch)
end
end
end
# frozen_string_literal: true
require "spec_helper"
RSpec.shared_examples_for 'EE wiki model' do
let_it_be(:user) { create(:user) }
let(:wiki) { described_class.for_container(wiki_container, user) }
describe ProjectWiki do
let(:user) { create(:user, :commit_email) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:project_wiki) { described_class.new(project, user) }
subject { wiki }
subject { project_wiki }
describe "#kerberos_url_to_repo" do
describe '#kerberos_url_to_repo' do
it 'returns valid kerberos url for this repo' do
gitlab_kerberos_url = Gitlab.config.build_gitlab_kerberos_url
repo_kerberos_url = "#{gitlab_kerberos_url}/#{subject.full_path}.git"
......
......@@ -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
......
......@@ -8663,9 +8663,6 @@ msgstr ""
msgid "Failed Jobs"
msgstr ""
msgid "Failed create wiki"
msgstr ""
msgid "Failed to add a Zoom meeting"
msgstr ""
......@@ -8705,6 +8702,9 @@ msgstr ""
msgid "Failed to create resources"
msgstr ""
msgid "Failed to create wiki"
msgstr ""
msgid "Failed to delete board. Please try again."
msgstr ""
......
......@@ -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
......
......@@ -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
# 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
......@@ -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
......
# 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
......@@ -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 }
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......
......@@ -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}" }
......
......@@ -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) }
......
# 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
......@@ -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
......@@ -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
......@@ -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') }
......@@ -4728,20 +4712,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) }
......
# 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 }
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 '#empty?' do
subject { super().empty? }
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
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")
describe '#disk_path' do
it 'returns the repository storage path' do
expect(subject.disk_path).to eq("#{subject.container.disk_path}.wiki")
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')) }
describe '#update_container_activity' do
it 'updates project activity' do
wiki_container.update!(
last_activity_at: nil,
last_repository_updated_at: nil
)
before do
subject.wiki # Make sure the wiki repo exists
subject.create_page('Test Page', 'This is content')
wiki_container.reload
repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
subject.repository.path_to_repo
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
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
......@@ -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
......
......@@ -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
......
# 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
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment