Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-04-30 00:09:38 +00:00
parent cb37aee989
commit 1f520d0c7a
52 changed files with 655 additions and 490 deletions

View File

@ -59,6 +59,7 @@ import WordBreak from '../extensions/word_break';
import { ContentEditor } from './content_editor';
import createMarkdownSerializer from './markdown_serializer';
import createGlApiMarkdownDeserializer from './gl_api_markdown_deserializer';
import createRemarkMarkdownDeserializer from './remark_markdown_deserializer';
import trackInputRulesAndShortcuts from './track_input_rules_and_shortcuts';
import languageLoader from './code_block_language_loader';
@ -146,7 +147,11 @@ export const createContentEditor = ({
const trackedExtensions = allExtensions.map(trackInputRulesAndShortcuts);
const tiptapEditor = createTiptapEditor({ extensions: trackedExtensions, ...tiptapOptions });
const serializer = createMarkdownSerializer({ serializerConfig });
const deserializer = createGlApiMarkdownDeserializer({ render: renderMarkdown });
const deserializer = window.gon?.features?.preserveUnchangedMarkdown
? createRemarkMarkdownDeserializer()
: createGlApiMarkdownDeserializer({
render: renderMarkdown,
});
return new ContentEditor({ tiptapEditor, serializer, eventHub, deserializer, languageLoader });
};

View File

@ -37,9 +37,11 @@ export default {
<template>
<div class="gl-py-6 gl-px-6 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100">
<gl-link
v-if="feature.image_url"
:href="feature.url"
target="_blank"
class="gl-display-block"
data-testid="whats-new-image-link"
data-track-action="click_whats_new_item"
:data-track-label="feature.title"
:data-track-property="feature.url"

View File

@ -21,6 +21,10 @@ module WikiActions
before_action :load_sidebar, except: [:pages]
before_action :set_content_class
before_action do
push_frontend_feature_flag(:preserve_unchanged_markdown, @group, default_enabled: :yaml)
end
before_action only: [:show, :edit, :update] do
@valid_encoding = valid_encoding?
end

View File

@ -20,30 +20,17 @@ module InviteMembersHelper
end
end
def group_select_data(group)
# This should only be used for groups to load the invite group modal.
# For instance the invite groups modal should not call this from a project scope
# this is only to be called in scope of a group context as noted in this thread
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79036#note_821465513
# the group sharing in projects disabling is explained there as well
if group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy
{ groups_filter: 'descendant_groups', parent_id: group.root_ancestor.id }
else
{}
end
end
def common_invite_group_modal_data(source, member_class, is_project)
{
id: source.id,
root_id: source.root_ancestor&.id,
root_id: source.root_ancestor.id,
name: source.name,
default_access_level: Gitlab::Access::GUEST,
invalid_groups: source.related_group_ids,
help_link: help_page_url('user/permissions'),
is_project: is_project,
access_levels: member_class.access_level_roles.to_json
}
}.merge(group_select_data(source))
end
# Overridden in EE
@ -68,6 +55,14 @@ module InviteMembersHelper
private
def group_select_data(source)
if source.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy
{ groups_filter: 'descendant_groups', parent_id: source.root_ancestor.id }
else
{}
end
end
# Overridden in EE
def users_filter_data(group)
{}

View File

@ -6,13 +6,34 @@ class UserCustomAttribute < ApplicationRecord
validates :user_id, :key, :value, presence: true
validates :key, uniqueness: { scope: [:user_id] }
def self.upsert_custom_attributes(custom_attributes)
created_at = DateTime.now
updated_at = DateTime.now
scope :by_key, ->(key) { where(key: key) }
scope :by_user_id, ->(user_id) { where(user_id: user_id) }
scope :by_updated_at, ->(updated_at) { where(updated_at: updated_at) }
scope :arkose_sessions, -> { by_key('arkose_session') }
custom_attributes.map! do |custom_attribute|
custom_attribute.merge({ created_at: created_at, updated_at: updated_at })
class << self
def upsert_custom_attributes(custom_attributes)
created_at = DateTime.now
updated_at = DateTime.now
custom_attributes.map! do |custom_attribute|
custom_attribute.merge({ created_at: created_at, updated_at: updated_at })
end
upsert_all(custom_attributes, unique_by: [:user_id, :key])
end
def sessions
return none if blocked_users.empty?
arkose_sessions
.by_user_id(blocked_users.map(&:user_id))
.select(:value)
end
private
def blocked_users
by_key('blocked_at').by_updated_at(Date.yesterday.all_day)
end
upsert_all(custom_attributes, unique_by: [:user_id, :key])
end
end

View File

@ -1,3 +1,3 @@
- return unless can_admin_group_member?(group)
.js-invite-groups-modal{ data: common_invite_group_modal_data(group, GroupMember, 'false').merge(group_select_data(group)) }
.js-invite-groups-modal{ data: common_invite_group_modal_data(group, GroupMember, 'false') }

View File

@ -0,0 +1,8 @@
---
name: preserve_unchanged_markdown
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86060
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360713
milestone: '15.0'
type: development
group: group::editor
default_enabled: false

View File

@ -772,6 +772,9 @@ Gitlab.ee do
Settings.cron_jobs['ci_project_mirrors_consistency_check_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_project_mirrors_consistency_check_worker']['cron'] ||= '2-58/4 * * * *'
Settings.cron_jobs['ci_project_mirrors_consistency_check_worker']['job_class'] = 'Database::CiProjectMirrorsConsistencyCheckWorker'
Settings.cron_jobs['arkose_blocked_users_report_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['arkose_blocked_users_report_worker']['cron'] ||= '0 6 * * *'
Settings.cron_jobs['arkose_blocked_users_report_worker']['job_class'] = 'Arkose::BlockedUsersReportWorker'
end
#

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddIndexForColumnsUserCustomAttribute < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'index_key_updated_at_on_user_custom_attribute'
def up
add_concurrent_index(:user_custom_attributes, [:key, :updated_at], name: INDEX_NAME)
end
def down
remove_concurrent_index_by_name(:user_custom_attributes, INDEX_NAME)
end
end

View File

@ -0,0 +1 @@
d966d06f88e31be3e310bb1e414484c95fa458680d4cc7f04f20f2a297feb8fd

View File

@ -28101,6 +28101,8 @@ CREATE INDEX index_job_artifact_states_on_verification_state ON ci_job_artifact_
CREATE INDEX index_job_artifact_states_pending_verification ON ci_job_artifact_states USING btree (verified_at NULLS FIRST) WHERE (verification_state = 0);
CREATE INDEX index_key_updated_at_on_user_custom_attribute ON user_custom_attributes USING btree (key, updated_at);
CREATE INDEX index_keys_on_expires_at_and_id ON keys USING btree (date(timezone('UTC'::text, expires_at)), id) WHERE (expiry_notification_delivered_at IS NULL);
CREATE INDEX index_keys_on_fingerprint ON keys USING btree (fingerprint);

View File

@ -456,20 +456,23 @@ To restore a group that is marked for deletion:
## Prevent group sharing outside the group hierarchy
This setting is only available on top-level groups. It affects all subgroups.
This setting is only available on top-level groups. It affects all subgroups and projects.
When checked, any group in the top-level group hierarchy can be shared only with other groups in the hierarchy.
When checked, any group in the top-level group hierarchy can only invite other groups from within the top-level
group's hierarchy.
For example, with these groups:
For example, with this setup:
- **Animals > Dogs**
- **Animals > Dogs > Dog Project**
- **Animals > Cats**
- **Plants > Trees**
If you select this setting in the **Animals** group:
- **Dogs** can be shared with **Cats**.
- **Dogs** cannot be shared with **Trees**.
- **Dogs** can invite the group **Cats**.
- **Dogs** cannot invite the group **Trees**.
- **Dog Project** can invite the group **Cats**.
- **Dog Project** cannot invite the group **Trees**.
To prevent sharing outside of the group's hierarchy:

View File

@ -1,3 +1,6 @@
--color
--force-color
--order random
--format documentation
--default-path qa/specs
--require spec_helper
--tag ~orchestrated

View File

@ -1,4 +1,4 @@
--force-color
--order random
--format documentation
--require specs/spec_helper
--require spec_helper

View File

@ -24,6 +24,7 @@ module QA
loader.push_dir(root, namespace: QA)
loader.ignore("#{root}/specs/features")
loader.ignore("#{root}/specs/spec_helper.rb")
loader.inflector.inflect(
"ce" => "CE",

View File

@ -21,14 +21,7 @@ module QA
end
def perform(options, *args)
gitlab_address = extract_gitlab_address(options, args)
# Define the "About" page as an `about` subdomain.
# @example
# Given *gitlab_address* = 'https://gitlab.com/' #=> https://about.gitlab.com/
# Given *gitlab_address* = 'https://staging.gitlab.com/' #=> https://about.staging.gitlab.com/
# Given *gitlab_address* = 'http://gitlab-abc123.test/' #=> http://about.gitlab-abc123.test/
Runtime::Scenario.define(:about_address, gitlab_address.tap { |add| add.host = "about.#{add.host}" }.to_s)
define_gitlab_address(options, args)
# Save the scenario class name
Runtime::Scenario.define(:klass, self.class.name)
@ -36,7 +29,7 @@ module QA
##
# Setup knapsack and download latest report
#
Tools::KnapsackReport.configure! if Runtime::Env.knapsack?
Support::KnapsackReport.configure!
##
# Perform before hooks, which are different for CE and EE
@ -70,32 +63,22 @@ module QA
private
# For backwards-compatibility, if the gitlab instance address is not
# specified as an option parsed by OptionParser, it can be specified as
# the first argument
def extract_gitlab_address(options, args)
opt_name = :gitlab_address
address_from_opt = Runtime::Scenario.attributes[opt_name]
# return gitlab address if it was set via named option already
return validate_address(opt_name, address_from_opt) && URI(address_from_opt) if address_from_opt
delegate :define_gitlab_address_attribute!, to: 'QA::Support::GitlabAddress'
address = if args.first.nil? || File.exist?(args.first)
# if first arg is a valid path and not address, it's a spec file, default to environment variable
Runtime::Env.gitlab_url
else
args.shift
end
# Define gitlab address attribute
#
# Use first argument if a valid address, else use named argument or default to environment variable
#
# @param [Hash] options
# @param [Array] args
# @return [void]
def define_gitlab_address(options, args)
address_from_opt = Runtime::Scenario.attributes[:gitlab_address]
validate_address(opt_name, address)
Runtime::Scenario.define(opt_name, address)
return define_gitlab_address_attribute!(args.shift) if args.first && Runtime::Address.valid?(args.first)
return define_gitlab_address_attribute!(address_from_opt) if address_from_opt
URI(address)
end
def validate_address(name, address)
Runtime::Address.valid?(address) || raise(
::ArgumentError, "Configured address parameter '#{name}' is not a valid url: #{address}"
)
define_gitlab_address_attribute!
end
end
end

View File

@ -12,12 +12,6 @@ module QA
tags :mattermost
attribute :mattermost_address, '--mattermost-address URL', 'Address of the Mattermost server'
def perform(options, *args)
extract_address(:mattermost_address, options)
super(options, *args)
end
end
end
end

View File

@ -7,9 +7,11 @@ require 'active_support/gem_version'
module QaDeprecationToolkitEnv
# Taken from https://github.com/jeremyevans/ruby-warning/blob/1.1.0/lib/warning.rb#L18
# rubocop:disable Layout/LineLength
def self.kwargs_warning
%r{warning: (?:Using the last argument (?:for `.+' )?as keyword parameters is deprecated; maybe \*\* should be added to the call|Passing the keyword argument (?:for `.+' )?as the last hash parameter is deprecated|Splitting the last argument (?:for `.+' )?into positional and keyword parameters is deprecated|The called method (?:`.+' )?is defined here)\n\z}
end
# rubocop:enable Layout/LineLength
def self.configure!
# Enable ruby deprecations for keywords, it's suppressed by default in Ruby 2.7

136
qa/qa/specs/spec_helper.rb Normal file
View File

@ -0,0 +1,136 @@
# frozen_string_literal: true
require_relative '../../qa'
require_relative 'qa_deprecation_toolkit_env'
QaDeprecationToolkitEnv.configure!
Knapsack::Adapters::RSpecAdapter.bind if QA::Runtime::Env.knapsack?
QA::Support::GitlabAddress.define_gitlab_address_attribute!
QA::Runtime::Browser.configure! unless QA::Runtime::Env.dry_run
QA::Runtime::AllureReport.configure!
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes)
Dir[::File.join(__dir__, "features/shared_examples/*.rb")].sort.each { |f| require f }
Dir[::File.join(__dir__, "features/shared_contexts/*.rb")].sort.each { |f| require f }
RSpec.configure do |config|
config.include QA::Support::Matchers::EventuallyMatcher
config.include QA::Support::Matchers::HaveMatcher
config.add_formatter QA::Support::Formatters::ContextFormatter
config.add_formatter QA::Support::Formatters::QuarantineFormatter
config.add_formatter QA::Support::Formatters::FeatureFlagFormatter
config.add_formatter QA::Support::Formatters::TestStatsFormatter if QA::Runtime::Env.export_metrics?
config.before(:suite) do |suite|
QA::Resource::ReusableCollection.register_resource_classes do |collection|
QA::Resource::ReusableProject.register(collection)
QA::Resource::ReusableGroup.register(collection)
end
end
config.prepend_before do |example|
QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}")
QA::Runtime::Example.current = example
# Reset fabrication counters tracked in resource base
Thread.current[:api_fabrication] = 0
Thread.current[:browser_ui_fabrication] = 0
end
config.after do
# If a .netrc file was created during the test, delete it so that subsequent tests don't try to use the same logins
QA::Git::Repository.new.delete_netrc
end
# Add fabrication time to spec metadata
config.append_after do |example|
example.metadata[:api_fabrication] = Thread.current[:api_fabrication]
example.metadata[:browser_ui_fabrication] = Thread.current[:browser_ui_fabrication]
end
config.after(:context) do
if !QA::Runtime::Browser.blank_page? && QA::Page::Main::Menu.perform(&:signed_in?)
QA::Page::Main::Menu.perform(&:sign_out)
raise(
<<~ERROR
The test left the browser signed in.
Usually, Capybara prevents this from happening but some things can
interfere. For example, if it has an `after(:context)` block that logs
in, the browser will stay logged in and this will cause the next test
to fail.
Please make sure the test does not leave the browser signed in.
ERROR
)
end
end
config.after(:suite) do |suite|
# Write all test created resources to JSON file
QA::Tools::TestResourceDataProcessor.write_to_file(suite.reporter.failed_examples.any?)
# If requested, confirm that resources were used appropriately (e.g., not left with changes that interfere with
# further reuse)
QA::Resource::ReusableCollection.validate_resource_reuse if QA::Runtime::Env.validate_resource_reuse?
# If any tests failed, leave the resources behind to help troubleshoot, otherwise remove them.
# Do not remove the shared resource on live environments
begin
next if suite.reporter.failed_examples.present?
next unless QA::Runtime::Scenario.attributes.include?(:gitlab_address)
next if QA::Runtime::Env.running_on_dot_com?
QA::Resource::ReusableCollection.remove_all_via_api!
rescue QA::Resource::Errors::InternalServerError => e
# Temporarily prevent this error from failing jobs while the cause is investigated
# See https://gitlab.com/gitlab-org/gitlab/-/issues/354387
QA::Runtime::Logger.debug(e.message)
end
end
config.append_after(:suite) do
QA::Support::KnapsackReport.move_regenerated_report if QA::Runtime::Env.knapsack?
end
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.disable_monkey_patching!
config.expose_dsl_globally = true
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
# This option allows to use shorthand aliases for adding :focus metadata - fit, fdescribe and fcontext
config.filter_run_when_matching :focus
if ENV['CI'] && !QA::Runtime::Env.disable_rspec_retry?
# show retry status in spec process
config.verbose_retry = true
# show exception that triggers a retry if verbose_retry is set to true
config.display_try_failure_messages = true
non_quarantine_retries = QA::Runtime::Env.ci_project_name =~ /staging|canary|production/ ? 3 : 2
config.around do |example|
quarantine = example.metadata[:quarantine]
different_quarantine_context = QA::Specs::Helpers::Quarantine.quarantined_different_context?(quarantine)
focused_quarantine = QA::Specs::Helpers::Quarantine.filters.key?(:quarantine)
# Do not disable retry when spec is quarantined but on different environment
next example.run_with_retry(retry: non_quarantine_retries) if different_quarantine_context && !focused_quarantine
example.run_with_retry(retry: quarantine ? 1 : non_quarantine_retries)
end
end
end

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
module QA
module Support
class GitlabAddress
class << self
# Define gitlab address
#
# @param [String] address
# @return [void]
def define_gitlab_address_attribute!(address = Runtime::Env.gitlab_url)
return if initialized?
validate_address(address)
Runtime::Scenario.define(:gitlab_address, address)
# Define the "About" page as an `about` subdomain.
# @example
# Given *gitlab_address* = 'https://gitlab.com/' #=> https://about.gitlab.com/
# Given *gitlab_address* = 'https://staging.gitlab.com/' #=> https://about.staging.gitlab.com/
# Given *gitlab_address* = 'http://gitlab-abc123.test/' #=> http://about.gitlab-abc123.test/
Runtime::Scenario.define(:about_address, URI(address).tap { |uri| uri.host = "about.#{uri.host}" }.to_s)
@initialized = true
end
private
# Gitlab address already set up
#
# @return [Boolean]
def initialized?
@initialized
end
# Validate if address is a valid url
#
# @param [String] address
# @return [void]
def validate_address(address)
Runtime::Address.valid?(address) || raise(
::ArgumentError, "Configured gitlab address is not a valid url: #{address}"
)
end
end
end
end
end

View File

@ -3,7 +3,7 @@
require "fog/google"
module QA
module Tools
module Support
class KnapsackReport
extend SingleForwardable
@ -20,11 +20,10 @@ module QA
#
# @return [void]
def configure!
ENV["KNAPSACK_TEST_FILE_PATTERN"] ||= "qa/specs/features/**/*_spec.rb"
ENV["KNAPSACK_REPORT_PATH"] = report_path
Knapsack.logger = QA::Runtime::Logger.logger
return unless QA::Runtime::Env.knapsack?
setup_logger!
setup_environment!
download_report
end
@ -89,6 +88,21 @@ module QA
private
# Setup knapsack logger
#
# @return [void]
def setup_logger!
Knapsack.logger = QA::Runtime::Logger.logger
end
# Set knapsack environment variables
#
# @return [void]
def setup_environment!
ENV["KNAPSACK_TEST_FILE_PATTERN"] ||= "qa/specs/features/**/*_spec.rb"
ENV["KNAPSACK_REPORT_PATH"] = report_path
end
# Logger instance
#
# @return [Logger]

7
qa/spec/README.md Normal file
View File

@ -0,0 +1,7 @@
# QA framework unit tests
To run framework unit tests, following command can be used:
```shell
bundle exec rspec -O .rspec_internal
```

View File

@ -20,6 +20,8 @@ RSpec.describe QA::Scenario::Template do
allow(scenario).to receive(:attributes).and_return({ gitlab_address: gitlab_address })
allow(scenario).to receive(:define)
QA::Support::GitlabAddress.instance_variable_set(:@initialized, false)
end
it 'allows a feature to be enabled' do
@ -95,13 +97,6 @@ RSpec.describe QA::Scenario::Template do
expect(scenario).to have_received(:define).with(:gitlab_address, gitlab_address_from_env)
end
it 'defines only about address' do
subject.perform({ gitlab_address: gitlab_address })
expect(scenario).not_to have_received(:define).with(:gitlab_address, gitlab_address)
expect(scenario).to have_received(:define).with(:about_address, 'https://about.gitlab.com/')
end
it 'defines klass attribute' do
subject.perform({ gitlab_address: gitlab_address })

View File

@ -3,17 +3,10 @@
RSpec.describe QA::Scenario::Test::Integration::Mattermost do
describe '#perform' do
it_behaves_like 'a QA scenario class' do
let(:args) { { gitlab_address: 'http://gitlab_address', mattermost_address: 'http://mattermost_address' } }
let(:args) { { gitlab_address: 'http://gitlab_address' } }
let(:named_options) { %w[--address http://gitlab_address --mattermost-address http://mattermost_address] }
let(:tags) { [:mattermost] }
let(:options) { ['path1'] }
it 'defines mattermost address' do
subject.perform(args)
expect(scenario).to have_received(:define)
.with(:mattermost_address, 'http://mattermost_address')
end
end
end
end

View File

@ -2,134 +2,4 @@
require_relative '../qa'
require_relative 'qa_deprecation_toolkit_env'
QaDeprecationToolkitEnv.configure!
Knapsack::Adapters::RSpecAdapter.bind if QA::Runtime::Env.knapsack?
QA::Runtime::Browser.configure! unless QA::Runtime::Env.dry_run
QA::Runtime::AllureReport.configure!
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes)
Dir[::File.join(__dir__, "support/shared_examples/*.rb")].sort.each { |f| require f }
Dir[::File.join(__dir__, "support/shared_contexts/*.rb")].sort.each { |f| require f }
RSpec.configure do |config|
config.include QA::Support::Matchers::EventuallyMatcher
config.include QA::Support::Matchers::HaveMatcher
config.add_formatter QA::Support::Formatters::ContextFormatter
config.add_formatter QA::Support::Formatters::QuarantineFormatter
config.add_formatter QA::Support::Formatters::FeatureFlagFormatter
config.add_formatter QA::Support::Formatters::TestStatsFormatter if QA::Runtime::Env.export_metrics?
config.before(:suite) do |suite|
QA::Resource::ReusableCollection.register_resource_classes do |collection|
QA::Resource::ReusableProject.register(collection)
QA::Resource::ReusableGroup.register(collection)
end
end
config.prepend_before do |example|
QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}")
QA::Runtime::Example.current = example
# Reset fabrication counters tracked in resource base
Thread.current[:api_fabrication] = 0
Thread.current[:browser_ui_fabrication] = 0
end
config.after do
# If a .netrc file was created during the test, delete it so that subsequent tests don't try to use the same logins
QA::Git::Repository.new.delete_netrc
end
# Add fabrication time to spec metadata
config.append_after do |example|
example.metadata[:api_fabrication] = Thread.current[:api_fabrication]
example.metadata[:browser_ui_fabrication] = Thread.current[:browser_ui_fabrication]
end
config.after(:context) do
if !QA::Runtime::Browser.blank_page? && QA::Page::Main::Menu.perform(&:signed_in?)
QA::Page::Main::Menu.perform(&:sign_out)
raise(
<<~ERROR
The test left the browser signed in.
Usually, Capybara prevents this from happening but some things can
interfere. For example, if it has an `after(:context)` block that logs
in, the browser will stay logged in and this will cause the next test
to fail.
Please make sure the test does not leave the browser signed in.
ERROR
)
end
end
config.after(:suite) do |suite|
# Write all test created resources to JSON file
QA::Tools::TestResourceDataProcessor.write_to_file(suite.reporter.failed_examples.any?)
# If requested, confirm that resources were used appropriately (e.g., not left with changes that interfere with
# further reuse)
QA::Resource::ReusableCollection.validate_resource_reuse if QA::Runtime::Env.validate_resource_reuse?
# If any tests failed, leave the resources behind to help troubleshoot, otherwise remove them.
# Do not remove the shared resource on live environments
begin
next if suite.reporter.failed_examples.present?
next unless QA::Runtime::Scenario.attributes.include?(:gitlab_address)
next if QA::Runtime::Env.running_on_dot_com?
QA::Resource::ReusableCollection.remove_all_via_api!
rescue QA::Resource::Errors::InternalServerError => e
# Temporarily prevent this error from failing jobs while the cause is investigated
# See https://gitlab.com/gitlab-org/gitlab/-/issues/354387
QA::Runtime::Logger.debug(e.message)
end
end
config.append_after(:suite) do
QA::Tools::KnapsackReport.move_regenerated_report if QA::Runtime::Env.knapsack?
end
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.disable_monkey_patching!
config.expose_dsl_globally = true
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
# show retry status in spec process
config.verbose_retry = true
# show exception that triggers a retry if verbose_retry is set to true
config.display_try_failure_messages = true
# This option allows to use shorthand aliases for adding :focus metadata - fit, fdescribe and fcontext
config.filter_run_when_matching :focus
if ENV['CI'] && !QA::Runtime::Env.disable_rspec_retry?
non_quarantine_retries = QA::Runtime::Env.ci_project_name =~ /staging|canary|production/ ? 3 : 2
config.around do |example|
quarantine = example.metadata[:quarantine]
different_quarantine_context = QA::Specs::Helpers::Quarantine.quarantined_different_context?(quarantine)
focused_quarantine = QA::Specs::Helpers::Quarantine.filters.key?(:quarantine)
# Do not disable retry when spec is quarantined but on different environment
next example.run_with_retry(retry: non_quarantine_retries) if different_quarantine_context && !focused_quarantine
example.run_with_retry(retry: quarantine ? 1 : non_quarantine_retries)
end
end
end
require_relative 'scenario_shared_examples'

View File

@ -1,5 +0,0 @@
# frozen_string_literal: true
require_relative '../../qa'
require_relative 'scenario_shared_examples'

View File

@ -1,20 +1,26 @@
# frozen_string_literal: true
# rubocop:disable Rails/RakeEnvironment
namespace :knapsack do
desc "Run tests with knapsack runner"
task :rspec, [:rspec_args] do |_, args|
raise "This environment is not compatible with knapsack runner!" unless QA::Runtime::Env.knapsack?
QA::Support::KnapsackReport.configure!
Knapsack::Runners::RSpecRunner.run(args[:rspec_args])
end
desc "Download latest knapsack report"
task :download do
QA::Tools::KnapsackReport.download
QA::Support::KnapsackReport.download
end
desc "Merge and upload knapsack report"
task :upload, [:glob] do |_task, args|
QA::Tools::KnapsackReport.upload_report(args[:glob])
QA::Support::KnapsackReport.upload_report(args[:glob])
end
desc "Report long running spec files"
task :notify_long_running_specs do
QA::Tools::LongRunningSpecReporter.execute
QA::Support::LongRunningSpecReporter.execute
end
end
# rubocop:enable Rails/RakeEnvironment

View File

@ -183,6 +183,10 @@ FactoryBot.define do
request_access_enabled { false }
end
trait :with_namespace_settings do
namespace factory: [:namespace, :with_namespace_settings]
end
trait :with_avatar do
avatar { fixture_file_upload('spec/fixtures/dk.png') }
end

View File

@ -8,7 +8,7 @@ RSpec.describe "Admin::Projects" do
include Spec::Support::Helpers::ModalHelpers
let(:user) { create :user }
let(:project) { create(:project) }
let(:project) { create(:project, :with_namespace_settings) }
let(:current_user) { create(:admin) }
before do
@ -82,7 +82,7 @@ RSpec.describe "Admin::Projects" do
describe 'transfer project' do
# The gitlab-shell transfer will fail for a project without a repository
let(:project) { create(:project, :repository) }
let(:project) { create(:project, :repository, :with_namespace_settings) }
before do
create(:group, name: 'Web')

View File

@ -119,141 +119,11 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
describe 'group search results' do
let_it_be(:group, refind: true) { create(:group) }
context 'with instance admin considerations' do
let_it_be(:group_to_share) { create(:group) }
context 'when user is an admin' do
let_it_be(:admin) { create(:admin) }
before do
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
end
it 'shows groups where the admin has no direct membership' do
visit group_group_members_path(group)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_to_share)
expect_not_to_have_group(group)
end
end
it 'shows groups where the admin has at least guest level membership' do
group_to_share.add_guest(admin)
visit group_group_members_path(group)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_to_share)
expect_not_to_have_group(group)
end
end
end
context 'when user is not an admin' do
before do
group.add_owner(user)
end
it 'shows groups where the user has no direct membership' do
visit group_group_members_path(group)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_not_to_have_group(group_to_share)
expect_not_to_have_group(group)
end
end
it 'shows groups where the user has at least guest level membership' do
group_to_share.add_guest(user)
visit group_group_members_path(group)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_to_share)
expect_not_to_have_group(group)
end
end
end
end
context 'when user is not an admin and there are hierarchy considerations' do
it_behaves_like 'inviting groups search results' do
let_it_be(:entity) { group }
let_it_be(:group_within_hierarchy) { create(:group, parent: group) }
let_it_be(:group_outside_hierarchy) { create(:group) }
before_all do
group.add_owner(user)
group_within_hierarchy.add_owner(user)
group_outside_hierarchy.add_owner(user)
end
it 'does not show self or ancestors', :aggregate_failures do
group_sibbling = create(:group, parent: group)
group_sibbling.add_owner(user)
visit group_group_members_path(group_within_hierarchy)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_outside_hierarchy)
expect_to_have_group(group_sibbling)
expect_not_to_have_group(group)
expect_not_to_have_group(group_within_hierarchy)
end
end
context 'when sharing with groups outside the hierarchy is enabled' do
it 'shows groups within and outside the hierarchy in search results' do
visit group_group_members_path(group)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_within_hierarchy)
expect_to_have_group(group_outside_hierarchy)
end
end
end
context 'when sharing with groups outside the hierarchy is disabled' do
before do
group.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: true)
end
it 'shows only groups within the hierarchy in search results' do
visit group_group_members_path(group)
click_on 'Invite a group'
click_on 'Select a group'
page.within(group_dropdown_selector) do
expect_to_have_group(group_within_hierarchy)
expect_not_to_have_group(group_outside_hierarchy)
end
end
end
let_it_be(:members_page_path) { group_group_members_path(entity) }
let_it_be(:members_page_path_within_hierarchy) { group_group_members_path(group_within_hierarchy) }
end
end
end

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Project active tab' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project) { create(:project, :repository, :with_namespace_settings) }
let(:user) { project.first_owner }

View File

@ -9,7 +9,7 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public) }
let_it_be(:project) { create(:project, :public, :with_namespace_settings) }
let_it_be(:expiration_date) { 5.days.from_now.to_date }
let(:additional_link_attrs) { {} }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Project > Members > Invite group', :js do
RSpec.describe 'Project > Members > Manage groups', :js do
include ActionView::Helpers::DateHelper
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
@ -126,7 +126,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
end
describe 'setting an expiration date for a group link' do
let(:project) { create(:project) }
let(:project) { create(:project, :with_namespace_settings) }
let!(:group) { create(:group) }
let_it_be(:expiration_date) { 5.days.from_now.to_date }
@ -153,81 +153,18 @@ RSpec.describe 'Project > Members > Invite group', :js do
end
end
describe 'the groups dropdown' do
describe 'group search results' do
let_it_be(:parent_group) { create(:group, :public) }
let_it_be(:project_group) { create(:group, :public, parent: parent_group) }
let_it_be(:project) { create(:project, group: project_group) }
context 'with instance admin considerations' do
let_it_be(:group_to_share) { create(:group) }
context 'when user is an admin' do
let_it_be(:admin) { create(:admin) }
before do
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
end
it 'shows groups where the admin has no direct membership' do
visit project_project_members_path(project)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_to_share)
end
end
it 'shows groups where the admin has at least guest level membership' do
group_to_share.add_guest(admin)
visit project_project_members_path(project)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_to_share)
end
end
end
context 'when user is not an admin' do
before do
project.add_maintainer(maintainer)
sign_in(maintainer)
end
it 'does not show groups where the user has no direct membership' do
visit project_project_members_path(project)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_not_to_have_group(group_to_share)
end
end
it 'shows groups where the user has at least guest level membership' do
group_to_share.add_guest(maintainer)
visit project_project_members_path(project)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_to_share)
end
end
end
it_behaves_like 'inviting groups search results' do
let_it_be(:user) { maintainer }
let_it_be(:group) { parent_group }
let_it_be(:group_within_hierarchy) { create(:group, parent: group) }
let_it_be(:project_within_hierarchy) { create(:project, group: group_within_hierarchy)}
let_it_be(:members_page_path) { project_project_members_path(project) }
let_it_be(:members_page_path_within_hierarchy) { project_project_members_path(project_within_hierarchy) }
end
context 'for a project in a nested group' do

View File

@ -8,7 +8,7 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let_it_be(:maintainer) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:project) { create(:project, :with_namespace_settings) }
let_it_be(:three_days_from_now) { 3.days.from_now.to_date }
let_it_be(:five_days_from_now) { 5.days.from_now.to_date }

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe 'Projects > Members > Maintainer manages access requests' do
it_behaves_like 'Maintainer manages access requests' do
let(:entity) { create(:project, :public) }
let(:entity) { create(:project, :public, :with_namespace_settings) }
let(:members_page_path) { project_project_members_path(entity) }
end
end

View File

@ -6,7 +6,7 @@ RSpec.describe 'Projects > Members > Member leaves project' do
include Spec::Support::Helpers::Features::MembersHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:project) { create(:project, :repository, :with_namespace_settings) }
before do
project.add_developer(user)

View File

@ -7,7 +7,7 @@ RSpec.describe 'Projects > Settings > User manages project members' do
include Spec::Support::Helpers::ModalHelpers
let(:group) { create(:group, name: 'OpenSource') }
let(:project) { create(:project) }
let(:project) { create(:project, :with_namespace_settings) }
let(:project2) { create(:project) }
let(:user) { create(:user) }
let(:user_dmitriy) { create(:user, name: 'Dmitriy') }

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe "Internal Project Access" do
include AccessMatchers
let_it_be(:project, reload: true) { create(:project, :internal, :repository) }
let_it_be(:project, reload: true) { create(:project, :internal, :repository, :with_namespace_settings) }
describe "Project should be internal" do
describe '#internal?' do

View File

@ -5,7 +5,9 @@ require 'spec_helper'
RSpec.describe "Private Project Access" do
include AccessMatchers
let_it_be(:project, reload: true) { create(:project, :private, :repository, public_builds: false) }
let_it_be(:project, reload: true) do
create(:project, :private, :repository, :with_namespace_settings, public_builds: false)
end
describe "Project should be private" do
describe '#private?' do

View File

@ -5,7 +5,9 @@ require 'spec_helper'
RSpec.describe "Public Project Access" do
include AccessMatchers
let_it_be(:project, reload: true) { create(:project, :public, :repository) }
let_it_be(:project, reload: true) do
create(:project, :public, :repository, :with_namespace_settings)
end
describe "Project should be public" do
describe '#public?' do

View File

@ -1,8 +1,12 @@
import { PROVIDE_SERIALIZER_OR_RENDERER_ERROR } from '~/content_editor/constants';
import { createContentEditor } from '~/content_editor/services/create_content_editor';
import createGlApiMarkdownDeserializer from '~/content_editor/services/gl_api_markdown_deserializer';
import createRemarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import { createTestContentEditorExtension } from '../test_utils';
jest.mock('~/emoji');
jest.mock('~/content_editor/services/remark_markdown_deserializer');
jest.mock('~/content_editor/services/gl_api_markdown_deserializer');
describe('content_editor/services/create_content_editor', () => {
let renderMarkdown;
@ -11,9 +15,36 @@ describe('content_editor/services/create_content_editor', () => {
beforeEach(() => {
renderMarkdown = jest.fn();
window.gon = {
features: {
preserveUnchangedMarkdown: false,
},
};
editor = createContentEditor({ renderMarkdown, uploadsPath });
});
describe('when preserveUnchangedMarkdown feature is on', () => {
beforeEach(() => {
window.gon.features.preserveUnchangedMarkdown = true;
});
it('provides a remark markdown deserializer to the content editor class', () => {
createContentEditor({ renderMarkdown, uploadsPath });
expect(createRemarkMarkdownDeserializer).toHaveBeenCalled();
});
});
describe('when preserveUnchangedMarkdown feature is off', () => {
beforeEach(() => {
window.gon.features.preserveUnchangedMarkdown = false;
});
it('provides a gl api markdown deserializer to the content editor class', () => {
createContentEditor({ renderMarkdown, uploadsPath });
expect(createGlApiMarkdownDeserializer).toHaveBeenCalledWith({ render: renderMarkdown });
});
});
it('sets gl-outline-0! class selector to the tiptapEditor instance', () => {
expect(editor.tiptapEditor.options.editorProps).toMatchObject({
attributes: {
@ -22,30 +53,19 @@ describe('content_editor/services/create_content_editor', () => {
});
});
it('provides the renderMarkdown function to the markdown serializer', async () => {
const serializedContent = '**bold text**';
renderMarkdown.mockReturnValueOnce('<p><b>bold text</b></p>');
await editor.setSerializedContent(serializedContent);
expect(renderMarkdown).toHaveBeenCalledWith(serializedContent);
});
it('allows providing external content editor extensions', async () => {
const labelReference = 'this is a ~group::editor';
const { tiptapExtension, serializer } = createTestContentEditorExtension();
renderMarkdown.mockReturnValueOnce(
'<p>this is a <span data-reference="label" data-label-name="group::editor">group::editor</span></p>',
);
editor = createContentEditor({
renderMarkdown,
extensions: [tiptapExtension],
serializerConfig: { nodes: { [tiptapExtension.name]: serializer } },
});
await editor.setSerializedContent(labelReference);
editor.tiptapEditor.commands.setContent(
'<p>this is a <span data-reference="label" data-label-name="group::editor">group::editor</span></p>',
);
expect(editor.getSerializedContent()).toBe(labelReference);
});

View File

@ -21,6 +21,7 @@ describe("What's new single feature", () => {
const findReleaseDate = () => wrapper.find('[data-testid="release-date"]');
const findBodyAnchor = () => wrapper.find('[data-testid="body-content"] a');
const findImageLink = () => wrapper.find('[data-testid="whats-new-image-link"]');
const createWrapper = ({ feature } = {}) => {
wrapper = shallowMount(Feature, {
@ -35,18 +36,38 @@ describe("What's new single feature", () => {
it('renders the date', () => {
createWrapper({ feature: exampleFeature });
expect(findReleaseDate().text()).toBe('April 22, 2021');
});
describe('when the published_at is null', () => {
it("doesn't render the date", () => {
it('renders image link', () => {
createWrapper({ feature: exampleFeature });
expect(findImageLink().exists()).toBe(true);
expect(findImageLink().find('div').attributes('style')).toBe(
`background-image: url(${exampleFeature.image_url});`,
);
});
describe('when published_at is null', () => {
it('does not render the date', () => {
createWrapper({ feature: { ...exampleFeature, published_at: null } });
expect(findReleaseDate().exists()).toBe(false);
});
});
describe('when image_url is null', () => {
it('does not render image link', () => {
createWrapper({ feature: { ...exampleFeature, image_url: null } });
expect(findImageLink().exists()).toBe(false);
});
});
it('safe-html config allows target attribute on elements', () => {
createWrapper({ feature: exampleFeature });
expect(findBodyAnchor().attributes()).toEqual({
href: expect.any(String),
rel: 'noopener noreferrer',

View File

@ -30,6 +30,28 @@ RSpec.describe InviteMembersHelper do
expect(helper.common_invite_group_modal_data(project, ProjectMember, 'true')).to include(attributes)
end
context 'when sharing with groups outside the hierarchy is disabled' do
let_it_be(:group) { create(:group) }
before do
group.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: true)
end
it 'provides the correct attributes' do
expect(helper.common_invite_group_modal_data(group, GroupMember, 'false')).to include({ groups_filter: 'descendant_groups', parent_id: group.id })
end
end
context 'when sharing with groups outside the hierarchy is enabled' do
before do
group.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: false)
end
it 'does not return filter attributes' do
expect(helper.common_invite_group_modal_data(project.group, ProjectMember, 'true').keys).not_to include(:groups_filter, :parent_id)
end
end
end
describe '#common_invite_modal_dataset' do
@ -162,28 +184,4 @@ RSpec.describe InviteMembersHelper do
end
end
end
describe '#group_select_data' do
let_it_be(:group) { create(:group) }
context 'when sharing with groups outside the hierarchy is disabled' do
before do
group.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: true)
end
it 'provides the correct attributes' do
expect(helper.group_select_data(group)).to eq({ groups_filter: 'descendant_groups', parent_id: group.id })
end
end
context 'when sharing with groups outside the hierarchy is enabled' do
before do
group.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: false)
end
it 'returns an empty hash' do
expect(helper.group_select_data(project.group)).to eq({})
end
end
end
end

View File

@ -15,4 +15,61 @@ RSpec.describe UserCustomAttribute do
it { is_expected.to validate_presence_of(:value) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:user_id) }
end
describe 'scopes' do
let(:user) { create(:user) }
let(:blocked_at) { DateTime.now }
let(:custom_attribute) { create(:user_custom_attribute, key: 'blocked_at', value: blocked_at, user_id: user.id) }
describe '.by_user_id' do
subject { UserCustomAttribute.by_user_id(user.id) }
it { is_expected.to match_array([custom_attribute]) }
end
describe '.by_updated_at' do
subject { UserCustomAttribute.by_updated_at(Date.today.all_day) }
it { is_expected.to match_array([custom_attribute]) }
end
describe '.by_key' do
subject { UserCustomAttribute.by_key('blocked_at') }
it { is_expected.to match_array([custom_attribute]) }
end
end
describe '#upsert_custom_attributes' do
subject { UserCustomAttribute.upsert_custom_attributes(custom_attributes) }
let_it_be_with_reload(:user) { create(:user) }
let(:arkose_session) { '22612c147bb418c8.2570749403' }
let(:risk_band) { 'Low' }
let(:global_score) { '0' }
let(:custom_score) { '0' }
let(:custom_attributes) do
custom_attributes = []
custom_attributes.push({ key: 'arkose_session', value: arkose_session })
custom_attributes.push({ key: 'arkose_risk_band', value: risk_band })
custom_attributes.push({ key: 'arkose_global_score', value: global_score })
custom_attributes.push({ key: 'arkose_custom_score', value: custom_score })
custom_attributes.map! { |custom_attribute| custom_attribute.merge({ user_id: user.id }) }
custom_attributes
end
it 'adds arkose data to custom attributes' do
subject
expect(user.custom_attributes.count).to eq(4)
expect(user.custom_attributes.find_by(key: 'arkose_session').value).to eq(arkose_session)
expect(user.custom_attributes.find_by(key: 'arkose_risk_band').value).to eq(risk_band)
expect(user.custom_attributes.find_by(key: 'arkose_global_score').value).to eq(global_score)
expect(user.custom_attributes.find_by(key: 'arkose_custom_score').value).to eq(custom_score)
end
end
end

View File

@ -23,6 +23,8 @@ RSpec.shared_examples 'edits content using the content editor' do
describe 'code block bubble menu' do
it 'shows a code block bubble menu for a code block' do
find(content_editor_testid).send_keys [:enter, :enter]
find(content_editor_testid).send_keys '```js ' # trigger input rule
find(content_editor_testid).send_keys 'var a = 0'
find(content_editor_testid).send_keys [:shift, :left]
@ -32,6 +34,8 @@ RSpec.shared_examples 'edits content using the content editor' do
end
it 'sets code block type to "javascript" for `js`' do
find(content_editor_testid).send_keys [:enter, :enter]
find(content_editor_testid).send_keys '```js '
find(content_editor_testid).send_keys 'var a = 0'
@ -39,6 +43,8 @@ RSpec.shared_examples 'edits content using the content editor' do
end
it 'sets code block type to "Custom (nomnoml)" for `nomnoml`' do
find(content_editor_testid).send_keys [:enter, :enter]
find(content_editor_testid).send_keys '```nomnoml '
find(content_editor_testid).send_keys 'test'

View File

@ -0,0 +1,144 @@
# frozen_string_literal: true
RSpec.shared_examples 'inviting groups search results' do
context 'with instance admin considerations' do
let_it_be(:group_to_invite) { create(:group) }
context 'when user is an admin' do
let_it_be(:admin) { create(:admin) }
before do
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
end
it 'shows groups where the admin has no direct membership' do
visit members_page_path
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_to_invite)
expect_not_to_have_group(group)
end
end
it 'shows groups where the admin has at least guest level membership' do
group_to_invite.add_guest(admin)
visit members_page_path
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_to_invite)
expect_not_to_have_group(group)
end
end
end
context 'when user is not an admin' do
before do
group.add_owner(user)
sign_in(user)
end
it 'does not show groups where the user has no direct membership' do
visit members_page_path
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_not_to_have_group(group_to_invite)
expect_not_to_have_group(group)
end
end
it 'shows groups where the user has at least guest level membership' do
group_to_invite.add_guest(user)
visit members_page_path
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_to_invite)
expect_not_to_have_group(group)
end
end
end
end
context 'when user is not an admin and there are hierarchy considerations' do
let_it_be(:group_outside_hierarchy) { create(:group) }
before_all do
group.add_owner(user)
group_within_hierarchy.add_owner(user)
group_outside_hierarchy.add_owner(user)
end
before do
sign_in(user)
end
it 'does not show self or ancestors', :aggregate_failures do
group_sibling = create(:group, parent: group)
group_sibling.add_owner(user)
visit members_page_path_within_hierarchy
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_outside_hierarchy)
expect_to_have_group(group_sibling)
expect_not_to_have_group(group)
expect_not_to_have_group(group_within_hierarchy)
end
end
context 'when sharing with groups outside the hierarchy is enabled' do
it 'shows groups within and outside the hierarchy in search results' do
visit members_page_path
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within(group_dropdown_selector) do
expect_to_have_group(group_within_hierarchy)
expect_to_have_group(group_outside_hierarchy)
end
end
end
context 'when sharing with groups outside the hierarchy is disabled' do
before do
group.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: true)
end
it 'shows only groups within the hierarchy in search results' do
visit members_page_path
click_on 'Invite a group'
click_on 'Select a group'
page.within(group_dropdown_selector) do
expect_to_have_group(group_within_hierarchy)
expect_not_to_have_group(group_outside_hierarchy)
end
end
end
end
end

View File

@ -4,8 +4,7 @@ require 'spec_helper'
RSpec.describe 'projects/project_members/index', :aggregate_failures do
let_it_be(:user) { create(:user) }
let_it_be(:source) { create(:project, :empty_repo) }
let_it_be(:project) { ProjectPresenter.new(source, current_user: user) }
let_it_be(:project) { create(:project, :empty_repo, :with_namespace_settings).present(current_user: user) }
before do
allow(view).to receive(:project_members_app_data_json).and_return({})