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) }
......
This diff is collapsed.
......@@ -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
This diff is collapsed.
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