Commit b63f476d authored by Francisco Javier López's avatar Francisco Javier López Committed by Douglas Barbosa Alexandre

Include group wikis in Geo replication

In this commit, we add the logic to include group wikis in Geo
replication.
parent 50aa76a5
......@@ -20,6 +20,7 @@ ActiveSupport::Inflector.inflections do |inflect|
event_log
file_registry
group_view
group_wiki_repository_registry
job_artifact_registry
lfs_object_registry
package_file_registry
......
......@@ -217,6 +217,12 @@ configuration option in `gitlab.yml`. These metrics are served from the
| `geo_snippet_repositories_synced` | Gauge | 13.4 | Number of syncable snippets synced on secondary | `url` |
| `geo_snippet_repositories_failed` | Gauge | 13.4 | Number of syncable snippets failed on secondary | `url` |
| `geo_snippet_repositories_registry` | Gauge | 13.4 | Number of syncable snippets in the registry | `url` |
| `geo_group_wiki_repositories` | Gauge | 13.10 | Number of group wikis on primary | `url` |
| `geo_group_wiki_repositories_checksummed` | Gauge | 13.10 | Number of group wikis checksummed on primary | `url` |
| `geo_group_wiki_repositories_checksum_failed` | Gauge | 13.10 | Number of group wikis failed to calculate the checksum on primary | `url` |
| `geo_group_wiki_repositories_synced` | Gauge | 13.10 | Number of syncable group wikis synced on secondary | `url` |
| `geo_group_wiki_repositories_failed` | Gauge | 13.10 | Number of syncable group wikis failed on secondary | `url` |
| `geo_group_wiki_repositories_registry` | Gauge | 13.10 | Number of syncable group wikis in the registry | `url` |
| `limited_capacity_worker_running_jobs` | Gauge | 13.5 | Number of running jobs | `worker` |
| `limited_capacity_worker_max_running_jobs` | Gauge | 13.5 | Maximum number of running jobs | `worker` |
| `limited_capacity_worker_remaining_work_count` | Gauge | 13.5 | Number of jobs waiting to be enqueued | `worker` |
......
......@@ -383,7 +383,12 @@ Example response:
"snippet_repositories_checksum_failed_count": 0,
"snippet_repositories_registry_count": 10,
"snippet_repositories_synced_count": 6,
"snippet_repositories_failed_count": 3
"snippet_repositories_failed_count": 3,
"group_wiki_repositories_checksummed_count": 10,
"group_wiki_repositories_checksum_failed_count": 0,
"group_wiki_repositories_registry_count": 10,
"group_wiki_repositories_synced_count": 6,
"group_wiki_repositories_failed_count": 3
},
{
"geo_node_id": 2,
......@@ -477,7 +482,12 @@ Example response:
"snippet_repositories_checksum_failed_count": 0,
"snippet_repositories_registry_count": 10,
"snippet_repositories_synced_count": 6,
"snippet_repositories_failed_count": 3
"snippet_repositories_failed_count": 3,
"group_wiki_repositories_checksummed_count": 10,
"group_wiki_repositories_checksum_failed_count": 0,
"group_wiki_repositories_registry_count": 10,
"group_wiki_repositories_synced_count": 6,
"group_wiki_repositories_failed_count": 3
}
]
```
......
......@@ -2139,6 +2139,7 @@ Represents an external issue.
| `containerRepositoriesMaxCapacity` | Int | The maximum concurrency of container repository sync for this secondary node. |
| `enabled` | Boolean | Indicates whether this Geo node is enabled. |
| `filesMaxCapacity` | Int | The maximum concurrency of LFS/attachment backfill for this secondary node. |
| `groupWikiRepositoryRegistries` | GroupWikiRepositoryRegistryConnection | Find group wiki repository registries on this Geo node. Available only when feature flag `geo_group_wiki_repository_replication` is enabled. |
| `id` | ID! | ID of this GeoNode. |
| `internalUrl` | String | The URL defined on the primary node that secondary nodes should use to contact it. |
| `mergeRequestDiffRegistries` | MergeRequestDiffRegistryConnection | Find merge request diff registries on this Geo node. |
......@@ -2282,6 +2283,21 @@ Contains statistics about a group.
| ----- | ---- | ----------- |
| `releaseStats` | GroupReleaseStats | Statistics related to releases within the group. |
### `GroupWikiRepositoryRegistry`
Represents the Geo sync and verification state of a group wiki repository.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `createdAt` | Time | Timestamp when the GroupWikiRepositoryRegistry was created |
| `groupWikiRepositoryId` | ID! | ID of the Group Wiki Repository. |
| `id` | ID! | ID of the GroupWikiRepositoryRegistry |
| `lastSyncFailure` | String | Error message during sync of the GroupWikiRepositoryRegistry |
| `lastSyncedAt` | Time | Timestamp of the most recent successful sync of the GroupWikiRepositoryRegistry |
| `retryAt` | Time | Timestamp after which the GroupWikiRepositoryRegistry should be resynced |
| `retryCount` | Int | Number of consecutive failed sync attempts of the GroupWikiRepositoryRegistry |
| `state` | RegistryState | Sync state of the GroupWikiRepositoryRegistry |
### `HttpIntegrationCreatePayload`
Autogenerated return type of HttpIntegrationCreate.
......
# frozen_string_literal: true
module Geo
class GroupWikiRepositoryRegistryFinder
include FrameworkRegistryFinder
end
end
# frozen_string_literal: true
module Resolvers
module Geo
class GroupWikiRepositoryRegistriesResolver < BaseResolver
type ::Types::Geo::GeoNodeType.connection_type, null: true
include RegistriesResolver
end
end
end
......@@ -38,6 +38,11 @@ module Types
null: true,
resolver: ::Resolvers::Geo::TerraformStateVersionRegistriesResolver,
description: 'Find terraform state version registries on this Geo node.'
field :group_wiki_repository_registries, ::Types::Geo::GroupWikiRepositoryRegistryType.connection_type,
null: true,
resolver: ::Resolvers::Geo::GroupWikiRepositoryRegistriesResolver,
description: 'Find group wiki repository registries on this Geo node.',
feature_flag: :geo_group_wiki_repository_replication
end
end
end
# frozen_string_literal: true
module Types
module Geo
# rubocop:disable Graphql/AuthorizeTypes because it is included
class GroupWikiRepositoryRegistryType < BaseObject
include ::Types::Geo::RegistryType
graphql_name 'GroupWikiRepositoryRegistry'
description 'Represents the Geo sync and verification state of a group wiki repository'
field :group_wiki_repository_id, GraphQL::ID_TYPE, null: false, description: 'ID of the Group Wiki Repository.'
end
end
end
# frozen_string_literal: true
class Geo::GroupWikiRepositoryRegistry < Geo::BaseRegistry
include Geo::ReplicableRegistry
MODEL_CLASS = ::GroupWikiRepository
MODEL_FOREIGN_KEY = :group_wiki_repository_id
belongs_to :group_wiki_repository, class_name: 'GroupWikiRepository'
end
# frozen_string_literal: true
class GroupWikiRepository < ApplicationRecord
include ::Gitlab::Geo::ReplicableModel
include EachBatch
include Shardable
with_replicator Geo::GroupWikiRepositoryReplicator
belongs_to :group
validates :group, :disk_path, presence: true, uniqueness: true
delegate :repository_storage, to: :group
def self.replicables_for_current_secondary(primary_key_in)
node = ::Gitlab::Geo.current_node
replicables = if !node.selective_sync?
all
elsif node.selective_sync_by_namespaces?
group_wiki_repositories_for_selected_namespaces
elsif node.selective_sync_by_shards?
group_wiki_repositories_for_selected_shards
else
self.none
end
replicables.primary_key_in(primary_key_in)
end
def self.group_wiki_repositories_for_selected_namespaces
self.joins(:group).where(group_id: ::Gitlab::Geo.current_node.namespaces_for_group_owned_replicables.select(:id))
end
def self.group_wiki_repositories_for_selected_shards
self.for_repository_storage(::Gitlab::Geo.current_node.selective_sync_shards)
end
def pool_repository
nil
end
def repository
group.wiki.repository
end
end
# frozen_string_literal: true
module Geo
class GroupWikiRepositoryReplicator < Gitlab::Geo::Replicator
include ::Geo::RepositoryReplicatorStrategy
def self.model
::GroupWikiRepository
end
def self.git_access_class
::Gitlab::GitAccessWiki
end
def repository
model_record.repository
end
def self.replication_enabled_by_default?
false
end
end
end
......@@ -10,7 +10,13 @@ module EE
super.tap do |group|
delete_dependency_proxy_blobs(group)
log_audit_event unless group&.persisted?
unless group&.persisted?
log_audit_event
if ::Gitlab::Geo.primary? && group.group_wiki_repository
group.group_wiki_repository.replicator.handle_after_destroy
end
end
end
end
......
......@@ -26,12 +26,13 @@ module EE
def process_wiki_changes(post_received, wiki)
super
# TODO: Support Geo for group wikis.
# https://gitlab.com/gitlab-org/gitlab/-/issues/208147
return unless wiki.is_a?(ProjectWiki)
return unless ::Gitlab::Geo.primary?
if ::Gitlab::Geo.primary?
if wiki.is_a?(ProjectWiki)
::Geo::RepositoryUpdatedService.new(wiki.repository).execute
else
group_wiki_repository = wiki.group.group_wiki_repository
group_wiki_repository.replicator.handle_after_update if group_wiki_repository
end
end
......
......@@ -25,7 +25,8 @@ module Geo
Geo::ProjectRegistry,
Geo::TerraformStateVersionRegistry,
Geo::UploadRegistry,
Geo::SnippetRepositoryRegistry
Geo::SnippetRepositoryRegistry,
Geo::GroupWikiRepositoryRegistry
].freeze
BATCH_SIZE = 10000
......
---
title: Include group wikis in Geo replication
merge_request: 54914
author:
type: added
---
name: geo_group_wiki_repository_replication
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54914
rollout_issue_url:
milestone: '13.10'
type: development
group: group::geo
default_enabled: false
# frozen_string_literal: true
class CreateGroupWikiRepositoryRegistry < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :group_wiki_repository_registry, id: :bigserial, force: :cascade do |t|
t.datetime_with_timezone :retry_at
t.datetime_with_timezone :last_synced_at
t.datetime_with_timezone :created_at, null: false
t.bigint :group_wiki_repository_id, null: false
t.integer :state, default: 0, null: false, limit: 2
t.integer :retry_count, default: 0, limit: 2
t.text :last_sync_failure
t.boolean :force_to_redownload
t.boolean :missing_on_primary
t.index :group_wiki_repository_id, name: :index_g_wiki_repository_registry_on_group_wiki_repository_id, unique: true
t.index :retry_at
t.index :state
end
add_text_limit :group_wiki_repository_registry, :last_sync_failure, 255
end
def down
drop_table :group_wiki_repository_registry
end
end
......@@ -63,6 +63,21 @@ ActiveRecord::Schema.define(version: 2021_02_25_200858) do
t.index ["success"], name: "index_file_registry_on_success"
end
create_table "group_wiki_repository_registry", force: :cascade do |t|
t.datetime_with_timezone "retry_at"
t.datetime_with_timezone "last_synced_at"
t.datetime_with_timezone "created_at", null: false
t.bigint "group_wiki_repository_id", null: false
t.integer "state", limit: 2, default: 0, null: false
t.integer "retry_count", limit: 2, default: 0
t.text "last_sync_failure"
t.boolean "force_to_redownload"
t.boolean "missing_on_primary"
t.index ["group_wiki_repository_id"], name: "index_g_wiki_repository_registry_on_group_wiki_repository_id", unique: true
t.index ["retry_at"], name: "index_group_wiki_repository_registry_on_retry_at"
t.index ["state"], name: "index_group_wiki_repository_registry_on_state"
end
create_table "job_artifact_registry", id: :serial, force: :cascade do |t|
t.datetime_with_timezone "created_at"
t.datetime_with_timezone "retry_at"
......
......@@ -57,6 +57,8 @@ module EE
end
def can_read_group?
return true if geo?
if user
user.can?(:read_group, group)
else
......
......@@ -23,7 +23,8 @@ module Gitlab
::Geo::MergeRequestDiffReplicator,
::Geo::PackageFileReplicator,
::Geo::TerraformStateVersionReplicator,
::Geo::SnippetRepositoryReplicator
::Geo::SnippetRepositoryReplicator,
::Geo::GroupWikiRepositoryReplicator
].freeze
def self.current_node
......
# frozen_string_literal: true
FactoryBot.define do
factory :geo_group_wiki_repository_registry, class: 'Geo::GroupWikiRepositoryRegistry' do
group_wiki_repository
state { Geo::GroupWikiRepositoryRegistry.state_value(:pending) }
trait :synced do
state { Geo::GroupWikiRepositoryRegistry.state_value(:synced) }
last_synced_at { 5.days.ago }
end
trait :failed do
state { Geo::GroupWikiRepositoryRegistry.state_value(:failed) }
last_synced_at { 1.day.ago }
retry_count { 2 }
last_sync_failure { 'Random error' }
end
trait :started do
state { Geo::GroupWikiRepositoryRegistry.state_value(:started) }
last_synced_at { 1.day.ago }
retry_count { 0 }
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :group_wiki_repository do
group
after(:build) do |group_wiki_repository, _|
group_wiki_repository.shard_name = group_wiki_repository.repository_storage
group_wiki_repository.disk_path = group_wiki_repository.group.wiki.storage.disk_path
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::GroupWikiRepositoryRegistryFinder do
it_behaves_like 'a framework registry finder', :geo_group_wiki_repository_registry
end
......@@ -96,6 +96,18 @@
"snippet_repositories_verification_total_count",
"snippet_repositories_verified_count",
"snippet_repositories_verified_in_percentage",
"group_wiki_repositories_count",
"group_wiki_repositories_checksum_total_count",
"group_wiki_repositories_checksummed_count",
"group_wiki_repositories_checksum_failed_count",
"group_wiki_repositories_synced_count",
"group_wiki_repositories_failed_count",
"group_wiki_repositories_registry_count",
"group_wiki_repositories_verification_total_count",
"group_wiki_repositories_verified_count",
"group_wiki_repositories_verification_failed_count",
"group_wiki_repositories_synced_in_percentage",
"group_wiki_repositories_verified_in_percentage",
"repositories_verified_count",
"repositories_verification_failed_count",
"repositories_verification_total_count",
......@@ -229,6 +241,18 @@
"snippet_repositories_verification_total_count": { "type": ["integer", "null"] },
"snippet_repositories_verified_count": { "type": ["integer", "null"] },
"snippet_repositories_verified_in_percentage": { "type": "string" },
"group_wiki_repositories_count": { "type": ["integer", "null"] },
"group_wiki_repositories_checksummed_count": { "type": ["integer", "null"] },
"group_wiki_repositories_checksum_failed_count": { "type": ["integer", "null"] },
"group_wiki_repositories_checksum_total_count": { "type": ["integer", "null"] },
"group_wiki_repositories_registry_count": { "type": ["integer", "null"] },
"group_wiki_repositories_failed_count": { "type": ["integer", "null"] },
"group_wiki_repositories_synced_count": { "type": ["integer", "null"] },
"group_wiki_repositories_synced_in_percentage": { "type": "string" },
"group_wiki_repositories_verification_failed_count": { "type": ["integer", "null"] },
"group_wiki_repositories_verification_total_count": { "type": ["integer", "null"] },
"group_wiki_repositories_verified_count": { "type": ["integer", "null"] },
"group_wiki_repositories_verified_in_percentage": { "type": "string" },
"repositories_verified_count": { "type": ["integer", "null"] },
"repositories_verification_failed_count": { "type": ["integer", "null"] },
"repositories_verification_total_count": { "type": ["integer", "null"] },
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Geo::GroupWikiRepositoryRegistriesResolver do
it_behaves_like 'a Geo registries resolver', :geo_group_wiki_repository_registry
end
......@@ -13,7 +13,7 @@ RSpec.describe GitlabSchema.types['GeoNode'] do
selective_sync_type selective_sync_shards selective_sync_namespaces
minimum_reverification_interval merge_request_diff_registries
package_file_registries snippet_repository_registries
terraform_state_version_registries
terraform_state_version_registries group_wiki_repository_registries
]
expect(described_class).to have_graphql_fields(*expected_fields)
......
......@@ -108,6 +108,14 @@ RSpec.describe Gitlab::GitAccessWiki do
end
end
context 'when actor is geo' do
let(:user) { :geo }
it 'gives access to download wiki code' do
expect { subject }.not_to raise_error
end
end
context 'the group is public' do
let(:group) { create(:group, :public, :wiki_repo) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::GroupWikiRepositoryRegistry, :geo, type: :model do
let_it_be(:registry) { create(:geo_group_wiki_repository_registry) }
specify 'factory is valid' do
expect(registry).to be_valid
end
include_examples 'a Geo framework registry'
end
......@@ -1162,6 +1162,7 @@ RSpec.describe GeoNodeStatus, :geo do
Geo::PackageFileReplicator | :package_file | :geo_package_file_registry
Geo::TerraformStateVersionReplicator | :terraform_state_version | :geo_terraform_state_version_registry
Geo::SnippetRepositoryReplicator | :snippet_repository | :geo_snippet_repository_registry
Geo::GroupWikiRepositoryReplicator | :group_wiki_repository | :geo_group_wiki_repository_registry
end
with_them do
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe GroupWikiRepository do
RSpec.describe GroupWikiRepository, :geo do
describe 'associations' do
it { is_expected.to belong_to(:shard) }
it { is_expected.to belong_to(:group) }
......@@ -20,4 +20,109 @@ RSpec.describe GroupWikiRepository do
it { is_expected.to validate_uniqueness_of(:disk_path) }
end
end
describe 'Geo Replication' do
include EE::GeoHelpers
let(:node) { create(:geo_node) }
before do
stub_current_geo_node(node)
end
context 'with root group and subgroup wikis' do
let_it_be(:root_group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: root_group) }
let_it_be(:root_group_wiki_repository) { create(:group_wiki_repository, group: root_group) }
let_it_be(:subgroup_wiki_repository) { create(:group_wiki_repository, group: subgroup) }
let_it_be(:broken_wiki_repository) { create(:group_wiki_repository, shard_name: 'broken') }
describe '#in_replicables_for_current_secondary?' do
it 'all returns true if all are replicated' do
[
root_group_wiki_repository,
subgroup_wiki_repository,
broken_wiki_repository
].each do |repository|
expect(repository.in_replicables_for_current_secondary?).to be true
end
end
context 'with selective sync by namespace' do
before do
node.update!(selective_sync_type: 'namespaces', namespaces: [root_group])
end
it 'returns true for groups' do
expect(root_group_wiki_repository.in_replicables_for_current_secondary?).to be true
end
it 'returns true for subgroups' do
expect(subgroup_wiki_repository.in_replicables_for_current_secondary?).to be true
end
end
context 'with selective sync by shard' do
before do
node.update!(selective_sync_type: 'shards', selective_sync_shards: ['default'])
end
it 'returns true for groups in the shard' do
expect(root_group_wiki_repository.in_replicables_for_current_secondary?).to be true
expect(subgroup_wiki_repository.in_replicables_for_current_secondary?).to be true
end
it 'returns false for group wiki repositories not in an included shard' do
expect(broken_wiki_repository.in_replicables_for_current_secondary?).to be false
end
end
end
describe '#replicables_for_current_secondary' do
it 'returns all group wiki repositories without selective sync' do
expect(described_class.replicables_for_current_secondary(1..described_class.last.id)).to match_array([
root_group_wiki_repository,
subgroup_wiki_repository,
broken_wiki_repository
])
end
context 'with selective sync by namespace' do
it 'returns group wiki repositories that belong to the namespaces and descendants' do
node.update!(selective_sync_type: 'namespaces', namespaces: [root_group])
expect(described_class.replicables_for_current_secondary(1..described_class.last.id)).to match_array([
root_group_wiki_repository,
subgroup_wiki_repository
])
end
it 'returns group wiki repositories that belong to the namespace' do
node.update!(selective_sync_type: 'namespaces', namespaces: [subgroup])
expect(described_class.replicables_for_current_secondary(1..described_class.last.id)).to match_array([
subgroup_wiki_repository
])
end
end
context 'with selective sync by shard' do
it 'returns group wiki repositories that belong to the shards' do
node.update!(selective_sync_type: 'shards', selective_sync_shards: ['default'])
expect(described_class.replicables_for_current_secondary(1..described_class.last.id)).to match_array([
root_group_wiki_repository,
subgroup_wiki_repository
])
end
end
it 'returns nothing if an unrecognised selective sync type is used' do
node.update_attribute(:selective_sync_type, 'unknown')
expect(described_class.replicables_for_current_secondary(1..described_class.last.id)).to be_empty
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Geo::GroupWikiRepositoryReplicator do
let(:model_record) { build(:group_wiki_repository, group: create(:group)) }
include_examples 'a repository replicator'
end
......@@ -30,4 +30,11 @@ RSpec.describe 'Gets registries' do
registry_factory: :geo_terraform_state_version_registry,
registry_foreign_key_field_name: 'terraformStateVersionId'
}
it_behaves_like 'gets registries for', {
field_name: 'groupWikiRepositoryRegistries',
registry_class_name: 'GroupWikiRepositoryRegistry',
registry_factory: :geo_group_wiki_repository_registry,
registry_foreign_key_field_name: 'groupWikiRepositoryId'
}
end
......@@ -45,4 +45,36 @@ RSpec.describe Groups::DestroyService do
expect { subject.execute }.to change { DependencyProxy::Blob.count }.by(-1)
end
end
context 'when on a Geo primary node' do
before do
allow(Gitlab::Geo).to receive(:primary?) { true }
end
context 'when group_wiki_repository does not exist' do
it 'does not call replicator to update Geo' do
expect_next_instance_of(Geo::GroupWikiRepositoryReplicator).never
subject.execute
end
end
it 'calls replicator to update Geo' do
group.wiki.create_wiki_repository
expect(group.group_wiki_repository.replicator).to receive(:handle_after_destroy)
subject.execute
end
end
context 'when not on a Geo primary node' do
it 'does not call replicator to update Geo' do
group.wiki.create_wiki_repository
expect(group.group_wiki_repository.replicator).not_to receive(:handle_after_destroy)
subject.execute
end
end
end
......@@ -139,12 +139,60 @@ RSpec.describe PostReceive do
described_class.new.perform(gl_repository, key_id, base64_changes)
end
it 'does not call Geo::RepositoryUpdatedService when running on a Geo primary node' do
allow(Gitlab::Geo).to receive(:primary?) { true }
context 'when on a Geo primary node' do
before do
allow(Gitlab::Geo).to receive(:primary?) { true }
end
expect_next_instance_of(::Geo::RepositoryUpdatedService).never
it 'does not call Geo::RepositoryUpdatedService' do
expect_next_instance_of(::Geo::RepositoryUpdatedService).never
described_class.new.perform(gl_repository, key_id, base64_changes)
described_class.new.perform(gl_repository, key_id, base64_changes)
end
context 'when wiki is a project wiki' do
let(:wiki) { build(:project_wiki, project: project) }
it 'does not call replicator to update Geo' do
expect_next_instance_of(Geo::GroupWikiRepositoryReplicator).never
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
context 'when group_wiki_repository does not exist' do
it 'does not call replicator to update Geo' do
expect(group.group_wiki_repository).to be_nil
expect_next_instance_of(Geo::GroupWikiRepositoryReplicator).never
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
context 'when group_wiki_repository exists' do
it 'calls replicator to update Geo' do
wiki.create_wiki_repository
expect(group.group_wiki_repository).to be_present
expect_next_instance_of(Geo::GroupWikiRepositoryReplicator) do |instance|
expect(instance).to receive(:handle_after_update)
end
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
end
context 'when not on a Geo primary node' do
it 'does not call replicator to update Geo' do
wiki.create_wiki_repository
expect(group.group_wiki_repository).to be_present
expect_next_instance_of(Geo::GroupWikiRepositoryReplicator).never
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
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