Skip to content
Snippets Groups Projects
Commit 256b9063 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot
Browse files

Merge remote-tracking branch 'dev/12-6-stable' into 12-6-stable

parents 52a6715d c1c07df4
No related branches found
No related tags found
No related merge requests found
Showing
with 559 additions and 52 deletions
Loading
Loading
@@ -5,5 +5,9 @@ require 'spec_helper'
describe GitlabSchema.types['DiffRefs'] do
it { expect(described_class.graphql_name).to eq('DiffRefs') }
 
it { expect(described_class).to have_graphql_fields(:base_sha, :head_sha, :start_sha) }
it { is_expected.to have_graphql_fields(:head_sha, :base_sha, :start_sha) }
it { expect(described_class.fields['headSha'].type).to be_non_null }
it { expect(described_class.fields['baseSha'].type).not_to be_non_null }
it { expect(described_class.fields['startSha'].type).to be_non_null }
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::AssetProxy do
context 'when asset proxy is disabled' do
before do
stub_asset_proxy_setting(enabled: false)
end
it 'returns the original URL' do
url = 'http://example.com/test.png'
expect(described_class.proxy_url(url)).to eq(url)
end
end
context 'when asset proxy is enabled' do
before do
stub_asset_proxy_setting(whitelist: %w(gitlab.com *.mydomain.com))
stub_asset_proxy_setting(
enabled: true,
url: 'https://assets.example.com',
secret_key: 'shared-secret',
domain_regexp: Banzai::Filter::AssetProxyFilter.compile_whitelist(Gitlab.config.asset_proxy.whitelist)
)
end
it 'returns a proxied URL' do
url = 'http://example.com/test.png'
proxied_url = 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67'
expect(described_class.proxy_url(url)).to eq(proxied_url)
end
context 'whitelisted domain' do
it 'returns original URL for single domain whitelist' do
url = 'http://gitlab.com/test.png'
expect(described_class.proxy_url(url)).to eq(url)
end
it 'returns original URL for wildcard subdomain whitelist' do
url = 'http://test.mydomain.com/test.png'
expect(described_class.proxy_url(url)).to eq(url)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizationsWithMinMaxUserId, :migration, schema: 20200204113224 do
let(:users_table) { table(:users) }
let(:min) { 1 }
let(:max) { 5 }
before do
min.upto(max) do |i|
users_table.create!(id: i, email: "user#{i}@example.com", projects_limit: 10)
end
end
describe '#perform' do
it 'initializes Users::RefreshAuthorizedProjectsService with correct users' do
min.upto(max) do |i|
user = User.find(i)
expect(Users::RefreshAuthorizedProjectsService).to(
receive(:new).with(user, any_args).and_call_original)
end
described_class.new.perform(min, max)
end
it 'executes Users::RefreshAuthorizedProjectsService' do
expected_call_counts = max - min + 1
service = instance_double(Users::RefreshAuthorizedProjectsService)
expect(Users::RefreshAuthorizedProjectsService).to(
receive(:new).exactly(expected_call_counts).times.and_return(service))
expect(service).to receive(:execute).exactly(expected_call_counts).times
described_class.new.perform(min, max)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::DependencyLinker::BaseLinker do
let(:linker_class) do
Class.new(described_class) do
def link_dependencies
link_regex(%r{^(?<name>https?://[^ ]+)}, &:itself)
end
end
end
let(:plain_content) do
<<~CONTENT
http://\\njavascript:alert(1)
https://gitlab.com/gitlab-org/gitlab
CONTENT
end
let(:highlighted_content) do
<<~CONTENT
<span><span>http://</span><span>\\n</span><span>javascript:alert(1)</span></span>
<span><span>https://gitlab.com/gitlab-org/gitlab</span></span>
CONTENT
end
let(:linker) { linker_class.new(plain_content, highlighted_content) }
describe '#link' do
subject { linker.link }
it 'only converts valid links' do
expect(subject).to eq(
<<~CONTENT
<span><span>#{link('http://')}</span><span>#{link('\n', url: '%5Cn')}</span><span>#{link('javascript:alert(1)', url: nil)}</span></span>
<span><span>#{link('https://gitlab.com/gitlab-org/gitlab')}</span></span>
CONTENT
)
end
end
def link(text, url: text)
attrs = [
'rel="nofollow noreferrer noopener"',
'target="_blank"'
]
attrs.unshift(%{href="#{url}"}) if url
%{<a #{attrs.join(' ')}>#{text}</a>}
end
end
Loading
Loading
@@ -109,6 +109,20 @@ describe Gitlab::ProjectAuthorizations do
end
end
 
context 'with lower group access level than max access level for share' do
let(:user) { create(:user) }
it 'creates proper authorizations' do
group.add_reporter(user)
mapping = map_access_levels(authorizations)
expect(mapping[project_parent.id]).to be_nil
expect(mapping[project.id]).to eq(Gitlab::Access::REPORTER)
expect(mapping[project_child.id]).to eq(Gitlab::Access::REPORTER)
end
end
context 'parent group user' do
let(:user) { parent_group_user }
 
Loading
Loading
Loading
Loading
@@ -30,6 +30,17 @@ describe Gitlab::UserAccess do
end
end
 
describe 'push to branch in an internal project' do
it 'will not infinitely loop when a project is internal' do
project.visibility_level = Gitlab::VisibilityLevel::INTERNAL
project.save!
expect(project).not_to receive(:branch_allows_collaboration?)
access.can_push_to_branch?('master')
end
end
describe 'push to empty project' do
let(:empty_project) { create(:project_empty_repo) }
let(:project_access) { described_class.new(user, project: empty_project) }
Loading
Loading
Loading
Loading
@@ -252,4 +252,18 @@ describe Gitlab::Utils do
expect(described_class.string_to_ip_object('1:0:0:0:0:0:0:0/124')).to eq(IPAddr.new('1:0:0:0:0:0:0:0/124'))
end
end
describe '.parse_url' do
it 'returns Addressable::URI object' do
expect(described_class.parse_url('http://gitlab.com')).to be_instance_of(Addressable::URI)
end
it 'returns nil when URI cannot be parsed' do
expect(described_class.parse_url('://gitlab.com')).to be nil
end
it 'returns nil with invalid parameter' do
expect(described_class.parse_url(1)).to be nil
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20200214085940_clean_grafana_url.rb')
describe CleanGrafanaUrl, :migration do
let(:application_settings_table) { table(:application_settings) }
[
'javascript:alert(window.opener.document.location)',
' javascript:alert(window.opener.document.location)'
].each do |grafana_url|
it "sets grafana_url back to its default value when grafana_url is '#{grafana_url}'" do
application_settings = application_settings_table.create!(grafana_url: grafana_url)
migrate!
expect(application_settings.reload.grafana_url).to eq('/-/grafana')
end
end
['/-/grafana', '/some/relative/url', 'http://localhost:9000'].each do |grafana_url|
it "does not modify grafana_url when grafana_url is '#{grafana_url}'" do
application_settings = application_settings_table.create!(grafana_url: grafana_url)
migrate!
expect(application_settings.reload.grafana_url).to eq(grafana_url)
end
end
context 'when application_settings table has no rows' do
it 'does not fail' do
migrate!
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20200204113224_schedule_recalculate_project_authorizations_second_run.rb')
describe ScheduleRecalculateProjectAuthorizationsSecondRun, :migration, :sidekiq do
let(:users_table) { table(:users) }
before do
stub_const("#{described_class}::BATCH_SIZE", 2)
1.upto(4) do |i|
users_table.create!(id: i, name: "user#{i}", email: "user#{i}@example.com", projects_limit: 1)
end
end
it 'schedules background migration' do
Sidekiq::Testing.fake! do
Timecop.freeze do
migrate!
expect(BackgroundMigrationWorker.jobs.size).to eq(2)
expect(described_class::MIGRATION).to be_scheduled_migration(1, 2)
expect(described_class::MIGRATION).to be_scheduled_migration(3, 4)
end
end
end
end
Loading
Loading
@@ -19,6 +19,7 @@ describe ApplicationSetting do
let(:http) { 'http://example.com' }
let(:https) { 'https://example.com' }
let(:ftp) { 'ftp://example.com' }
let(:javascript) { 'javascript:alert(window.opener.document.location)' }
 
it { is_expected.to allow_value(nil).for(:home_page_url) }
it { is_expected.to allow_value(http).for(:home_page_url) }
Loading
Loading
@@ -74,6 +75,53 @@ describe ApplicationSetting do
it { is_expected.not_to allow_value('abc').for(:minimum_password_length) }
it { is_expected.to allow_value(10).for(:minimum_password_length) }
 
context 'grafana_url validations' do
before do
subject.instance_variable_set(:@parsed_grafana_url, nil)
end
it { is_expected.to allow_value(http).for(:grafana_url) }
it { is_expected.to allow_value(https).for(:grafana_url) }
it { is_expected.not_to allow_value(ftp).for(:grafana_url) }
it { is_expected.not_to allow_value(javascript).for(:grafana_url) }
it { is_expected.to allow_value('/-/grafana').for(:grafana_url) }
it { is_expected.to allow_value('http://localhost:9000').for(:grafana_url) }
context 'when local URLs are not allowed in system hooks' do
before do
stub_application_setting(allow_local_requests_from_system_hooks: false)
end
it { is_expected.not_to allow_value('http://localhost:9000').for(:grafana_url) }
end
context 'with invalid grafana URL' do
it 'adds an error' do
subject.grafana_url = ' ' + http
expect(subject.save).to be false
expect(subject.errors[:grafana_url]).to eq([
'must be a valid relative or absolute URL. ' \
'Please check your Grafana URL setting in ' \
'Admin Area > Settings > Metrics and profiling > Metrics - Grafana'
])
end
end
context 'with blocked grafana URL' do
it 'adds an error' do
subject.grafana_url = javascript
expect(subject.save).to be false
expect(subject.errors[:grafana_url]).to eq([
'is blocked: Only allowed schemes are http, https. Please check your ' \
'Grafana URL setting in ' \
'Admin Area > Settings > Metrics and profiling > Metrics - Grafana'
])
end
end
end
context 'when snowplow is enabled' do
before do
setting.snowplow_enabled = true
Loading
Loading
Loading
Loading
@@ -91,6 +91,22 @@ describe Badge do
let(:method) { :image_url }
 
it_behaves_like 'rendered_links'
context 'when asset proxy is enabled' do
let(:placeholder_url) { 'http://www.example.com/image' }
before do
stub_asset_proxy_setting(
enabled: true,
url: 'https://assets.example.com',
secret_key: 'shared-secret'
)
end
it 'returns a proxied URL' do
expect(badge.rendered_image_url).to start_with('https://assets.example.com')
end
end
end
end
end
Loading
Loading
@@ -562,6 +562,18 @@ describe Group do
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::DEVELOPER)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::DEVELOPER)
end
context 'with lower group access level than max access level for share' do
let(:user) { create(:user) }
it 'returns correct access level' do
group.add_reporter(user)
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::REPORTER)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::REPORTER)
end
end
end
 
context 'with user in the parent group' do
Loading
Loading
@@ -583,6 +595,33 @@ describe Group do
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
end
end
context 'unrelated project owner' do
let(:common_id) { [Project.maximum(:id).to_i, Namespace.maximum(:id).to_i].max + 999 }
let!(:group) { create(:group, id: common_id) }
let!(:unrelated_project) { create(:project, id: common_id) }
let(:user) { unrelated_project.owner }
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
end
end
context 'user without accepted access request' do
let!(:user) { create(:user) }
before do
create(:group_member, :developer, :access_request, user: user, group: group)
end
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
expect(shared_group_child.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
end
end
end
 
context 'when feature flag share_group_with_group is disabled' do
Loading
Loading
Loading
Loading
@@ -65,10 +65,10 @@ describe GroupMember do
end
 
describe '#update_two_factor_requirement' do
let(:user) { build :user }
let(:group_member) { build :group_member, user: user }
it 'is called after creation and deletion' do
user = build :user
group_member = build :group_member, user: user
expect(user).to receive(:update_two_factor_requirement)
 
group_member.save
Loading
Loading
@@ -79,6 +79,21 @@ describe GroupMember do
end
end
 
describe '#after_accept_invite' do
it 'calls #update_two_factor_requirement' do
email = 'foo@email.com'
user = build(:user, email: email)
group = create(:group, require_two_factor_authentication: true)
group_member = create(:group_member, group: group, invite_token: '1234', invite_email: email)
expect(user).to receive(:require_two_factor_authentication_from_group).and_call_original
group_member.accept_invite!(user)
expect(user.require_two_factor_authentication_from_group).to be_truthy
end
end
context 'access levels' do
context 'with parent group' do
it_behaves_like 'inherited access level as a member of entity' do
Loading
Loading
Loading
Loading
@@ -4766,6 +4766,38 @@ describe Project do
end
end
 
context 'with cross internal project merge requests' do
let(:project) { create(:project, :repository, :internal) }
let(:forked_project) { fork_project(project, nil, repository: true) }
let(:user) { double(:user) }
it "does not endlessly loop for internal projects with MRs to each other", :sidekiq_inline do
allow(user).to receive(:can?).and_return(true, false, true)
allow(user).to receive(:id).and_return(1)
create(
:merge_request,
target_project: project,
target_branch: 'merge-test',
source_project: forked_project,
source_branch: 'merge-test',
allow_collaboration: true
)
create(
:merge_request,
target_project: forked_project,
target_branch: 'merge-test',
source_project: project,
source_branch: 'merge-test',
allow_collaboration: true
)
expect(user).to receive(:can?).at_most(5).times
project.branch_allows_collaboration?(user, "merge-test")
end
end
context 'with cross project merge requests' do
let(:user) { create(:user) }
let(:target_project) { create(:project, :repository) }
Loading
Loading
Loading
Loading
@@ -6,6 +6,7 @@ describe Ci::PipelinePresenter do
include Gitlab::Routing
 
let(:user) { create(:user) }
let(:current_user) { user }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
 
Loading
Loading
@@ -15,7 +16,7 @@ describe Ci::PipelinePresenter do
 
before do
project.add_developer(user)
allow(presenter).to receive(:current_user) { user }
allow(presenter).to receive(:current_user) { current_user }
end
 
it 'inherits from Gitlab::View::Presenter::Delegated' do
Loading
Loading
@@ -215,10 +216,90 @@ describe Ci::PipelinePresenter do
describe '#all_related_merge_requests' do
it 'memoizes the returned relation' do
query_count = ActiveRecord::QueryRecorder.new do
2.times { presenter.send(:all_related_merge_requests).count }
3.times { presenter.send(:all_related_merge_requests).count }
end.count
 
expect(query_count).to eq(1)
expect(query_count).to eq(2)
end
context 'permissions' do
let!(:merge_request) do
create(:merge_request, project: project, source_project: project)
end
subject(:all_related_merge_requests) do
presenter.send(:all_related_merge_requests)
end
shared_examples 'private merge requests' do
context 'when not logged in' do
let(:current_user) {}
it { is_expected.to be_empty }
end
context 'when logged in as a non_member' do
let(:current_user) { create(:user) }
it { is_expected.to be_empty }
end
context 'when logged in as a guest' do
let(:current_user) { create(:user) }
before do
project.add_guest(current_user)
end
it { is_expected.to be_empty }
end
context 'when logged in as a developer' do
it { is_expected.to contain_exactly(merge_request) }
end
context 'when logged in as a maintainer' do
let(:current_user) { create(:user) }
before do
project.add_maintainer(current_user)
end
it { is_expected.to contain_exactly(merge_request) }
end
end
context 'with a private project' do
it_behaves_like 'private merge requests'
end
context 'with a public project with private merge requests' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
project
.project_feature
.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
end
it_behaves_like 'private merge requests'
end
context 'with a public project with public merge requests' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
project
.project_feature
.update!(merge_requests_access_level: ProjectFeature::ENABLED)
end
context 'when not logged in' do
let(:current_user) {}
it { is_expected.to contain_exactly(merge_request) }
end
end
end
end
 
Loading
Loading
Loading
Loading
@@ -132,6 +132,18 @@ describe API::Triggers do
end
end
end
context 'when is triggered by a pipeline hook' do
it 'does not create a new pipeline' do
expect do
post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"),
params: { ref: 'refs/heads/other-branch' },
headers: { WebHookService::GITLAB_EVENT_HEADER => 'Pipeline Hook' }
end.not_to change(Ci::Pipeline, :count)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
 
describe 'GET /projects/:id/triggers' do
Loading
Loading
Loading
Loading
@@ -769,6 +769,15 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'when deploy token has read_registry as a scope' do
let(:current_user) { create(:deploy_token, projects: [project]) }
 
shared_examples 'able to login' do
context 'registry provides read_container_image authentication_abilities' do
let(:current_params) { {} }
let(:authentication_abilities) { [:read_container_image] }
it_behaves_like 'an authenticated'
end
end
context 'for public project' do
let(:project) { create(:project, :public) }
 
Loading
Loading
@@ -783,6 +792,8 @@ describe Auth::ContainerRegistryAuthenticationService do
 
it_behaves_like 'an inaccessible'
end
it_behaves_like 'able to login'
end
 
context 'for internal project' do
Loading
Loading
@@ -799,6 +810,8 @@ describe Auth::ContainerRegistryAuthenticationService do
 
it_behaves_like 'an inaccessible'
end
it_behaves_like 'able to login'
end
 
context 'for private project' do
Loading
Loading
@@ -815,18 +828,38 @@ describe Auth::ContainerRegistryAuthenticationService do
 
it_behaves_like 'an inaccessible'
end
it_behaves_like 'able to login'
end
end
 
context 'when deploy token does not have read_registry scope' do
let(:current_user) { create(:deploy_token, projects: [project], read_registry: false) }
 
shared_examples 'unable to login' do
context 'registry provides no container authentication_abilities' do
let(:current_params) { {} }
let(:authentication_abilities) { [] }
it_behaves_like 'a forbidden'
end
context 'registry provides inapplicable container authentication_abilities' do
let(:current_params) { {} }
let(:authentication_abilities) { [:download_code] }
it_behaves_like 'a forbidden'
end
end
context 'for public project' do
let(:project) { create(:project, :public) }
 
context 'when pulling' do
it_behaves_like 'a pullable'
end
it_behaves_like 'unable to login'
end
 
context 'for internal project' do
Loading
Loading
@@ -835,6 +868,8 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'when pulling' do
it_behaves_like 'an inaccessible'
end
it_behaves_like 'unable to login'
end
 
context 'for private project' do
Loading
Loading
@@ -843,6 +878,15 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'when pulling' do
it_behaves_like 'an inaccessible'
end
context 'when logging in' do
let(:current_params) { {} }
let(:authentication_abilities) { [] }
it_behaves_like 'a forbidden'
end
it_behaves_like 'unable to login'
end
end
 
Loading
Loading
Loading
Loading
@@ -133,6 +133,21 @@ describe Projects::LfsPointers::LfsDownloadService do
end
end
 
context 'when an lfs object with the same oid already exists' do
let!(:existing_lfs_object) { create(:lfs_object, oid: oid) }
before do
stub_full_request(download_link).to_return(body: lfs_content)
end
it_behaves_like 'no lfs object is created'
it 'does not update the file attached to the existing LfsObject' do
expect { subject.execute }
.not_to change { existing_lfs_object.reload.file.file.file }
end
end
context 'when credentials present' do
let(:download_link_with_credentials) { "http://user:password@gitlab.com/#{oid}" }
let(:lfs_object) { LfsDownloadObject.new(oid: oid, size: size, link: download_link_with_credentials) }
Loading
Loading
@@ -210,17 +225,5 @@ describe Projects::LfsPointers::LfsDownloadService do
subject.execute
end
end
context 'when an lfs object with the same oid already exists' do
before do
create(:lfs_object, oid: oid)
end
it 'does not download the file' do
expect(subject).not_to receive(:download_lfs_file!)
subject.execute
end
end
end
end
Loading
Loading
@@ -24,7 +24,6 @@ describe Projects::LfsPointers::LfsObjectDownloadListService do
describe '#execute' do
context 'when no lfs pointer is linked' do
before do
allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return([])
allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links)
expect(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:new).with(project, remote_uri: URI.parse(default_endpoint)).and_call_original
end
Loading
Loading
@@ -35,12 +34,6 @@ describe Projects::LfsPointers::LfsObjectDownloadListService do
subject.execute
end
 
it 'links existent lfs objects to the project' do
expect_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute)
subject.execute
end
it 'retrieves the download links of non existent objects' do
expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(all_oids)
 
Loading
Loading
@@ -48,32 +41,6 @@ describe Projects::LfsPointers::LfsObjectDownloadListService do
end
end
 
context 'when some lfs objects are linked' do
before do
allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(existing_lfs_objects.keys)
allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).and_return(oid_download_links)
end
it 'retrieves the download links of non existent objects' do
expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with(oids)
subject.execute
end
end
context 'when all lfs objects are linked' do
before do
allow_any_instance_of(Projects::LfsPointers::LfsLinkService).to receive(:execute).and_return(all_oids.keys)
allow_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute)
end
it 'retrieves no download links' do
expect_any_instance_of(Projects::LfsPointers::LfsDownloadLinkListService).to receive(:execute).with({}).and_call_original
expect(subject.execute).to be_empty
end
end
context 'when lfsconfig file exists' do
before do
allow(project.repository).to receive(:lfsconfig_for).and_return("[lfs]\n\turl = #{lfs_endpoint}\n")
Loading
Loading
Loading
Loading
@@ -7,6 +7,7 @@ RSpec.shared_context 'GroupPolicy context' do
let_it_be(:maintainer) { create(:user) }
let_it_be(:owner) { create(:user) }
let_it_be(:admin) { create(:admin) }
let_it_be(:non_group_member) { create(:user) }
let_it_be(:group, refind: true) { create(:group, :private, :owner_subgroup_creation_only) }
 
let(:guest_permissions) do
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment