Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
205b6baf26
commit
e129eff883
|
@ -186,6 +186,12 @@ graphql-schema-dump:
|
|||
- tmp/tests/graphql/gitlab_schema.graphql
|
||||
- tmp/tests/graphql/gitlab_schema.json
|
||||
|
||||
graphql-schema-dump as-if-foss:
|
||||
extends:
|
||||
- graphql-schema-dump
|
||||
- .frontend:rules:eslint-as-if-foss
|
||||
- .as-if-foss
|
||||
|
||||
.frontend-test-base:
|
||||
extends:
|
||||
- .default-retry
|
||||
|
|
|
@ -828,9 +828,7 @@
|
|||
.frontend:rules:eslint-as-if-foss:
|
||||
rules:
|
||||
- !reference [".strict-ee-only-rules", rules]
|
||||
# We already have `static-analysis as-if-foss` which already runs `lint:eslint:all` if the `pipeline:run-as-if-foss` label is set.
|
||||
- <<: *if-merge-request-labels-as-if-foss
|
||||
when: never
|
||||
- <<: *if-merge-request
|
||||
changes: *frontend-patterns
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ static-verification-with-database:
|
|||
variables:
|
||||
SETUP_DB: "true"
|
||||
|
||||
generate-apollo-graphl-schema:
|
||||
generate-apollo-graphql-schema:
|
||||
extends:
|
||||
- .static-analysis-base
|
||||
- .frontend:rules:default-frontend-jobs
|
||||
|
@ -66,12 +66,19 @@ generate-apollo-graphl-schema:
|
|||
paths:
|
||||
- "${GRAPHQL_SCHEMA_APOLLO_FILE}"
|
||||
|
||||
generate-apollo-graphql-schema as-if-foss:
|
||||
extends:
|
||||
- generate-apollo-graphql-schema
|
||||
- .frontend:rules:eslint-as-if-foss
|
||||
- .as-if-foss
|
||||
needs: ['graphql-schema-dump as-if-foss']
|
||||
|
||||
eslint:
|
||||
extends:
|
||||
- .static-analysis-base
|
||||
- .yarn-cache
|
||||
- .frontend:rules:default-frontend-jobs
|
||||
needs: ['generate-apollo-graphl-schema']
|
||||
needs: ['generate-apollo-graphql-schema']
|
||||
variables:
|
||||
USE_BUNDLE_INSTALL: "false"
|
||||
script:
|
||||
|
@ -83,6 +90,7 @@ eslint as-if-foss:
|
|||
- eslint
|
||||
- .frontend:rules:eslint-as-if-foss
|
||||
- .as-if-foss
|
||||
needs: ['generate-apollo-graphql-schema as-if-foss']
|
||||
|
||||
haml-lint foss:
|
||||
extends:
|
||||
|
|
|
@ -71,7 +71,6 @@ Rails/HelperInstanceVariable:
|
|||
- 'ee/app/helpers/ee/lock_helper.rb'
|
||||
- 'ee/app/helpers/ee/merge_requests_helper.rb'
|
||||
- 'ee/app/helpers/ee/mirror_helper.rb'
|
||||
- 'ee/app/helpers/ee/namespace_storage_limit_alert_helper.rb'
|
||||
- 'ee/app/helpers/ee/notes_helper.rb'
|
||||
- 'ee/app/helpers/ee/operations_helper.rb'
|
||||
- 'ee/app/helpers/ee/projects/security/configuration_helper.rb'
|
||||
|
|
|
@ -30,7 +30,6 @@ RSpec/ExpectInHook:
|
|||
- 'ee/spec/helpers/billing_plans_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/ci/runners_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/issues_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/namespace_storage_limit_alert_helper_spec.rb'
|
||||
- 'ee/spec/helpers/ee/welcome_helper_spec.rb'
|
||||
- 'ee/spec/helpers/kerberos_spnego_helper_spec.rb'
|
||||
- 'ee/spec/helpers/vulnerabilities_helper_spec.rb'
|
||||
|
@ -95,7 +94,6 @@ RSpec/ExpectInHook:
|
|||
- 'ee/spec/services/projects/update_mirror_service_spec.rb'
|
||||
- 'ee/spec/services/security/findings/cleanup_service_spec.rb'
|
||||
- 'ee/spec/services/upcoming_reconciliations/update_service_spec.rb'
|
||||
- 'ee/spec/support/shared_examples/controllers/namespace_storage_limit_alert_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/controllers/registrations/projects_controller_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/models/concerns/elastic/cannot_read_cross_project_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/models/concerns/verifiable_replicator_shared_examples.rb'
|
||||
|
|
|
@ -14,7 +14,6 @@ RSpec/InstanceVariable:
|
|||
- ee/spec/graphql/types/vulnerability_request_type_spec.rb
|
||||
- ee/spec/graphql/types/vulnerability_response_type_spec.rb
|
||||
- ee/spec/helpers/ee/issuables_helper_spec.rb
|
||||
- ee/spec/helpers/ee/namespace_storage_limit_alert_helper_spec.rb
|
||||
- ee/spec/helpers/ee/wiki_helper_spec.rb
|
||||
- ee/spec/helpers/notes_helper_spec.rb
|
||||
- ee/spec/helpers/search_helper_spec.rb
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
---
|
||||
RSpec/RepeatedExampleGroupBody:
|
||||
# Offense count: 143
|
||||
# Temporarily disabled due to too many offenses
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'ee/spec/controllers/ee/groups_controller_spec.rb'
|
||||
- 'ee/spec/lib/banzai/filter/references/vulnerability_reference_filters_spec.rb'
|
||||
|
@ -18,7 +15,6 @@ RSpec/RepeatedExampleGroupBody:
|
|||
- 'ee/spec/requests/api/graphql/mutations/compliance_management/frameworks/update_spec.rb'
|
||||
- 'ee/spec/requests/groups/security/credentials_controller_spec.rb'
|
||||
- 'ee/spec/services/app_sec/dast/profiles/create_associations_service_spec.rb'
|
||||
- 'ee/spec/services/groups/sync_service_spec.rb'
|
||||
- 'spec/controllers/groups/registry/repositories_controller_spec.rb'
|
||||
- 'spec/controllers/projects/blob_controller_spec.rb'
|
||||
- 'spec/controllers/projects/graphs_controller_spec.rb'
|
||||
|
@ -30,6 +26,7 @@ RSpec/RepeatedExampleGroupBody:
|
|||
- 'spec/features/security/project/private_access_spec.rb'
|
||||
- 'spec/finders/packages/nuget/package_finder_spec.rb'
|
||||
- 'spec/helpers/gitlab_routing_helper_spec.rb'
|
||||
- 'spec/helpers/groups_helper_spec.rb'
|
||||
- 'spec/lib/api/entities/application_setting_spec.rb'
|
||||
- 'spec/lib/banzai/filter/references/commit_range_reference_filter_spec.rb'
|
||||
- 'spec/lib/banzai/filter/references/commit_reference_filter_spec.rb'
|
||||
|
@ -38,9 +35,9 @@ RSpec/RepeatedExampleGroupBody:
|
|||
- 'spec/lib/gitlab/ci/config/entry/release_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/pipeline/seed/build_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/yaml_processor_spec.rb'
|
||||
- 'spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb'
|
||||
- 'spec/lib/gitlab/empty_search_results_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/project/sample/relation_factory_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb'
|
||||
- 'spec/lib/gitlab/lfs/client_spec.rb'
|
||||
- 'spec/lib/gitlab/pagination/keyset/simple_order_builder_spec.rb'
|
||||
- 'spec/lib/gitlab/sanitizers/exif_spec.rb'
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
---
|
||||
RSpec/RepeatedExampleGroupDescription:
|
||||
# Offense count: 263
|
||||
# Temporarily disabled due to too many offenses
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'ee/spec/finders/merge_trains_finder_spec.rb'
|
||||
- 'ee/spec/finders/security/vulnerability_reads_finder_spec.rb'
|
||||
- 'ee/spec/graphql/resolvers/vulnerabilities_grade_resolver_spec.rb'
|
||||
- 'ee/spec/graphql/resolvers/vulnerability_severities_count_resolver_spec.rb'
|
||||
- 'ee/spec/helpers/ee/auth_helper_spec.rb'
|
||||
|
@ -24,16 +22,15 @@ RSpec/RepeatedExampleGroupDescription:
|
|||
- 'ee/spec/models/software_license_spec.rb'
|
||||
- 'ee/spec/policies/app_sec/fuzzing/coverage/corpus_policy_spec.rb'
|
||||
- 'ee/spec/policies/group_policy_spec.rb'
|
||||
- 'ee/spec/policies/project_policy_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/iteration_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/mutations/iterations/create_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/vulnerabilities/sort_spec.rb'
|
||||
- 'ee/spec/requests/groups/security/credentials_controller_spec.rb'
|
||||
- 'ee/spec/requests/groups/settings/reporting_controller_spec.rb'
|
||||
- 'ee/spec/services/app_sec/dast/profiles/create_associations_service_spec.rb'
|
||||
- 'ee/spec/services/app_sec/dast/site_validations/find_or_create_service_spec.rb'
|
||||
- 'ee/spec/services/audit_event_service_spec.rb'
|
||||
- 'ee/spec/services/groups/sync_service_spec.rb'
|
||||
- 'ee/spec/services/todo_service_spec.rb'
|
||||
- 'ee/spec/support/shared_examples/models/concerns/verifiable_replicator_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/services/scoped_label_shared_examples.rb'
|
||||
- 'ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb'
|
||||
- 'spec/controllers/profiles/notifications_controller_spec.rb'
|
||||
|
@ -44,6 +41,7 @@ RSpec/RepeatedExampleGroupDescription:
|
|||
- 'spec/features/merge_request/user_sees_merge_widget_spec.rb'
|
||||
- 'spec/features/projects/jobs_spec.rb'
|
||||
- 'spec/features/projects/new_project_spec.rb'
|
||||
- 'spec/features/projects/pipelines/legacy_pipeline_spec.rb'
|
||||
- 'spec/features/security/project/private_access_spec.rb'
|
||||
- 'spec/finders/ci/pipelines_for_merge_request_finder_spec.rb'
|
||||
- 'spec/frontend/fixtures/runner.rb'
|
||||
|
@ -52,7 +50,6 @@ RSpec/RepeatedExampleGroupDescription:
|
|||
- 'spec/helpers/dropdowns_helper_spec.rb'
|
||||
- 'spec/helpers/gitlab_routing_helper_spec.rb'
|
||||
- 'spec/helpers/namespaces_helper_spec.rb'
|
||||
- 'spec/initializers/omniauth_spec.rb'
|
||||
- 'spec/lib/banzai/pipeline/gfm_pipeline_spec.rb'
|
||||
- 'spec/lib/gitlab/alert_management/payload/base_spec.rb'
|
||||
- 'spec/lib/gitlab/auth/atlassian/auth_hash_spec.rb'
|
||||
|
@ -69,14 +66,15 @@ RSpec/RepeatedExampleGroupDescription:
|
|||
- 'spec/lib/gitlab/ci/pipeline/seed/build_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/yaml_processor_spec.rb'
|
||||
- 'spec/lib/gitlab/data_builder/push_spec.rb'
|
||||
- 'spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb'
|
||||
- 'spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb'
|
||||
- 'spec/lib/gitlab/git/diff_spec.rb'
|
||||
- 'spec/lib/gitlab/git/push_spec.rb'
|
||||
- 'spec/lib/gitlab/git/repository_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/project/sample/relation_factory_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/project/tree_restorer_spec.rb'
|
||||
- 'spec/lib/gitlab/kubernetes/rollout_status_spec.rb'
|
||||
- 'spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb'
|
||||
- 'spec/lib/gitlab/redis/multi_store_spec.rb'
|
||||
- 'spec/lib/gitlab/sanitizers/exif_spec.rb'
|
||||
- 'spec/lib/gitlab/template/finders/global_template_finder_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_spec.rb'
|
||||
|
@ -93,8 +91,10 @@ RSpec/RepeatedExampleGroupDescription:
|
|||
- 'spec/models/project_spec.rb'
|
||||
- 'spec/models/ssh_host_key_spec.rb'
|
||||
- 'spec/requests/api/files_spec.rb'
|
||||
- 'spec/requests/api/graphql/ci/runners_spec.rb'
|
||||
- 'spec/requests/api/graphql/project/release_spec.rb'
|
||||
- 'spec/requests/api/group_clusters_spec.rb'
|
||||
- 'spec/requests/api/internal/base_spec.rb'
|
||||
- 'spec/requests/api/merge_requests_spec.rb'
|
||||
- 'spec/requests/api/notification_settings_spec.rb'
|
||||
- 'spec/requests/api/project_clusters_spec.rb'
|
||||
|
@ -105,10 +105,10 @@ RSpec/RepeatedExampleGroupDescription:
|
|||
- 'spec/services/import/github_service_spec.rb'
|
||||
- 'spec/services/merge_requests/refresh_service_spec.rb'
|
||||
- 'spec/services/metrics/dashboard/gitlab_alert_embed_service_spec.rb'
|
||||
- 'spec/services/resource_access_tokens/create_service_spec.rb'
|
||||
- 'spec/services/verify_pages_domain_service_spec.rb'
|
||||
- 'spec/support/cycle_analytics_helpers/test_generation.rb'
|
||||
- 'spec/support/shared_examples/models/application_setting_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/models/concerns/limitable_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/serializers/diff_file_entity_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb'
|
||||
|
|
|
@ -24,7 +24,6 @@ Style/CaseLikeIf:
|
|||
- 'ee/app/controllers/concerns/credentials_inventory_actions.rb'
|
||||
- 'ee/app/finders/ee/notes_finder.rb'
|
||||
- 'ee/app/helpers/ee/branches_helper.rb'
|
||||
- 'ee/app/helpers/ee/namespace_storage_limit_alert_helper.rb'
|
||||
- 'ee/app/services/epics/tree_reorder_service.rb'
|
||||
- 'ee/app/services/merge_request_approval_settings/update_service.rb'
|
||||
- 'ee/lib/gitlab/alert_management/alert_payload_field_extractor.rb'
|
||||
|
|
|
@ -61,7 +61,6 @@ Style/EmptyMethod:
|
|||
- 'app/controllers/registrations/welcome_controller.rb'
|
||||
- 'app/controllers/search_controller.rb'
|
||||
- 'app/graphql/resolvers/concerns/caching_array_resolver.rb'
|
||||
- 'app/helpers/namespace_storage_limit_alert_helper.rb'
|
||||
- 'app/helpers/subscribable_banner_helper.rb'
|
||||
- 'app/helpers/users/callouts_helper.rb'
|
||||
- 'app/models/ci/bridge.rb'
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -183,7 +183,7 @@ gem 'diffy', '~> 3.3'
|
|||
gem 'diff_match_patch', '~> 0.1.0'
|
||||
|
||||
# Application server
|
||||
gem 'rack', '~> 2.2.3.0'
|
||||
gem 'rack', '~> 2.2.4'
|
||||
# https://github.com/zombocom/rack-timeout/blob/master/README.md#rails-apps-manually
|
||||
gem 'rack-timeout', '~> 0.6.0', require: 'rack/timeout/base'
|
||||
|
||||
|
|
|
@ -1009,7 +1009,7 @@ GEM
|
|||
pyu-ruby-sasl (0.0.3.3)
|
||||
raabro (1.1.6)
|
||||
racc (1.6.0)
|
||||
rack (2.2.3.1)
|
||||
rack (2.2.4)
|
||||
rack-accept (0.4.5)
|
||||
rack (>= 0.4)
|
||||
rack-attack (6.6.1)
|
||||
|
@ -1668,7 +1668,7 @@ DEPENDENCIES
|
|||
pry-shell (~> 0.5.0)
|
||||
puma (~> 5.6.2)
|
||||
puma_worker_killer (~> 0.3.1)
|
||||
rack (~> 2.2.3.0)
|
||||
rack (~> 2.2.4)
|
||||
rack-attack (~> 6.6.0)
|
||||
rack-cors (~> 1.1.0)
|
||||
rack-oauth2 (~> 1.19.0)
|
||||
|
|
|
@ -163,7 +163,7 @@ export default class SSHMirror {
|
|||
const $fingerprintsList = this.$hostKeysInformation.find('.js-fingerprints-list');
|
||||
let fingerprints = '';
|
||||
sshHostKeys.fingerprints.forEach((fingerprint) => {
|
||||
const escFingerprints = escape(fingerprint.fingerprint);
|
||||
const escFingerprints = escape(fingerprint.fingerprint_sha256 || fingerprint.fingerprint);
|
||||
fingerprints += `<code>${escFingerprints}</code>`;
|
||||
});
|
||||
|
||||
|
|
|
@ -5,9 +5,10 @@ module JiraConnect
|
|||
feature_category :integrations
|
||||
|
||||
skip_before_action :authenticate_user!
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
def show
|
||||
if Feature.enabled?(:jira_connect_oauth_self_managed) && jira_connect_application_key.present?
|
||||
if show_application_id?
|
||||
render json: { application_id: jira_connect_application_key }
|
||||
else
|
||||
head :not_found
|
||||
|
@ -16,6 +17,12 @@ module JiraConnect
|
|||
|
||||
private
|
||||
|
||||
def show_application_id?
|
||||
return if Gitlab.com?
|
||||
|
||||
Feature.enabled?(:jira_connect_oauth_self_managed) && jira_connect_application_key.present?
|
||||
end
|
||||
|
||||
def jira_connect_application_key
|
||||
Gitlab::CurrentSettings.jira_connect_application_key.presence
|
||||
end
|
||||
|
|
|
@ -25,6 +25,7 @@ class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController
|
|||
|
||||
before_action :allow_rendering_in_iframe, only: :index
|
||||
before_action :verify_qsh_claim!, only: :index
|
||||
before_action :allow_self_managed_content_security_policy, only: :index
|
||||
before_action :authenticate_user!, only: :create
|
||||
|
||||
def index
|
||||
|
@ -62,6 +63,13 @@ class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def allow_self_managed_content_security_policy
|
||||
return unless current_jira_installation.instance_url?
|
||||
|
||||
request.content_security_policy.directives['connect-src'] ||= []
|
||||
request.content_security_policy.directives['connect-src'] << Gitlab::Utils.append_path(current_jira_installation.instance_url, '/-/jira_connect/oauth_application_ids')
|
||||
end
|
||||
|
||||
def create_service
|
||||
JiraConnectSubscriptions::CreateService.new(current_jira_installation, current_user, namespace_path: params['namespace_path'], jira_user: jira_user)
|
||||
end
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module NamespaceStorageLimitAlertHelper
|
||||
# Overridden in EE
|
||||
def display_namespace_storage_limit_alert!
|
||||
end
|
||||
end
|
||||
|
||||
NamespaceStorageLimitAlertHelper.prepend_mod_with('NamespaceStorageLimitAlertHelper')
|
|
@ -87,7 +87,8 @@ module TodosHelper
|
|||
elsif todo.for_alert?
|
||||
details_project_alert_management_path(todo.project, todo.target)
|
||||
elsif todo.for_issue_or_work_item?
|
||||
Gitlab::UrlBuilder.build(todo.target, only_path: true)
|
||||
path_options[:only_path] = true
|
||||
Gitlab::UrlBuilder.build(todo.target, **path_options)
|
||||
else
|
||||
path = [todo.resource_parent, todo.target]
|
||||
|
||||
|
|
|
@ -361,8 +361,8 @@ class Group < Namespace
|
|||
owners.include?(user)
|
||||
end
|
||||
|
||||
def add_users(users, access_level, current_user: nil, expires_at: nil, tasks_to_be_done: [], tasks_project_id: nil)
|
||||
Members::Groups::CreatorService.add_users( # rubocop:disable CodeReuse/ServiceClass
|
||||
def add_members(users, access_level, current_user: nil, expires_at: nil, tasks_to_be_done: [], tasks_project_id: nil)
|
||||
Members::Groups::CreatorService.add_members( # rubocop:disable CodeReuse/ServiceClass
|
||||
self,
|
||||
users,
|
||||
access_level,
|
||||
|
@ -373,8 +373,8 @@ class Group < Namespace
|
|||
)
|
||||
end
|
||||
|
||||
def add_user(user, access_level, current_user: nil, expires_at: nil, ldap: false, blocking_refresh: true)
|
||||
Members::Groups::CreatorService.add_user( # rubocop:disable CodeReuse/ServiceClass
|
||||
def add_member(user, access_level, current_user: nil, expires_at: nil, ldap: false, blocking_refresh: true)
|
||||
Members::Groups::CreatorService.add_member( # rubocop:disable CodeReuse/ServiceClass
|
||||
self,
|
||||
user,
|
||||
access_level,
|
||||
|
@ -386,23 +386,23 @@ class Group < Namespace
|
|||
end
|
||||
|
||||
def add_guest(user, current_user = nil)
|
||||
add_user(user, :guest, current_user: current_user)
|
||||
add_member(user, :guest, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_reporter(user, current_user = nil)
|
||||
add_user(user, :reporter, current_user: current_user)
|
||||
add_member(user, :reporter, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_developer(user, current_user = nil)
|
||||
add_user(user, :developer, current_user: current_user)
|
||||
add_member(user, :developer, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_maintainer(user, current_user = nil)
|
||||
add_user(user, :maintainer, current_user: current_user)
|
||||
add_member(user, :maintainer, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_owner(user, current_user = nil)
|
||||
add_user(user, :owner, current_user: current_user)
|
||||
add_member(user, :owner, current_user: current_user)
|
||||
end
|
||||
|
||||
def member?(user, min_access_level = Gitlab::Access::GUEST)
|
||||
|
|
|
@ -21,30 +21,30 @@ class ProjectMember < Member
|
|||
end
|
||||
|
||||
class << self
|
||||
# Add users to projects with passed access option
|
||||
# Add members to projects with passed access option
|
||||
#
|
||||
# access can be an integer representing a access code
|
||||
# or symbol like :maintainer representing role
|
||||
#
|
||||
# Ex.
|
||||
# add_users_to_projects(
|
||||
# add_members_to_projects(
|
||||
# project_ids,
|
||||
# user_ids,
|
||||
# ProjectMember::MAINTAINER
|
||||
# )
|
||||
#
|
||||
# add_users_to_projects(
|
||||
# add_members_to_projects(
|
||||
# project_ids,
|
||||
# user_ids,
|
||||
# :maintainer
|
||||
# )
|
||||
#
|
||||
def add_users_to_projects(project_ids, users, access_level, current_user: nil, expires_at: nil)
|
||||
def add_members_to_projects(project_ids, users, access_level, current_user: nil, expires_at: nil)
|
||||
self.transaction do
|
||||
project_ids.each do |project_id|
|
||||
project = Project.find(project_id)
|
||||
|
||||
Members::Projects::CreatorService.add_users( # rubocop:disable CodeReuse/ServiceClass
|
||||
Members::Projects::CreatorService.add_members( # rubocop:disable CodeReuse/ServiceClass
|
||||
project,
|
||||
users,
|
||||
access_level,
|
||||
|
|
|
@ -21,6 +21,10 @@ class MergeRequestDiff < ApplicationRecord
|
|||
# from the database if this sentinel is seen
|
||||
FILES_COUNT_SENTINEL = 2**15 - 1
|
||||
|
||||
# External diff cache key used by diffs export
|
||||
EXTERNAL_DIFFS_CACHE_TMPDIR = 'project-%{project_id}-external-mr-%{mr_id}-diff-%{id}-cache'
|
||||
EXTERNAL_DIFF_CACHE_CHUNK_SIZE = 8.megabytes
|
||||
|
||||
belongs_to :merge_request
|
||||
|
||||
manual_inverse_association :merge_request, :merge_request_diff
|
||||
|
@ -545,6 +549,28 @@ class MergeRequestDiff < ApplicationRecord
|
|||
merge_request_diff_files.reset
|
||||
end
|
||||
|
||||
# Yields locally cached external diff if it's externally stored.
|
||||
# Used during Project Export to speed up externally
|
||||
# stored merge request diffs export
|
||||
def cached_external_diff
|
||||
return yield(nil) unless stored_externally?
|
||||
|
||||
cache_external_diff unless File.exist?(external_diff_cache_filepath)
|
||||
|
||||
File.open(external_diff_cache_filepath) do |file|
|
||||
yield(file)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_cached_external_diff
|
||||
Gitlab::Utils.check_path_traversal!(external_diff_cache_dir)
|
||||
Gitlab::Utils.check_allowed_absolute_path!(external_diff_cache_dir, [Dir.tmpdir])
|
||||
|
||||
return unless Dir.exist?(external_diff_cache_dir)
|
||||
|
||||
FileUtils.rm_rf(external_diff_cache_dir)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def convert_external_diffs_to_database
|
||||
|
@ -791,6 +817,31 @@ class MergeRequestDiff < ApplicationRecord
|
|||
def sort_diffs(diffs)
|
||||
Gitlab::Diff::FileCollectionSorter.new(diffs).sort
|
||||
end
|
||||
|
||||
# Downloads external diff to a temp storage location.
|
||||
def cache_external_diff
|
||||
return unless stored_externally?
|
||||
return if File.exist?(external_diff_cache_filepath)
|
||||
|
||||
Dir.mkdir(external_diff_cache_dir) unless Dir.exist?(external_diff_cache_dir)
|
||||
|
||||
opening_external_diff do |external_diff|
|
||||
File.open(external_diff_cache_filepath, 'wb') do |file|
|
||||
file.write(external_diff.read(EXTERNAL_DIFF_CACHE_CHUNK_SIZE)) until external_diff.eof?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def external_diff_cache_filepath
|
||||
File.join(external_diff_cache_dir, "diff-#{id}")
|
||||
end
|
||||
|
||||
def external_diff_cache_dir
|
||||
File.join(
|
||||
Dir.tmpdir,
|
||||
EXTERNAL_DIFFS_CACHE_TMPDIR % { project_id: project.id, mr_id: merge_request_id, id: id }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
MergeRequestDiff.prepend_mod_with('MergeRequestDiff')
|
||||
|
|
|
@ -45,4 +45,38 @@ class MergeRequestDiffFile < ApplicationRecord
|
|||
content
|
||||
end
|
||||
end
|
||||
|
||||
# This method is meant to be used during Project Export.
|
||||
# It is identical to the behaviour in #diff & #utf8_diff with the only
|
||||
# difference of caching externally stored diffs on local disk in
|
||||
# temp storage location in order to improve diff export performance.
|
||||
def diff_export
|
||||
return utf8_diff unless Feature.enabled?(:externally_stored_diffs_caching_export)
|
||||
return utf8_diff unless merge_request_diff&.stored_externally?
|
||||
|
||||
content = merge_request_diff.cached_external_diff do |file|
|
||||
file.seek(external_diff_offset)
|
||||
|
||||
force_encode_utf8(file.read(external_diff_size))
|
||||
end
|
||||
|
||||
# See #diff
|
||||
if binary?
|
||||
content = begin
|
||||
content.unpack1('m0')
|
||||
rescue ArgumentError
|
||||
content
|
||||
end
|
||||
end
|
||||
|
||||
if content.respond_to?(:encoding)
|
||||
content = encode_utf8(content)
|
||||
end
|
||||
|
||||
return '' if content.blank?
|
||||
|
||||
content
|
||||
rescue StandardError
|
||||
utf8_diff
|
||||
end
|
||||
end
|
||||
|
|
|
@ -460,7 +460,7 @@ class Project < ApplicationRecord
|
|||
delegate :previous_default_branch, :previous_default_branch=, to: :project_setting
|
||||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||
delegate :members, to: :team, prefix: true
|
||||
delegate :add_user, :add_users, to: :team
|
||||
delegate :add_member, :add_members, to: :team
|
||||
delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_owner, :add_role, to: :team
|
||||
delegate :group_runners_enabled, :group_runners_enabled=, to: :ci_cd_settings, allow_nil: true
|
||||
delegate :root_ancestor, to: :namespace, allow_nil: true
|
||||
|
|
|
@ -8,23 +8,23 @@ class ProjectTeam
|
|||
end
|
||||
|
||||
def add_guest(user, current_user: nil)
|
||||
add_user(user, :guest, current_user: current_user)
|
||||
add_member(user, :guest, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_reporter(user, current_user: nil)
|
||||
add_user(user, :reporter, current_user: current_user)
|
||||
add_member(user, :reporter, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_developer(user, current_user: nil)
|
||||
add_user(user, :developer, current_user: current_user)
|
||||
add_member(user, :developer, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_maintainer(user, current_user: nil)
|
||||
add_user(user, :maintainer, current_user: current_user)
|
||||
add_member(user, :maintainer, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_owner(user, current_user: nil)
|
||||
add_user(user, :owner, current_user: current_user)
|
||||
add_member(user, :owner, current_user: current_user)
|
||||
end
|
||||
|
||||
def add_role(user, role, current_user: nil)
|
||||
|
@ -43,8 +43,8 @@ class ProjectTeam
|
|||
member
|
||||
end
|
||||
|
||||
def add_users(users, access_level, current_user: nil, expires_at: nil, tasks_to_be_done: [], tasks_project_id: nil)
|
||||
Members::Projects::CreatorService.add_users( # rubocop:disable CodeReuse/ServiceClass
|
||||
def add_members(users, access_level, current_user: nil, expires_at: nil, tasks_to_be_done: [], tasks_project_id: nil)
|
||||
Members::Projects::CreatorService.add_members( # rubocop:disable CodeReuse/ServiceClass
|
||||
project,
|
||||
users,
|
||||
access_level,
|
||||
|
@ -55,8 +55,8 @@ class ProjectTeam
|
|||
)
|
||||
end
|
||||
|
||||
def add_user(user, access_level, current_user: nil, expires_at: nil)
|
||||
Members::Projects::CreatorService.add_user( # rubocop:disable CodeReuse/ServiceClass
|
||||
def add_member(user, access_level, current_user: nil, expires_at: nil)
|
||||
Members::Projects::CreatorService.add_member( # rubocop:disable CodeReuse/ServiceClass
|
||||
project,
|
||||
user,
|
||||
access_level,
|
||||
|
|
|
@ -12,7 +12,15 @@ class SshHostKey
|
|||
end
|
||||
|
||||
def as_json(*)
|
||||
{ bits: bits, fingerprint: fingerprint, type: type, index: index }
|
||||
{ bits: bits, type: type, index: index }.merge(fingerprint_data)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fingerprint_data
|
||||
data = { fingerprint_sha256: fingerprint_sha256 }
|
||||
data[:fingerprint] = fingerprint unless Gitlab::FIPS.enabled?
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ module Issues
|
|||
def branches_with_iid_of(issue)
|
||||
branch_name_regex = /\A#{issue.iid}-(?!\d+-stable)/i
|
||||
|
||||
project.repository.search_branch_names("#{issue.iid}-*").select do |branch|
|
||||
project.repository.branch_names.select do |branch|
|
||||
branch.match?(branch_name_regex)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -84,7 +84,7 @@ module Members
|
|||
end
|
||||
|
||||
def add_members
|
||||
@members = source.add_users(
|
||||
@members = source.add_members(
|
||||
invites,
|
||||
params[:access_level],
|
||||
expires_at: params[:expires_at],
|
||||
|
|
|
@ -13,9 +13,9 @@ module Members
|
|||
Gitlab::Access.sym_options_with_owner
|
||||
end
|
||||
|
||||
def add_users( # rubocop:disable Metrics/ParameterLists
|
||||
def add_members( # rubocop:disable Metrics/ParameterLists
|
||||
source,
|
||||
users,
|
||||
invitees,
|
||||
access_level,
|
||||
current_user: nil,
|
||||
expires_at: nil,
|
||||
|
@ -24,17 +24,17 @@ module Members
|
|||
ldap: nil,
|
||||
blocking_refresh: nil
|
||||
)
|
||||
return [] unless users.present?
|
||||
return [] unless invitees.present?
|
||||
|
||||
# If this user is attempting to manage Owner members and doesn't have permission, do not allow
|
||||
return [] if managing_owners?(current_user, access_level) && cannot_manage_owners?(source, current_user)
|
||||
|
||||
emails, users, existing_members = parse_users_list(source, users)
|
||||
emails, users, existing_members = parse_users_list(source, invitees)
|
||||
|
||||
Member.transaction do
|
||||
(emails + users).map! do |user|
|
||||
(emails + users).map! do |invitee|
|
||||
new(source,
|
||||
user,
|
||||
invitee,
|
||||
access_level,
|
||||
existing_members: existing_members,
|
||||
current_user: current_user,
|
||||
|
@ -48,17 +48,17 @@ module Members
|
|||
end
|
||||
end
|
||||
|
||||
def add_user( # rubocop:disable Metrics/ParameterLists
|
||||
def add_member( # rubocop:disable Metrics/ParameterLists
|
||||
source,
|
||||
user,
|
||||
invitee,
|
||||
access_level,
|
||||
current_user: nil,
|
||||
expires_at: nil,
|
||||
ldap: nil,
|
||||
blocking_refresh: nil
|
||||
)
|
||||
add_users(source,
|
||||
[user],
|
||||
add_members(source,
|
||||
[invitee],
|
||||
access_level,
|
||||
current_user: current_user,
|
||||
expires_at: expires_at,
|
||||
|
@ -113,9 +113,9 @@ module Members
|
|||
end
|
||||
end
|
||||
|
||||
def initialize(source, user, access_level, **args)
|
||||
def initialize(source, invitee, access_level, **args)
|
||||
@source = source
|
||||
@user = user
|
||||
@invitee = invitee
|
||||
@access_level = self.class.parsed_access_level(access_level)
|
||||
@args = args
|
||||
end
|
||||
|
@ -133,7 +133,7 @@ module Members
|
|||
private
|
||||
|
||||
delegate :new_record?, to: :member
|
||||
attr_reader :source, :user, :access_level, :member, :args
|
||||
attr_reader :source, :invitee, :access_level, :member, :args
|
||||
|
||||
def assign_member_attributes
|
||||
member.attributes = member_attributes
|
||||
|
@ -170,7 +170,7 @@ module Members
|
|||
# Populates the attributes of a member.
|
||||
#
|
||||
# This logic resides in a separate method so that EE can extend this logic,
|
||||
# without having to patch the `add_user` method directly.
|
||||
# without having to patch the `add_members` method directly.
|
||||
def member_attributes
|
||||
{
|
||||
created_by: member.created_by || current_user,
|
||||
|
@ -241,12 +241,10 @@ module Members
|
|||
end
|
||||
|
||||
def find_or_build_member
|
||||
@user = parse_user_param
|
||||
|
||||
@member = if user.is_a?(User)
|
||||
@member = if invitee.is_a?(User)
|
||||
find_or_initialize_member_by_user
|
||||
else
|
||||
source.members.build(invite_email: user)
|
||||
find_or_initialize_member_with_email
|
||||
end
|
||||
|
||||
@member.blocking_refresh = args[:blocking_refresh]
|
||||
|
@ -254,24 +252,23 @@ module Members
|
|||
|
||||
# This method is used to find users that have been entered into the "Add members" field.
|
||||
# These can be the User objects directly, their IDs, their emails, or new emails to be invited.
|
||||
def parse_user_param
|
||||
case user
|
||||
when User
|
||||
user
|
||||
when Integer
|
||||
# might not return anything - this needs enhancement
|
||||
User.find_by(id: user) # rubocop:todo CodeReuse/ActiveRecord
|
||||
def find_or_initialize_member_with_email
|
||||
if user_by_email
|
||||
find_or_initialize_member_by_user(user_id: user_by_email.id)
|
||||
else
|
||||
# must be an email or at least we'll consider it one
|
||||
source.users_by_emails([user])[user] || user
|
||||
source.members.build(invite_email: invitee)
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_initialize_member_by_user
|
||||
def user_by_email
|
||||
source.users_by_emails([invitee])[invitee]
|
||||
end
|
||||
|
||||
def find_or_initialize_member_by_user(user_id: invitee.id)
|
||||
# We have to use `members_and_requesters` here since the given `members` is modified in the models
|
||||
# to act more like a scope(removing the requested_at members) and therefore ActiveRecord has issues with that
|
||||
# on build and refreshing that relation.
|
||||
existing_members[user.id] || source.members_and_requesters.build(user_id: user.id) # rubocop:disable CodeReuse/ActiveRecord
|
||||
existing_members[user_id] || source.members_and_requesters.build(user_id: user_id) # rubocop:disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
def ldap
|
||||
|
|
|
@ -31,8 +31,8 @@ module Members
|
|||
|
||||
return if params[:email].blank?
|
||||
|
||||
# we need the below due to add_users hitting Members::CreatorService.parse_users_list and ignoring invalid emails
|
||||
# ideally we wouldn't need this, but we can't really change the add_users method
|
||||
# we need the below due to add_member hitting Members::CreatorService.parse_users_list and ignoring invalid emails
|
||||
# ideally we wouldn't need this, but we can't really change the add_members method
|
||||
invalid_emails.each { |email| errors[email] = s_('AddMember|Invite email is invalid') }
|
||||
end
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ module Members
|
|||
end
|
||||
|
||||
def adding_the_creator_as_owner_in_a_personal_project?
|
||||
# this condition is reached during testing setup a lot due to use of `.add_user`
|
||||
# this condition is reached during testing setup a lot due to use of `.add_member`
|
||||
member.project.personal_namespace_holder?(member.user)
|
||||
end
|
||||
|
||||
|
|
|
@ -69,28 +69,32 @@ module QuickActions
|
|||
Gitlab::QuickActions::Extractor.new(self.class.command_definitions)
|
||||
end
|
||||
|
||||
# Find users for commands like /assign
|
||||
#
|
||||
# eg. /assign me and @jane and jack
|
||||
def extract_users(params)
|
||||
return [] if params.blank?
|
||||
Gitlab::QuickActions::UsersExtractor
|
||||
.new(current_user, project: project, group: group, target: quick_action_target, text: params)
|
||||
.execute
|
||||
|
||||
# We are using the a simple User.by_username query here rather than a ReferenceExtractor
|
||||
# because the needs here are much simpler: we only deal in usernames, and
|
||||
# want to also handle bare usernames. The ReferenceExtractor also has
|
||||
# different behaviour, and will return all group members for groups named
|
||||
# using a user-style reference, which is not in scope here.
|
||||
#
|
||||
# nb: underscores may be passed in escaped to protect them from markdown rendering
|
||||
args = params.split(/\s|,/).select(&:present?).uniq - ['and']
|
||||
args.map! { _1.gsub(/\\_/, '_') }
|
||||
usernames = (args - ['me']).map { _1.delete_prefix('@') }
|
||||
found = User.by_username(usernames).to_a.select { can?(:read_user, _1) }
|
||||
found_names = found.map(&:username).map(&:downcase).to_set
|
||||
missing = args.reject do |arg|
|
||||
arg == 'me' || found_names.include?(arg.downcase.delete_prefix('@'))
|
||||
end.map { "'#{_1}'" }
|
||||
rescue Gitlab::QuickActions::UsersExtractor::Error => err
|
||||
extract_users_failed(err)
|
||||
end
|
||||
|
||||
failed_parse(format(_("Failed to find users for %{missing}"), missing: missing.to_sentence)) if missing.present?
|
||||
|
||||
found + [current_user].select { args.include?('me') }
|
||||
def extract_users_failed(err)
|
||||
case err
|
||||
when Gitlab::QuickActions::UsersExtractor::MissingError
|
||||
failed_parse(format(_("Failed to find users for %{missing}"), missing: err.message))
|
||||
when Gitlab::QuickActions::UsersExtractor::TooManyRefsError
|
||||
failed_parse(format(_('Too many references. Quick actions are limited to at most %{max_count} user references'),
|
||||
max_count: err.limit))
|
||||
when Gitlab::QuickActions::UsersExtractor::TooManyFoundError
|
||||
failed_parse(format(_("Too many users found. Quick actions are limited to at most %{max_count} users"),
|
||||
max_count: err.limit))
|
||||
else
|
||||
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(err)
|
||||
failed_parse(_('Something went wrong'))
|
||||
end
|
||||
end
|
||||
|
||||
def find_milestones(project, params = {})
|
||||
|
|
|
@ -108,7 +108,7 @@ module ResourceAccessTokens
|
|||
end
|
||||
|
||||
def create_membership(resource, user, access_level)
|
||||
resource.add_user(user, access_level, expires_at: params[:expires_at])
|
||||
resource.add_member(user, access_level, expires_at: params[:expires_at])
|
||||
end
|
||||
|
||||
def log_event(token)
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
= dispensable_render "shared/service_ping_consent"
|
||||
= dispensable_render_if_exists "layouts/header/ee_subscribable_banner"
|
||||
= dispensable_render_if_exists "layouts/header/seat_count_alert"
|
||||
= dispensable_render_if_exists "shared/namespace_storage_limit_alert"
|
||||
= dispensable_render_if_exists "shared/namespace_user_cap_reached_alert"
|
||||
= dispensable_render_if_exists "shared/new_user_signups_cap_reached_alert"
|
||||
= yield :page_level_alert
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
- header_title group_title(@group) unless header_title
|
||||
- nav "group"
|
||||
- display_subscription_banner!
|
||||
- display_namespace_storage_limit_alert!
|
||||
- @left_sidebar = true
|
||||
|
||||
- content_for :flash_message do
|
||||
= render "layouts/header/storage_enforcement_banner", namespace: @group
|
||||
= dispensable_render_if_exists "shared/namespace_storage_limit_alert"
|
||||
|
||||
- content_for :page_specific_javascripts do
|
||||
- if current_user
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
- nav "project"
|
||||
- page_itemtype 'http://schema.org/SoftwareSourceCode'
|
||||
- display_subscription_banner!
|
||||
- display_namespace_storage_limit_alert!
|
||||
- @left_sidebar = true
|
||||
- @content_class = [@content_class, project_classes(@project)].compact.join(" ")
|
||||
|
||||
- content_for :flash_message do
|
||||
= render "layouts/header/storage_enforcement_banner", namespace: @project.namespace
|
||||
= dispensable_render_if_exists "shared/namespace_storage_limit_alert"
|
||||
|
||||
- content_for :project_javascripts do
|
||||
- project = @target_project || @project
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
= _('Fingerprints')
|
||||
.fingerprints-list.js-fingerprints-list{ data: { qa_selector: 'fingerprints_list' } }
|
||||
- mirror.ssh_known_hosts_fingerprints.each do |fp|
|
||||
%code= fp.fingerprint
|
||||
%code= fp.fingerprint_sha256 || fp.fingerprint
|
||||
- if verified_at
|
||||
.form-text.text-muted.js-fingerprint-verification
|
||||
= sprite_icon('check', css_class: 'gl-text-green-500')
|
||||
|
|
|
@ -406,6 +406,16 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
# Cross-origin requests must be enabled to fetch the self-managed application oauth application ID
|
||||
# for the GitLab for Jira app.
|
||||
allow do
|
||||
origins '*'
|
||||
resource '/-/jira_connect/oauth_application_id',
|
||||
headers: :any,
|
||||
methods: %i(get options),
|
||||
credentials: false
|
||||
end
|
||||
|
||||
# These are routes from doorkeeper-openid_connect:
|
||||
# https://github.com/doorkeeper-gem/doorkeeper-openid_connect#routes
|
||||
allow do
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: approval_rules_pagination
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91702
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366823
|
||||
milestone: '15.2'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: externally_stored_diffs_caching_export
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90159
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366255
|
||||
milestone: '15.2'
|
||||
type: development
|
||||
group: group::import
|
||||
default_enabled: false
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/341759
|
|||
milestone: '14.4'
|
||||
type: development
|
||||
group: group::code review
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -55,6 +55,8 @@ Rails.application.routes.draw do
|
|||
match '/oauth/token' => 'oauth/tokens#create', via: :options
|
||||
match '/oauth/revoke' => 'oauth/tokens#revoke', via: :options
|
||||
|
||||
match '/-/jira_connect/oauth_application_id' => 'jira_connect/oauth_application_ids#show', via: :options
|
||||
|
||||
# Sign up
|
||||
scope path: '/users/sign_up', module: :registrations, as: :users_sign_up do
|
||||
resource :welcome, only: [:show, :update], controller: 'welcome' do
|
||||
|
|
|
@ -4,7 +4,7 @@ Sidekiq::Testing.inline! do
|
|||
Gitlab::Seeder.quiet do
|
||||
Group.not_mass_generated.each do |group|
|
||||
User.not_mass_generated.sample(4).each do |user|
|
||||
if group.add_user(user, Gitlab::Access.values.sample).persisted?
|
||||
if group.add_member(user, Gitlab::Access.values.sample).persisted?
|
||||
print '.'
|
||||
else
|
||||
print 'F'
|
||||
|
|
|
@ -396,6 +396,16 @@ gitlab:
|
|||
tag: v15.1.0-fips
|
||||
```
|
||||
|
||||
## FIPS Performance Benchmarking
|
||||
|
||||
The Quality Engineering Enablement team assists these efforts by checking if FIPS-enabled environments perform well compared to non-FIPS environments.
|
||||
|
||||
Testing shows an impact in some places, such as Gitaly SSL, but it's not large enough to impact customers.
|
||||
|
||||
You can find more information on FIPS performance benchmarking in the following issue:
|
||||
|
||||
- [Benchmark performance of FIPS reference architecture](https://gitlab.com/gitlab-org/gitlab/-/issues/364051#note_1010450415)
|
||||
|
||||
## Verify FIPS
|
||||
|
||||
The following sections describe ways you can verify if FIPS is enabled.
|
||||
|
|
|
@ -78,7 +78,7 @@ or API. However, administrators can use a workaround:
|
|||
bot.confirm
|
||||
|
||||
# Add the bot to the group with the required role.
|
||||
group.add_user(bot, :maintainer)
|
||||
group.add_member(bot, :maintainer)
|
||||
|
||||
# Give the bot a personal access token.
|
||||
token = bot.personal_access_tokens.create(scopes:[:api, :write_repository], name: 'group_token')
|
||||
|
|
|
@ -26,7 +26,6 @@ module Gitlab
|
|||
variables.concat(secret_instance_variables)
|
||||
variables.concat(secret_group_variables(environment: environment))
|
||||
variables.concat(secret_project_variables(environment: environment))
|
||||
variables.concat(job.trigger_request.user_variables) if job.trigger_request
|
||||
variables.concat(pipeline.variables)
|
||||
variables.concat(pipeline_schedule_variables)
|
||||
end
|
||||
|
|
|
@ -77,7 +77,7 @@ module Gitlab
|
|||
|
||||
def add_group_members(result)
|
||||
group = result[:group]
|
||||
members = group.add_users(members_to_add(group), Gitlab::Access::MAINTAINER)
|
||||
members = group.add_members(members_to_add(group), Gitlab::Access::MAINTAINER)
|
||||
errors = members.flat_map { |member| member.errors.full_messages }
|
||||
|
||||
if errors.any?
|
||||
|
@ -112,7 +112,7 @@ module Gitlab
|
|||
|
||||
def members_to_add(group)
|
||||
# Exclude admins who are already members of group because
|
||||
# `group.add_users(users)` returns an error if the users parameter contains
|
||||
# `group.add_members(users)` returns an error if the users parameter contains
|
||||
# users who are already members of the group.
|
||||
instance_admins - group.members.collect(&:user)
|
||||
end
|
||||
|
|
|
@ -70,7 +70,11 @@ module Gitlab
|
|||
batch = batch.preload(key_preloads) if key_preloads
|
||||
|
||||
batch.each do |record|
|
||||
before_read_callback(record)
|
||||
|
||||
items << Raw.new(record.to_json(options))
|
||||
|
||||
after_read_callback(record)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -168,6 +172,20 @@ module Gitlab
|
|||
def read_from_replica_if_available(&block)
|
||||
::Gitlab::Database::LoadBalancing::Session.current.use_replicas_for_read_queries(&block)
|
||||
end
|
||||
|
||||
def before_read_callback(record)
|
||||
remove_cached_external_diff(record)
|
||||
end
|
||||
|
||||
def after_read_callback(record)
|
||||
remove_cached_external_diff(record)
|
||||
end
|
||||
|
||||
def remove_cached_external_diff(record)
|
||||
return unless record.is_a?(MergeRequest)
|
||||
|
||||
record.merge_request_diff&.remove_cached_external_diff
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -977,7 +977,7 @@ methods:
|
|||
statuses:
|
||||
- :type
|
||||
merge_request_diff_files:
|
||||
- :utf8_diff
|
||||
- :diff_export
|
||||
merge_requests:
|
||||
- :diff_head_sha
|
||||
- :source_branch_sha
|
||||
|
|
|
@ -133,7 +133,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def setup_diff
|
||||
diff = @relation_hash.delete('utf8_diff')
|
||||
diff = @relation_hash.delete('diff_export') || @relation_hash.delete('utf8_diff')
|
||||
|
||||
parsed_relation_hash['diff'] = diff
|
||||
end
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module QuickActions
|
||||
class UsersExtractor
|
||||
MAX_QUICK_ACTION_USERS = 100
|
||||
|
||||
Error = Class.new(ArgumentError)
|
||||
TooManyError = Class.new(Error) do
|
||||
def limit
|
||||
MAX_QUICK_ACTION_USERS
|
||||
end
|
||||
end
|
||||
|
||||
MissingError = Class.new(Error)
|
||||
TooManyFoundError = Class.new(TooManyError)
|
||||
TooManyRefsError = Class.new(TooManyError)
|
||||
|
||||
attr_reader :text, :current_user, :project, :group, :target
|
||||
|
||||
def initialize(current_user, project:, group:, target:, text:)
|
||||
@current_user = current_user
|
||||
@project = project
|
||||
@group = group
|
||||
@target = target
|
||||
@text = text
|
||||
end
|
||||
|
||||
def execute
|
||||
return [] unless text.present?
|
||||
|
||||
users = collect_users
|
||||
|
||||
check_users!(users)
|
||||
|
||||
users
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collect_users
|
||||
users = []
|
||||
users << current_user if me?
|
||||
users += find_referenced_users if references.any?
|
||||
|
||||
users
|
||||
end
|
||||
|
||||
def check_users!(users)
|
||||
raise TooManyFoundError if users.size > MAX_QUICK_ACTION_USERS
|
||||
|
||||
found = found_names(users)
|
||||
missing = references.filter_map do
|
||||
"'#{_1}'" unless found.include?(_1.downcase.delete_prefix('@'))
|
||||
end
|
||||
|
||||
raise MissingError, missing.to_sentence if missing.present?
|
||||
end
|
||||
|
||||
def found_names(users)
|
||||
users.map(&:username).map(&:downcase).to_set
|
||||
end
|
||||
|
||||
def find_referenced_users
|
||||
raise TooManyRefsError if references.size > MAX_QUICK_ACTION_USERS
|
||||
|
||||
User.by_username(usernames).limit(MAX_QUICK_ACTION_USERS)
|
||||
end
|
||||
|
||||
def usernames
|
||||
references.map { _1.delete_prefix('@') }
|
||||
end
|
||||
|
||||
def references
|
||||
@references ||= begin
|
||||
refs = args - ['me']
|
||||
# nb: underscores may be passed in escaped to protect them from markdown rendering
|
||||
refs.map! { _1.gsub(/\\_/, '_') }
|
||||
refs
|
||||
end
|
||||
end
|
||||
|
||||
def args
|
||||
@args ||= text.split(/\s|,/).map(&:strip).select(&:present?).uniq - ['and']
|
||||
end
|
||||
|
||||
def me?
|
||||
args&.include?('me')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::QuickActions::UsersExtractor.prepend_mod_with('Gitlab::QuickActions::UsersExtractor')
|
|
@ -9,10 +9,10 @@ namespace :gitlab do
|
|||
project_ids = Project.pluck(:id)
|
||||
|
||||
puts "Importing #{user_ids.size} users into #{project_ids.size} projects"
|
||||
ProjectMember.add_users_to_projects(project_ids, user_ids, ProjectMember::DEVELOPER)
|
||||
ProjectMember.add_members_to_projects(project_ids, user_ids, ProjectMember::DEVELOPER)
|
||||
|
||||
puts "Importing #{admin_ids.size} admins into #{project_ids.size} projects"
|
||||
ProjectMember.add_users_to_projects(project_ids, admin_ids, ProjectMember::MAINTAINER)
|
||||
ProjectMember.add_members_to_projects(project_ids, admin_ids, ProjectMember::MAINTAINER)
|
||||
end
|
||||
|
||||
desc "GitLab | Import | Add a specific user to all projects (as a developer)"
|
||||
|
@ -20,7 +20,7 @@ namespace :gitlab do
|
|||
user = User.find_by(email: args.email)
|
||||
project_ids = Project.pluck(:id)
|
||||
puts "Importing #{user.email} users into #{project_ids.size} projects"
|
||||
ProjectMember.add_users_to_projects(project_ids, Array.wrap(user.id), ProjectMember::DEVELOPER)
|
||||
ProjectMember.add_members_to_projects(project_ids, Array.wrap(user.id), ProjectMember::DEVELOPER)
|
||||
end
|
||||
|
||||
desc "GitLab | Import | Add all users to all groups (admin users are added as owners)"
|
||||
|
@ -32,8 +32,8 @@ namespace :gitlab do
|
|||
puts "Importing #{user_ids.size} users into #{groups.size} groups"
|
||||
puts "Importing #{admin_ids.size} admins into #{groups.size} groups"
|
||||
groups.each do |group|
|
||||
group.add_users(user_ids, GroupMember::DEVELOPER)
|
||||
group.add_users(admin_ids, GroupMember::OWNER)
|
||||
group.add_members(user_ids, GroupMember::DEVELOPER)
|
||||
group.add_members(admin_ids, GroupMember::OWNER)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -43,7 +43,7 @@ namespace :gitlab do
|
|||
groups = Group.all
|
||||
puts "Importing #{user.email} users into #{groups.size} groups"
|
||||
groups.each do |group|
|
||||
group.add_users(Array.wrap(user.id), GroupMember::DEVELOPER)
|
||||
group.add_members(Array.wrap(user.id), GroupMember::DEVELOPER)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40498,6 +40498,12 @@ msgstr ""
|
|||
msgid "Too many projects enabled. Manage them through the console or the API."
|
||||
msgstr ""
|
||||
|
||||
msgid "Too many references. Quick actions are limited to at most %{max_count} user references"
|
||||
msgstr ""
|
||||
|
||||
msgid "Too many users found. Quick actions are limited to at most %{max_count} users"
|
||||
msgstr ""
|
||||
|
||||
msgid "TopNav|Go back"
|
||||
msgstr ""
|
||||
|
||||
|
|
4
qa/qa.rb
4
qa/qa.rb
|
@ -21,6 +21,10 @@ module QA
|
|||
root = "#{__dir__}/qa"
|
||||
|
||||
loader = Zeitwerk::Loader.new
|
||||
|
||||
# require jh/qa/qa.rb first, to load JH module make prepend module works
|
||||
require '../jh/qa/qa' if GitlabEdition.jh?
|
||||
|
||||
loader.push_dir(root, namespace: QA)
|
||||
|
||||
loader.ignore("#{root}/specs/features")
|
||||
|
|
|
@ -86,3 +86,5 @@ module QA
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
QA::Scenario::Template.prepend_mod_with('Scenario::Template', namespace: QA)
|
||||
|
|
|
@ -13,7 +13,7 @@ RSpec.describe Groups::VariablesController do
|
|||
|
||||
before do
|
||||
sign_in(user)
|
||||
group.add_user(user, access_level)
|
||||
group.add_member(user, access_level)
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
|
|
|
@ -25,7 +25,7 @@ RSpec.describe Import::AvailableNamespacesController do
|
|||
|
||||
it "does not include group with access level #{params[:role]} in list" do
|
||||
group = create(:group, project_creation_level: group_project_creation_level)
|
||||
group.add_user(user, role)
|
||||
group.add_member(user, role)
|
||||
get :index
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -52,7 +52,7 @@ RSpec.describe Import::AvailableNamespacesController do
|
|||
|
||||
it "does not include group with access level #{params[:role]} in list" do
|
||||
group = create(:group, project_creation_level: group_project_creation_level)
|
||||
group.add_user(user, role)
|
||||
group.add_member(user, role)
|
||||
get :index
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -81,7 +81,7 @@ RSpec.describe Import::AvailableNamespacesController do
|
|||
|
||||
it "#{params[:is_visible] ? 'includes' : 'does not include'} group with access level #{params[:role]} in list" do
|
||||
group = create(:group, project_creation_level: project_creation_level)
|
||||
group.add_user(user, :developer)
|
||||
group.add_member(user, :developer)
|
||||
|
||||
get :index
|
||||
|
||||
|
|
|
@ -1818,7 +1818,7 @@ RSpec.describe Projects::IssuesController do
|
|||
|
||||
context 'user is allowed access' do
|
||||
before do
|
||||
project.add_user(user, :maintainer)
|
||||
project.add_member(user, :maintainer)
|
||||
end
|
||||
|
||||
it 'displays all available notes' do
|
||||
|
|
|
@ -211,7 +211,7 @@ RSpec.describe Projects::MirrorsController do
|
|||
|
||||
context 'data in the cache' do
|
||||
let(:ssh_key) { 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf' }
|
||||
let(:ssh_fp) { { type: 'ed25519', bits: 256, fingerprint: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16', index: 0 } }
|
||||
let(:ssh_fp) { { type: 'ed25519', bits: 256, fingerprint: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16', fingerprint_sha256: 'SHA256:eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8', index: 0 } }
|
||||
|
||||
it 'returns the data with a 200 response' do
|
||||
stub_reactive_cache(cache, known_hosts: ssh_key)
|
||||
|
|
|
@ -409,7 +409,7 @@ RSpec.describe ProjectsController do
|
|||
|
||||
before do
|
||||
project.update!(visibility: project_visibility.to_s)
|
||||
project.team.add_user(user, :guest) if user_type == :member
|
||||
project.team.add_member(user, :guest) if user_type == :member
|
||||
sign_in(user) unless user_type == :anonymous
|
||||
end
|
||||
|
||||
|
|
|
@ -236,7 +236,7 @@ RSpec.describe 'Admin Groups' do
|
|||
it 'renders relative time' do
|
||||
expire_time = Time.current + 2.days
|
||||
current_user.update!(time_display_relative: true)
|
||||
group.add_user(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||
group.add_member(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||
|
||||
visit admin_group_path(group)
|
||||
|
||||
|
@ -246,7 +246,7 @@ RSpec.describe 'Admin Groups' do
|
|||
it 'renders absolute time' do
|
||||
expire_time = Time.current.tomorrow.middle_of_day
|
||||
current_user.update!(time_display_relative: false)
|
||||
group.add_user(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||
group.add_member(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||
|
||||
visit admin_group_path(group)
|
||||
|
||||
|
@ -257,7 +257,7 @@ RSpec.describe 'Admin Groups' do
|
|||
|
||||
describe 'add admin himself to a group' do
|
||||
before do
|
||||
group.add_user(:user, Gitlab::Access::OWNER)
|
||||
group.add_member(:user, Gitlab::Access::OWNER)
|
||||
end
|
||||
|
||||
it 'adds admin a to a group as developer', :js do
|
||||
|
@ -274,7 +274,7 @@ RSpec.describe 'Admin Groups' do
|
|||
|
||||
describe 'admin removes themself from a group', :js do
|
||||
it 'removes admin from the group' do
|
||||
group.add_user(current_user, Gitlab::Access::DEVELOPER)
|
||||
group.add_member(current_user, Gitlab::Access::DEVELOPER)
|
||||
|
||||
visit group_group_members_path(group)
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe "Admin::Projects" do
|
|||
it 'renders relative time' do
|
||||
expire_time = Time.current + 2.days
|
||||
current_user.update!(time_display_relative: true)
|
||||
project.add_user(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||
project.add_member(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||
|
||||
visit admin_project_path(project)
|
||||
|
||||
|
@ -30,7 +30,7 @@ RSpec.describe "Admin::Projects" do
|
|||
it 'renders absolute time' do
|
||||
expire_time = Time.current.tomorrow.middle_of_day
|
||||
current_user.update!(time_display_relative: false)
|
||||
project.add_user(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||
project.add_member(user, Gitlab::Access::REPORTER, expires_at: expire_time)
|
||||
|
||||
visit admin_project_path(project)
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ RSpec.describe 'Invalid uploads that must be rejected', :api, :js do
|
|||
|
||||
# These keys are rejected directly by rack itself.
|
||||
# The request will not be received by multipart.rb (can't use the 'handling file uploads' shared example)
|
||||
it_behaves_like 'rejecting invalid keys', key_name: 'x' * 11000, message: 'Puma caught this error: exceeded available parameter key space (RangeError)'
|
||||
it_behaves_like 'rejecting invalid keys', key_name: 'x' * 11000, message: 'Puma caught this error: exceeded available parameter key space (Rack::QueryParser::ParamsTooDeepError)'
|
||||
it_behaves_like 'rejecting invalid keys', key_name: 'package[]test', status: 400, message: 'Bad Request'
|
||||
|
||||
it_behaves_like 'handling file uploads', 'by rejecting uploads with an invalid key'
|
||||
|
|
|
@ -9,7 +9,7 @@ RSpec.describe 'issuable list', :js do
|
|||
issuable_types = [:issue, :merge_request]
|
||||
|
||||
before do
|
||||
project.add_user(user, :developer)
|
||||
project.add_member(user, :developer)
|
||||
sign_in(user)
|
||||
issuable_types.each { |type| create_issuables(type) }
|
||||
end
|
||||
|
|
|
@ -15,8 +15,8 @@ RSpec.describe 'Visual tokens', :js do
|
|||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
|
||||
before do
|
||||
project.add_user(user, :maintainer)
|
||||
project.add_user(user_rock, :maintainer)
|
||||
project.add_member(user, :maintainer)
|
||||
project.add_member(user_rock, :maintainer)
|
||||
sign_in(user)
|
||||
|
||||
visit project_issues_path(project)
|
||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe 'User creates branch and merge request on issue page', :js do
|
|||
|
||||
context 'when signed in' do
|
||||
before do
|
||||
project.add_user(user, membership_level)
|
||||
project.add_member(user, membership_level)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe 'Merge request > User sees deployment widget', :js do
|
|||
|
||||
before do
|
||||
merge_request.update!(merge_commit_sha: sha)
|
||||
project.add_user(user, role)
|
||||
project.add_member(user, role)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
|
|||
end
|
||||
|
||||
it 'changes expiration date' do
|
||||
project.team.add_users([new_member.id], :developer, expires_at: three_days_from_now)
|
||||
project.team.add_members([new_member.id], :developer, expires_at: three_days_from_now)
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within find_member_row(new_member) do
|
||||
|
@ -46,7 +46,7 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
|
|||
end
|
||||
|
||||
it 'clears expiration date' do
|
||||
project.team.add_users([new_member.id], :developer, expires_at: five_days_from_now)
|
||||
project.team.add_members([new_member.id], :developer, expires_at: five_days_from_now)
|
||||
visit project_project_members_path(project)
|
||||
|
||||
page.within find_member_row(new_member) do
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe 'Project > Show > User interacts with auto devops implicitly enab
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_user(user, role)
|
||||
project.add_member(user, role)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ RSpec.describe 'Projects > Show > Collaboration links', :js do
|
|||
with_them do
|
||||
before do
|
||||
project.project_feature.update!({ merge_requests_access_level: merge_requests_access_level })
|
||||
project.add_user(user, user_level)
|
||||
project.add_member(user, user_level)
|
||||
visit project_path(project)
|
||||
end
|
||||
|
||||
|
|
|
@ -579,9 +579,9 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
context 'group setting' do
|
||||
before do
|
||||
group1 = create :group, name: 'Group 1', require_two_factor_authentication: true
|
||||
group1.add_user(user, GroupMember::DEVELOPER)
|
||||
group1.add_member(user, GroupMember::DEVELOPER)
|
||||
group2 = create :group, name: 'Group 2', require_two_factor_authentication: true
|
||||
group2.add_user(user, GroupMember::DEVELOPER)
|
||||
group2.add_member(user, GroupMember::DEVELOPER)
|
||||
end
|
||||
|
||||
context 'with grace period defined' do
|
||||
|
|
|
@ -62,7 +62,7 @@ RSpec.describe Autocomplete::UsersFinder do
|
|||
let_it_be(:group) { create(:group, :public) }
|
||||
|
||||
before_all do
|
||||
group.add_users([user1], GroupMember::DEVELOPER)
|
||||
group.add_members([user1], GroupMember::DEVELOPER)
|
||||
end
|
||||
|
||||
it { is_expected.to match_array([user1]) }
|
||||
|
|
|
@ -45,7 +45,7 @@ RSpec.describe JoinedGroupsFinder do
|
|||
context 'if profile visitor is in one of the private group projects' do
|
||||
before do
|
||||
project = create(:project, :private, group: private_group, name: 'B', path: 'B')
|
||||
project.add_user(profile_visitor, Gitlab::Access::DEVELOPER)
|
||||
project.add_member(profile_visitor, Gitlab::Access::DEVELOPER)
|
||||
end
|
||||
|
||||
it 'shows group' do
|
||||
|
|
|
@ -45,7 +45,7 @@ RSpec.describe ::Packages::Conan::PackageFinder do
|
|||
|
||||
before do
|
||||
project.update_column(:visibility_level, Gitlab::VisibilityLevel.string_options[visibility.to_s])
|
||||
project.add_user(user, role) unless role == :anonymous
|
||||
project.add_member(user, role) unless role == :anonymous
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected_packages) }
|
||||
|
|
|
@ -98,8 +98,8 @@ RSpec.describe Packages::GroupPackagesFinder do
|
|||
)
|
||||
|
||||
unless role == :anonymous
|
||||
project.add_user(user, role)
|
||||
subproject.add_user(user, role)
|
||||
project.add_member(user, role)
|
||||
subproject.add_member(user, role)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,209 +1,373 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
|
||||
import { GlButton, GlModal } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import createFlash from '~/flash';
|
||||
import modal from '~/ide/components/new_dropdown/modal.vue';
|
||||
import Modal from '~/ide/components/new_dropdown/modal.vue';
|
||||
import { createStore } from '~/ide/stores';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { createEntriesFromPaths } from '../../helpers';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
const NEW_NAME = 'babar';
|
||||
|
||||
describe('new file modal component', () => {
|
||||
const Component = Vue.extend(modal);
|
||||
let vm;
|
||||
const showModal = jest.fn();
|
||||
const toggleModal = jest.fn();
|
||||
|
||||
let store;
|
||||
let wrapper;
|
||||
|
||||
const findGlModal = () => wrapper.findComponent(GlModal);
|
||||
const findInput = () => wrapper.findByTestId('file-name-field');
|
||||
const findTemplateButtons = () => wrapper.findAllComponents(GlButton);
|
||||
const findTemplateButtonsModel = () =>
|
||||
findTemplateButtons().wrappers.map((x) => ({
|
||||
text: x.text(),
|
||||
variant: x.props('variant'),
|
||||
category: x.props('category'),
|
||||
}));
|
||||
|
||||
const open = (type, path) => {
|
||||
// TODO: This component can not be passed props
|
||||
// We have to interact with the open() method?
|
||||
wrapper.vm.open(type, path);
|
||||
};
|
||||
const triggerSubmit = () => {
|
||||
findGlModal().vm.$emit('primary');
|
||||
};
|
||||
const triggerCancel = () => {
|
||||
findGlModal().vm.$emit('cancel');
|
||||
};
|
||||
|
||||
const mountComponent = () => {
|
||||
const GlModalStub = stubComponent(GlModal);
|
||||
jest.spyOn(GlModalStub.methods, 'show').mockImplementation(showModal);
|
||||
jest.spyOn(GlModalStub.methods, 'toggle').mockImplementation(toggleModal);
|
||||
|
||||
wrapper = shallowMountExtended(Modal, {
|
||||
store,
|
||||
stubs: {
|
||||
GlModal: GlModalStub,
|
||||
},
|
||||
// We need to attach to document for "focus" to work
|
||||
attachTo: document.body,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
|
||||
Object.assign(
|
||||
store.state.entries,
|
||||
createEntriesFromPaths([
|
||||
'README.md',
|
||||
'src',
|
||||
'src/deleted.js',
|
||||
'src/parent_dir',
|
||||
'src/parent_dir/foo.js',
|
||||
]),
|
||||
);
|
||||
Object.assign(store.state.entries['src/deleted.js'], { deleted: true });
|
||||
|
||||
jest.spyOn(store, 'dispatch').mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
store = null;
|
||||
wrapper.destroy();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('default', () => {
|
||||
beforeEach(async () => {
|
||||
mountComponent();
|
||||
|
||||
// Not necessarily needed, but used to ensure that nothing extra is happening after the tick
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
it('renders modal', () => {
|
||||
expect(findGlModal().props()).toMatchObject({
|
||||
actionCancel: {
|
||||
attributes: [{ variant: 'default' }],
|
||||
text: 'Cancel',
|
||||
},
|
||||
actionPrimary: {
|
||||
attributes: [{ variant: 'confirm' }],
|
||||
text: 'Create file',
|
||||
},
|
||||
actionSecondary: null,
|
||||
size: 'lg',
|
||||
modalId: 'ide-new-entry',
|
||||
title: 'Create new file',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders name label', () => {
|
||||
expect(wrapper.find('label').text()).toBe('Name');
|
||||
});
|
||||
|
||||
it('renders template buttons', () => {
|
||||
const actual = findTemplateButtonsModel();
|
||||
|
||||
expect(actual.length).toBeGreaterThan(0);
|
||||
expect(actual).toEqual(
|
||||
store.getters['fileTemplates/templateTypes'].map((template) => ({
|
||||
category: 'secondary',
|
||||
text: template.name,
|
||||
variant: 'dashed',
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
// These negative ".not.toHaveBeenCalled" assertions complement the positive "toHaveBeenCalled"
|
||||
// assertions that show up later in this spec. Without these, we're not guaranteed the "act"
|
||||
// actually caused the change in behavior.
|
||||
it('does not dispatch actions by default', () => {
|
||||
expect(store.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not trigger modal by default', () => {
|
||||
expect(showModal).not.toHaveBeenCalled();
|
||||
expect(toggleModal).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not focus input by default', () => {
|
||||
expect(document.activeElement).toBe(document.body);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
entryType | modalTitle | btnTitle | showsFileTemplates
|
||||
${'tree'} | ${'Create new directory'} | ${'Create directory'} | ${false}
|
||||
${'blob'} | ${'Create new file'} | ${'Create file'} | ${true}
|
||||
`('$entryType', ({ entryType, modalTitle, btnTitle, showsFileTemplates }) => {
|
||||
beforeEach(async () => {
|
||||
const store = createStore();
|
||||
entryType | path | modalTitle | btnTitle | showsFileTemplates | inputValue | inputPlaceholder
|
||||
${'tree'} | ${''} | ${'Create new directory'} | ${'Create directory'} | ${false} | ${''} | ${'dir/'}
|
||||
${'blob'} | ${''} | ${'Create new file'} | ${'Create file'} | ${true} | ${''} | ${'dir/file_name'}
|
||||
${'blob'} | ${'foo/bar'} | ${'Create new file'} | ${'Create file'} | ${true} | ${'foo/bar/'} | ${'dir/file_name'}
|
||||
`(
|
||||
'when opened as $entryType with path "$path"',
|
||||
({
|
||||
entryType,
|
||||
path,
|
||||
modalTitle,
|
||||
btnTitle,
|
||||
showsFileTemplates,
|
||||
inputValue,
|
||||
inputPlaceholder,
|
||||
}) => {
|
||||
beforeEach(async () => {
|
||||
mountComponent();
|
||||
|
||||
vm = createComponentWithStore(Component, store).$mount();
|
||||
vm.open(entryType);
|
||||
vm.name = 'testing';
|
||||
open(entryType, path);
|
||||
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
it('sets modal props', () => {
|
||||
expect(findGlModal().props()).toMatchObject({
|
||||
title: modalTitle,
|
||||
actionPrimary: {
|
||||
attributes: [{ variant: 'confirm' }],
|
||||
text: btnTitle,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sets input attributes', () => {
|
||||
expect(findInput().element.value).toBe(inputValue);
|
||||
expect(findInput().attributes('placeholder')).toBe(inputPlaceholder);
|
||||
});
|
||||
|
||||
it(`shows file templates: ${showsFileTemplates}`, () => {
|
||||
const actual = findTemplateButtonsModel().length > 0;
|
||||
|
||||
expect(actual).toBe(showsFileTemplates);
|
||||
});
|
||||
|
||||
it('shows modal', () => {
|
||||
expect(showModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('focus on input', () => {
|
||||
expect(document.activeElement).toBe(findInput().element);
|
||||
});
|
||||
|
||||
it('resets when canceled', async () => {
|
||||
triggerCancel();
|
||||
|
||||
await nextTick();
|
||||
|
||||
// Resets input value
|
||||
expect(findInput().element.value).toBe('');
|
||||
// Resets to blob mode
|
||||
expect(findGlModal().props('title')).toBe('Create new file');
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe.each`
|
||||
modalType | name | expectedName
|
||||
${'blob'} | ${'foo/bar.js'} | ${'foo/bar.js'}
|
||||
${'blob'} | ${'foo /bar.js'} | ${'foo/bar.js'}
|
||||
${'tree'} | ${'foo/dir'} | ${'foo/dir'}
|
||||
${'tree'} | ${'foo /dir'} | ${'foo/dir'}
|
||||
`('when submitting as $modalType with "$name"', ({ modalType, name, expectedName }) => {
|
||||
beforeEach(async () => {
|
||||
mountComponent();
|
||||
|
||||
open(modalType, '');
|
||||
await nextTick();
|
||||
|
||||
findInput().setValue(name);
|
||||
triggerSubmit();
|
||||
});
|
||||
|
||||
it('triggers createTempEntry action', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
|
||||
name: expectedName,
|
||||
type: modalType,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when creating from template type', () => {
|
||||
beforeEach(async () => {
|
||||
mountComponent();
|
||||
|
||||
open('blob', 'some_dir');
|
||||
|
||||
await nextTick();
|
||||
|
||||
// Set input, then trigger button
|
||||
findInput().setValue('some_dir/foo.js');
|
||||
findTemplateButtons().at(1).vm.$emit('click');
|
||||
});
|
||||
|
||||
it('triggers createTempEntry action', () => {
|
||||
const { name: expectedName } = store.getters['fileTemplates/templateTypes'][1];
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith('createTempEntry', {
|
||||
name: `some_dir/${expectedName}`,
|
||||
type: 'blob',
|
||||
});
|
||||
});
|
||||
|
||||
it('toggles modal', () => {
|
||||
expect(toggleModal).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
origPath | title | inputValue | inputSelectionStart
|
||||
${'src/parent_dir'} | ${'Rename folder'} | ${'src/parent_dir'} | ${'src/'.length}
|
||||
${'README.md'} | ${'Rename file'} | ${'README.md'} | ${0}
|
||||
`('when renaming for $origPath', ({ origPath, title, inputValue, inputSelectionStart }) => {
|
||||
beforeEach(async () => {
|
||||
mountComponent();
|
||||
|
||||
open('rename', origPath);
|
||||
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.close();
|
||||
});
|
||||
|
||||
it(`sets modal title as ${entryType}`, () => {
|
||||
expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle);
|
||||
});
|
||||
|
||||
it(`sets button label as ${entryType}`, () => {
|
||||
expect(document.querySelector('.btn-confirm').textContent.trim()).toBe(btnTitle);
|
||||
});
|
||||
|
||||
it(`sets form label as ${entryType}`, () => {
|
||||
expect(document.querySelector('.label-bold').textContent.trim()).toBe('Name');
|
||||
});
|
||||
|
||||
it(`shows file templates: ${showsFileTemplates}`, () => {
|
||||
const templateFilesEl = document.querySelector('.file-templates');
|
||||
expect(Boolean(templateFilesEl)).toBe(showsFileTemplates);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rename entry', () => {
|
||||
beforeEach(() => {
|
||||
const store = createStore();
|
||||
store.state.entries = {
|
||||
'test-path': {
|
||||
name: 'test',
|
||||
type: 'blob',
|
||||
path: 'test-path',
|
||||
it('sets modal props for renaming', () => {
|
||||
expect(findGlModal().props()).toMatchObject({
|
||||
title,
|
||||
actionPrimary: {
|
||||
attributes: [{ variant: 'confirm' }],
|
||||
text: title,
|
||||
},
|
||||
};
|
||||
|
||||
vm = createComponentWithStore(Component, store).$mount();
|
||||
});
|
||||
|
||||
it.each`
|
||||
entryType | modalTitle | btnTitle
|
||||
${'tree'} | ${'Rename folder'} | ${'Rename folder'}
|
||||
${'blob'} | ${'Rename file'} | ${'Rename file'}
|
||||
`(
|
||||
'renders title and button for renaming $entryType',
|
||||
async ({ entryType, modalTitle, btnTitle }) => {
|
||||
vm.$store.state.entries['test-path'].type = entryType;
|
||||
vm.open('rename', 'test-path');
|
||||
|
||||
await nextTick();
|
||||
expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle);
|
||||
expect(document.querySelector('.btn-confirm').textContent.trim()).toBe(btnTitle);
|
||||
},
|
||||
);
|
||||
|
||||
describe('entryName', () => {
|
||||
it('returns entries name', () => {
|
||||
vm.open('rename', 'test-path');
|
||||
|
||||
expect(vm.entryName).toBe('test-path');
|
||||
});
|
||||
|
||||
it('does not reset entryName to its old value if empty', () => {
|
||||
vm.entryName = 'hello';
|
||||
vm.entryName = '';
|
||||
|
||||
expect(vm.entryName).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('open', () => {
|
||||
it('sets entryName to path provided if modalType is rename', () => {
|
||||
vm.open('rename', 'test-path');
|
||||
|
||||
expect(vm.entryName).toBe('test-path');
|
||||
});
|
||||
|
||||
it("appends '/' to the path if modalType isn't rename", () => {
|
||||
vm.open('blob', 'test-path');
|
||||
|
||||
expect(vm.entryName).toBe('test-path/');
|
||||
});
|
||||
|
||||
it('leaves entryName blank if no path is provided', () => {
|
||||
vm.open('blob');
|
||||
|
||||
expect(vm.entryName).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFromTemplate', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
store.state.entries = {
|
||||
'test-path/test': {
|
||||
name: 'test',
|
||||
deleted: false,
|
||||
},
|
||||
};
|
||||
|
||||
vm = createComponentWithStore(Component, store).$mount();
|
||||
vm.open('blob');
|
||||
|
||||
jest.spyOn(vm, 'createTempEntry').mockImplementation();
|
||||
it('sets input value', () => {
|
||||
expect(findInput().element.value).toBe(inputValue);
|
||||
});
|
||||
|
||||
it.each`
|
||||
entryName | newFilePath
|
||||
${''} | ${'.gitignore'}
|
||||
${'README.md'} | ${'.gitignore'}
|
||||
${'test-path/test/'} | ${'test-path/test/.gitignore'}
|
||||
${'test-path/test'} | ${'test-path/.gitignore'}
|
||||
${'test-path/test/abc.md'} | ${'test-path/test/.gitignore'}
|
||||
`(
|
||||
'creates a new file with the given template name in appropriate directory for path: $path',
|
||||
({ entryName, newFilePath }) => {
|
||||
vm.entryName = entryName;
|
||||
it(`does not show file templates`, () => {
|
||||
expect(findTemplateButtonsModel()).toHaveLength(0);
|
||||
});
|
||||
|
||||
vm.createFromTemplate({ name: '.gitignore' });
|
||||
it('shows modal when renaming', () => {
|
||||
expect(showModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(vm.createTempEntry).toHaveBeenCalledWith({
|
||||
name: newFilePath,
|
||||
type: 'blob',
|
||||
it('focus on input when renaming', () => {
|
||||
expect(document.activeElement).toBe(findInput().element);
|
||||
});
|
||||
|
||||
it('selects name part of the input', () => {
|
||||
expect(findInput().element.selectionStart).toBe(inputSelectionStart);
|
||||
expect(findInput().element.selectionEnd).toBe(origPath.length);
|
||||
});
|
||||
|
||||
describe('when renames is submitted successfully', () => {
|
||||
beforeEach(() => {
|
||||
findInput().setValue(NEW_NAME);
|
||||
triggerSubmit();
|
||||
});
|
||||
|
||||
it('dispatches renameEntry event', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith('renameEntry', {
|
||||
path: origPath,
|
||||
parentPath: '',
|
||||
name: NEW_NAME,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('does not trigger flash', () => {
|
||||
expect(createFlash).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('submitForm', () => {
|
||||
let store;
|
||||
describe('when renaming and file already exists', () => {
|
||||
beforeEach(async () => {
|
||||
mountComponent();
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
store.state.entries = {
|
||||
'test-path/test': {
|
||||
name: 'test',
|
||||
deleted: false,
|
||||
},
|
||||
};
|
||||
open('rename', 'src/parent_dir');
|
||||
|
||||
vm = createComponentWithStore(Component, store).$mount();
|
||||
await nextTick();
|
||||
|
||||
// Set to something that already exists!
|
||||
findInput().setValue('src');
|
||||
triggerSubmit();
|
||||
});
|
||||
|
||||
it('throws an error when target entry exists', () => {
|
||||
vm.open('rename', 'test-path/test');
|
||||
|
||||
expect(createFlash).not.toHaveBeenCalled();
|
||||
|
||||
vm.submitForm();
|
||||
|
||||
it('creates flash', () => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: 'The name "test-path/test" is already taken in this directory.',
|
||||
message: 'The name "src" is already taken in this directory.',
|
||||
fadeTransition: false,
|
||||
addBodyClass: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not throw error when target entry does not exist', () => {
|
||||
jest.spyOn(vm, 'renameEntry').mockImplementation();
|
||||
it('does not dispatch event', () => {
|
||||
expect(store.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
vm.open('rename', 'test-path/test');
|
||||
vm.entryName = 'test-path/test2';
|
||||
vm.submitForm();
|
||||
describe('when renaming and file has been deleted', () => {
|
||||
beforeEach(async () => {
|
||||
mountComponent();
|
||||
|
||||
open('rename', 'src/parent_dir/foo.js');
|
||||
|
||||
await nextTick();
|
||||
|
||||
findInput().setValue('src/deleted.js');
|
||||
triggerSubmit();
|
||||
});
|
||||
|
||||
it('does not create flash', () => {
|
||||
expect(createFlash).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('removes leading/trailing found in the new name', () => {
|
||||
vm.open('rename', 'test-path/test');
|
||||
|
||||
vm.entryName = 'test-path /test';
|
||||
|
||||
vm.submitForm();
|
||||
|
||||
expect(vm.entryName).toBe('test-path/test');
|
||||
it('dispatches event', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith('renameEntry', {
|
||||
path: 'src/parent_dir/foo.js',
|
||||
name: 'deleted.js',
|
||||
parentPath: 'src',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ RSpec.describe Resolvers::Ci::JobTokenScopeResolver do
|
|||
describe '#resolve' do
|
||||
context 'with access to scope' do
|
||||
before do
|
||||
project.add_user(current_user, :maintainer)
|
||||
project.add_member(current_user, :maintainer)
|
||||
end
|
||||
|
||||
it 'returns nil when scope is not enabled' do
|
||||
|
@ -51,7 +51,7 @@ RSpec.describe Resolvers::Ci::JobTokenScopeResolver do
|
|||
|
||||
context 'without access to scope' do
|
||||
before do
|
||||
project.add_user(current_user, :developer)
|
||||
project.add_member(current_user, :developer)
|
||||
end
|
||||
|
||||
it 'generates an error' do
|
||||
|
|
|
@ -62,7 +62,7 @@ RSpec.describe Resolvers::ContainerRepositoriesResolver do
|
|||
|
||||
context 'with authorized user' do
|
||||
before do
|
||||
group.add_user(user, :maintainer)
|
||||
group.add_member(user, :maintainer)
|
||||
end
|
||||
|
||||
context 'when the object is a project' do
|
||||
|
|
|
@ -38,7 +38,7 @@ RSpec.describe GitlabSchema.types['CiJobTokenScopeType'] do
|
|||
|
||||
context 'with access to scope' do
|
||||
before do
|
||||
project.add_user(current_user, :maintainer)
|
||||
project.add_member(current_user, :maintainer)
|
||||
end
|
||||
|
||||
context 'when multiple projects in the allow list' do
|
||||
|
@ -46,7 +46,7 @@ RSpec.describe GitlabSchema.types['CiJobTokenScopeType'] do
|
|||
|
||||
context 'when linked projects are readable' do
|
||||
before do
|
||||
link.target_project.add_user(current_user, :developer)
|
||||
link.target_project.add_member(current_user, :developer)
|
||||
end
|
||||
|
||||
it 'returns readable projects in scope' do
|
||||
|
|
|
@ -471,21 +471,60 @@ RSpec.describe DiffHelper do
|
|||
|
||||
describe '#conflicts' do
|
||||
let(:merge_request) { instance_double(MergeRequest) }
|
||||
let(:merge_ref_head_diff) { true }
|
||||
let(:can_be_resolved_in_ui?) { true }
|
||||
let(:allow_tree_conflicts) { false }
|
||||
let(:files) { [instance_double(Gitlab::Conflict::File, path: 'a')] }
|
||||
let(:exception) { nil }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:merge_request).and_return(merge_request)
|
||||
allow(helper).to receive(:options).and_return(merge_ref_head_diff: true)
|
||||
allow(helper).to receive(:options).and_return(merge_ref_head_diff: merge_ref_head_diff)
|
||||
|
||||
allow_next_instance_of(MergeRequests::Conflicts::ListService, merge_request, allow_tree_conflicts: allow_tree_conflicts) do |svc|
|
||||
allow(svc).to receive(:can_be_resolved_in_ui?).and_return(can_be_resolved_in_ui?)
|
||||
|
||||
if exception.present?
|
||||
allow(svc).to receive_message_chain(:conflicts, :files).and_raise(exception)
|
||||
else
|
||||
allow(svc).to receive_message_chain(:conflicts, :files).and_return(files)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns list of conflicts indexed by path' do
|
||||
expect(helper.conflicts).to eq('a' => files.first)
|
||||
end
|
||||
|
||||
context 'when merge_ref_head_diff option is false' do
|
||||
let(:merge_ref_head_diff) { false }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(helper.conflicts).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conflicts cannot be resolved in UI' do
|
||||
let(:can_be_resolved_in_ui?) { false }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(helper.conflicts).to be_nil
|
||||
end
|
||||
|
||||
context 'when allow_tree_conflicts is true' do
|
||||
let(:allow_tree_conflicts) { true }
|
||||
|
||||
it 'returns list of conflicts' do
|
||||
expect(helper.conflicts(allow_tree_conflicts: allow_tree_conflicts)).to eq('a' => files.first)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Gitlab::Git::Conflict::Resolver::ConflictSideMissing exception is raised' do
|
||||
before do
|
||||
allow_next_instance_of(MergeRequests::Conflicts::ListService, merge_request, allow_tree_conflicts: true) do |svc|
|
||||
allow(svc).to receive_message_chain(:conflicts, :files).and_raise(Gitlab::Git::Conflict::Resolver::ConflictSideMissing)
|
||||
end
|
||||
end
|
||||
let(:exception) { Gitlab::Git::Conflict::Resolver::ConflictSideMissing }
|
||||
|
||||
it 'returns an empty hash' do
|
||||
expect(helper.conflicts(allow_tree_conflicts: true)).to eq({})
|
||||
expect(helper.conflicts).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe NamespaceStorageLimitAlertHelper do
|
||||
describe '#display_namespace_storage_limit_alert!' do
|
||||
it 'is defined in CE' do
|
||||
expect { helper.display_namespace_storage_limit_alert! }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -698,7 +698,7 @@ RSpec.describe ProjectsHelper do
|
|||
def grant_user_access(project, user, access)
|
||||
case access
|
||||
when :developer, :maintainer
|
||||
project.add_user(user, access)
|
||||
project.add_member(user, access)
|
||||
when :owner
|
||||
project.namespace.update!(owner: user)
|
||||
end
|
||||
|
|
|
@ -133,6 +133,16 @@ RSpec.describe TodosHelper do
|
|||
expect(path).to eq("/#{todo.project.full_path}/-/work_items/#{todo.target.id}")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given an issue with a note anchor' do
|
||||
let(:todo) { create(:todo, project: issue.project, target: issue, note: note) }
|
||||
|
||||
it 'responds with an appropriate path' do
|
||||
path = helper.todo_target_path(todo)
|
||||
|
||||
expect(path).to eq("/#{issue.project.full_path}/-/issues/#{issue.iid}##{dom_id(note)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#todo_target_type_name' do
|
||||
|
|
|
@ -24,7 +24,7 @@ RSpec.describe Banzai::ReferenceParser::SnippetParser do
|
|||
end
|
||||
|
||||
before do
|
||||
project.add_user(project_member, :developer)
|
||||
project.add_member(project_member, :developer)
|
||||
end
|
||||
|
||||
describe '#nodes_visible_to_user' do
|
||||
|
|
|
@ -25,7 +25,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
|
|||
describe '#serialized_records' do
|
||||
shared_context 'when records are loaded by maintainer' do
|
||||
before do
|
||||
project.add_user(user, Gitlab::Access::DEVELOPER)
|
||||
project.add_member(user, Gitlab::Access::DEVELOPER)
|
||||
end
|
||||
|
||||
it 'returns all records' do
|
||||
|
@ -72,7 +72,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
|
|||
|
||||
context 'when records are loaded by guest' do
|
||||
before do
|
||||
project.add_user(user, Gitlab::Access::GUEST)
|
||||
project.add_member(user, Gitlab::Access::GUEST)
|
||||
end
|
||||
|
||||
it 'filters out confidential issues' do
|
||||
|
@ -124,7 +124,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
|
|||
end
|
||||
|
||||
before do
|
||||
project.add_user(user, Gitlab::Access::DEVELOPER)
|
||||
project.add_member(user, Gitlab::Access::DEVELOPER)
|
||||
|
||||
stub_const('Gitlab::Analytics::CycleAnalytics::RecordsFetcher::MAX_RECORDS', 2)
|
||||
end
|
||||
|
|
|
@ -166,9 +166,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder do
|
|||
allow(builder).to receive(:secret_instance_variables) { [var('J', 10), var('K', 10)] }
|
||||
allow(builder).to receive(:secret_group_variables) { [var('K', 11), var('L', 11)] }
|
||||
allow(builder).to receive(:secret_project_variables) { [var('L', 12), var('M', 12)] }
|
||||
allow(job).to receive(:trigger_request) { double(user_variables: [var('M', 13), var('N', 13)]) }
|
||||
allow(pipeline).to receive(:variables) { [var('N', 14), var('O', 14)] }
|
||||
allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('O', 15), var('P', 15)]) }
|
||||
allow(pipeline).to receive(:variables) { [var('M', 13), var('N', 13)] }
|
||||
allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('N', 14), var('O', 14)]) }
|
||||
end
|
||||
|
||||
it 'returns variables in order depending on resource hierarchy' do
|
||||
|
@ -185,8 +184,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder do
|
|||
var('K', 11), var('L', 11),
|
||||
var('L', 12), var('M', 12),
|
||||
var('M', 13), var('N', 13),
|
||||
var('N', 14), var('O', 14),
|
||||
var('O', 15), var('P', 15)])
|
||||
var('N', 14), var('O', 14)])
|
||||
end
|
||||
|
||||
it 'overrides duplicate keys depending on resource hierarchy' do
|
||||
|
@ -198,7 +196,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder do
|
|||
'I' => '9', 'J' => '10',
|
||||
'K' => '11', 'L' => '12',
|
||||
'M' => '13', 'N' => '14',
|
||||
'O' => '15', 'P' => '15')
|
||||
'O' => '14')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ RSpec.describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do
|
|||
admin2 = create(:user, :admin)
|
||||
|
||||
existing_group.add_owner(user)
|
||||
existing_group.add_users([admin1, admin2], Gitlab::Access::MAINTAINER)
|
||||
existing_group.add_members([admin1, admin2], Gitlab::Access::MAINTAINER)
|
||||
|
||||
application_setting.instance_administrators_group_id = existing_group.id
|
||||
end
|
||||
|
|
|
@ -171,4 +171,27 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do
|
|||
expect(described_class.batch_size(exportable)).to eq(described_class::BATCH_SIZE)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#serialize_relation' do
|
||||
context 'when record is a merge request' do
|
||||
let(:json_writer) do
|
||||
Class.new do
|
||||
def write_relation_array(_, _, enumerator)
|
||||
enumerator.each { _1 }
|
||||
end
|
||||
end.new
|
||||
end
|
||||
|
||||
it 'removes cached external diff' do
|
||||
merge_request = create(:merge_request, source_project: exportable, target_project: exportable)
|
||||
cache_dir = merge_request.merge_request_diff.send(:external_diff_cache_dir)
|
||||
|
||||
expect(subject).to receive(:remove_cached_external_diff).with(merge_request).twice
|
||||
|
||||
subject.serialize_relation({ merge_requests: { include: [] } })
|
||||
|
||||
expect(Dir.exist?(cache_dir)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -258,7 +258,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
|||
end
|
||||
|
||||
before do
|
||||
group.add_users([user, user2], GroupMember::DEVELOPER)
|
||||
group.add_members([user, user2], GroupMember::DEVELOPER)
|
||||
end
|
||||
|
||||
it 'maps the project member' do
|
||||
|
@ -281,7 +281,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
|||
end
|
||||
|
||||
before do
|
||||
group.add_users([user, user2], GroupMember::DEVELOPER)
|
||||
group.add_members([user, user2], GroupMember::DEVELOPER)
|
||||
end
|
||||
|
||||
it 'maps the importer' do
|
||||
|
@ -315,7 +315,7 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
|||
|
||||
shared_examples_for 'it fetches the access level from parent group' do
|
||||
before do
|
||||
group.add_users([user], group_access_level)
|
||||
group.add_members([user], group_access_level)
|
||||
end
|
||||
|
||||
it "and resolves it correctly" do
|
||||
|
|
|
@ -875,7 +875,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
context 'with group visibility' do
|
||||
before do
|
||||
group = create(:group, visibility_level: group_visibility)
|
||||
group.add_users([user], GroupMember::MAINTAINER)
|
||||
group.add_members([user], GroupMember::MAINTAINER)
|
||||
project.update!(group: group)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::QuickActions::UsersExtractor do
|
||||
subject(:extractor) { described_class.new(current_user, project: project, group: group, target: target, text: text) }
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:target) { create(:issue, project: project) }
|
||||
|
||||
let_it_be(:pancakes) { create(:user, username: 'pancakes') }
|
||||
let_it_be(:waffles) { create(:user, username: 'waffles') }
|
||||
let_it_be(:syrup) { create(:user, username: 'syrup') }
|
||||
|
||||
before do
|
||||
allow(target).to receive(:allows_multiple_assignees?).and_return(false)
|
||||
end
|
||||
|
||||
context 'when the text is nil' do
|
||||
let(:text) { nil }
|
||||
|
||||
it 'returns an empty array' do
|
||||
expect(extractor.execute).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the text is blank' do
|
||||
let(:text) { ' ' }
|
||||
|
||||
it 'returns an empty array' do
|
||||
expect(extractor.execute).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are users to be found' do
|
||||
context 'when using usernames' do
|
||||
let(:text) { 'me, pancakes waffles and syrup' }
|
||||
|
||||
it 'finds the users' do
|
||||
expect(extractor.execute).to contain_exactly(current_user, pancakes, waffles, syrup)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are too many users' do
|
||||
let(:text) { 'me, pancakes waffles and syrup' }
|
||||
|
||||
before do
|
||||
stub_const("#{described_class}::MAX_QUICK_ACTION_USERS", 2)
|
||||
end
|
||||
|
||||
it 'complains' do
|
||||
expect { extractor.execute }.to raise_error(described_class::TooManyError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using references' do
|
||||
let(:text) { 'me, @pancakes @waffles and @syrup' }
|
||||
|
||||
it 'finds the users' do
|
||||
expect(extractor.execute).to contain_exactly(current_user, pancakes, waffles, syrup)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using a mixture of usernames and references' do
|
||||
let(:text) { 'me, @pancakes waffles and @syrup' }
|
||||
|
||||
it 'finds the users' do
|
||||
expect(extractor.execute).to contain_exactly(current_user, pancakes, waffles, syrup)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when one or more users cannot be found' do
|
||||
let(:text) { 'me, @bacon @pancakes, chicken waffles and @syrup' }
|
||||
|
||||
it 'reports an error' do
|
||||
expect { extractor.execute }.to raise_error(described_class::MissingError, include('bacon', 'chicken'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trying to find group members' do
|
||||
let(:group) { create(:group, path: 'breakfast-foods') }
|
||||
let(:text) { group.to_reference }
|
||||
|
||||
it 'reports an error' do
|
||||
[pancakes, waffles].each { group.add_developer(_1) }
|
||||
|
||||
expect { extractor.execute }.to raise_error(described_class::MissingError, include('breakfast-foods'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -219,19 +219,19 @@ RSpec.describe Gitlab::UserAccess do
|
|||
describe '#can_create_tag?' do
|
||||
describe 'push to none protected tag' do
|
||||
it 'returns true if user is a maintainer' do
|
||||
project.add_user(user, :maintainer)
|
||||
project.add_member(user, :maintainer)
|
||||
|
||||
expect(access.can_create_tag?('random_tag')).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true if user is a developer' do
|
||||
project.add_user(user, :developer)
|
||||
project.add_member(user, :developer)
|
||||
|
||||
expect(access.can_create_tag?('random_tag')).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if user is a reporter' do
|
||||
project.add_user(user, :reporter)
|
||||
project.add_member(user, :reporter)
|
||||
|
||||
expect(access.can_create_tag?('random_tag')).to be_falsey
|
||||
end
|
||||
|
@ -242,19 +242,19 @@ RSpec.describe Gitlab::UserAccess do
|
|||
let(:not_existing_tag) { create :protected_tag, project: project }
|
||||
|
||||
it 'returns true if user is a maintainer' do
|
||||
project.add_user(user, :maintainer)
|
||||
project.add_member(user, :maintainer)
|
||||
|
||||
expect(access.can_create_tag?(tag.name)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if user is a developer' do
|
||||
project.add_user(user, :developer)
|
||||
project.add_member(user, :developer)
|
||||
|
||||
expect(access.can_create_tag?(tag.name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns false if user is a reporter' do
|
||||
project.add_user(user, :reporter)
|
||||
project.add_member(user, :reporter)
|
||||
|
||||
expect(access.can_create_tag?(tag.name)).to be_falsey
|
||||
end
|
||||
|
@ -266,19 +266,19 @@ RSpec.describe Gitlab::UserAccess do
|
|||
end
|
||||
|
||||
it 'returns true if user is a maintainer' do
|
||||
project.add_user(user, :maintainer)
|
||||
project.add_member(user, :maintainer)
|
||||
|
||||
expect(access.can_create_tag?(@tag.name)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true if user is a developer' do
|
||||
project.add_user(user, :developer)
|
||||
project.add_member(user, :developer)
|
||||
|
||||
expect(access.can_create_tag?(@tag.name)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if user is a reporter' do
|
||||
project.add_user(user, :reporter)
|
||||
project.add_member(user, :reporter)
|
||||
|
||||
expect(access.can_create_tag?(@tag.name)).to be_falsey
|
||||
end
|
||||
|
@ -288,19 +288,19 @@ RSpec.describe Gitlab::UserAccess do
|
|||
describe '#can_delete_branch?' do
|
||||
describe 'delete unprotected branch' do
|
||||
it 'returns true if user is a maintainer' do
|
||||
project.add_user(user, :maintainer)
|
||||
project.add_member(user, :maintainer)
|
||||
|
||||
expect(access.can_delete_branch?('random_branch')).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true if user is a developer' do
|
||||
project.add_user(user, :developer)
|
||||
project.add_member(user, :developer)
|
||||
|
||||
expect(access.can_delete_branch?('random_branch')).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if user is a reporter' do
|
||||
project.add_user(user, :reporter)
|
||||
project.add_member(user, :reporter)
|
||||
|
||||
expect(access.can_delete_branch?('random_branch')).to be_falsey
|
||||
end
|
||||
|
@ -310,19 +310,19 @@ RSpec.describe Gitlab::UserAccess do
|
|||
let(:branch) { create(:protected_branch, project: project, name: "test") }
|
||||
|
||||
it 'returns true if user is a maintainer' do
|
||||
project.add_user(user, :maintainer)
|
||||
project.add_member(user, :maintainer)
|
||||
|
||||
expect(access.can_delete_branch?(branch.name)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false if user is a developer' do
|
||||
project.add_user(user, :developer)
|
||||
project.add_member(user, :developer)
|
||||
|
||||
expect(access.can_delete_branch?(branch.name)).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns false if user is a reporter' do
|
||||
project.add_user(user, :reporter)
|
||||
project.add_member(user, :reporter)
|
||||
|
||||
expect(access.can_delete_branch?(branch.name)).to be_falsey
|
||||
end
|
||||
|
@ -334,7 +334,7 @@ RSpec.describe Gitlab::UserAccess do
|
|||
|
||||
context 'when user cannot push_code to a project repository (eg. as a guest)' do
|
||||
it 'is false' do
|
||||
project.add_user(user, :guest)
|
||||
project.add_member(user, :guest)
|
||||
|
||||
expect(access.can_push_for_ref?(ref)).to be_falsey
|
||||
end
|
||||
|
@ -342,7 +342,7 @@ RSpec.describe Gitlab::UserAccess do
|
|||
|
||||
context 'when user can push_code to a project repository (eg. as a developer)' do
|
||||
it 'is true' do
|
||||
project.add_user(user, :developer)
|
||||
project.add_member(user, :developer)
|
||||
|
||||
expect(access.can_push_for_ref?(ref)).to be_truthy
|
||||
end
|
||||
|
|
|
@ -1550,7 +1550,7 @@ RSpec.describe Notify do
|
|||
end
|
||||
|
||||
describe 'invitations' do
|
||||
let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } }
|
||||
let(:owner) { create(:user).tap { |u| group.add_member(u, Gitlab::Access::OWNER) } }
|
||||
let(:group_member) { invite_to_group(group, inviter: inviter) }
|
||||
let(:inviter) { owner }
|
||||
|
||||
|
@ -1605,7 +1605,7 @@ RSpec.describe Notify do
|
|||
end
|
||||
|
||||
describe 'group invitation reminders' do
|
||||
let_it_be(:inviter) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } }
|
||||
let_it_be(:inviter) { create(:user).tap { |u| group.add_member(u, Gitlab::Access::OWNER) } }
|
||||
|
||||
let(:group_member) { invite_to_group(group, inviter: inviter) }
|
||||
|
||||
|
@ -1688,7 +1688,7 @@ RSpec.describe Notify do
|
|||
|
||||
describe 'group invitation accepted' do
|
||||
let(:invited_user) { create(:user, name: 'invited user') }
|
||||
let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } }
|
||||
let(:owner) { create(:user).tap { |u| group.add_member(u, Gitlab::Access::OWNER) } }
|
||||
let(:group_member) do
|
||||
invitee = invite_to_group(group, inviter: owner)
|
||||
invitee.accept_invite!(invited_user)
|
||||
|
@ -1714,7 +1714,7 @@ RSpec.describe Notify do
|
|||
end
|
||||
|
||||
describe 'group invitation declined' do
|
||||
let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } }
|
||||
let(:owner) { create(:user).tap { |u| group.add_member(u, Gitlab::Access::OWNER) } }
|
||||
let(:group_member) do
|
||||
invitee = invite_to_group(group, inviter: owner)
|
||||
invitee.decline_invite!
|
||||
|
|
|
@ -3251,10 +3251,6 @@ RSpec.describe Ci::Build do
|
|||
let(:trigger) { create(:ci_trigger, project: project) }
|
||||
let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) }
|
||||
|
||||
let(:user_trigger_variable) do
|
||||
{ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false, masked: false }
|
||||
end
|
||||
|
||||
let(:predefined_trigger_variable) do
|
||||
{ key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true, masked: false }
|
||||
end
|
||||
|
@ -3263,26 +3259,7 @@ RSpec.describe Ci::Build do
|
|||
build.trigger_request = trigger_request
|
||||
end
|
||||
|
||||
shared_examples 'returns variables for triggers' do
|
||||
it { is_expected.to include(user_trigger_variable) }
|
||||
it { is_expected.to include(predefined_trigger_variable) }
|
||||
end
|
||||
|
||||
context 'when variables are stored in trigger_request' do
|
||||
before do
|
||||
trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
|
||||
end
|
||||
|
||||
it_behaves_like 'returns variables for triggers'
|
||||
end
|
||||
|
||||
context 'when variables are stored in pipeline_variables' do
|
||||
before do
|
||||
create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
|
||||
end
|
||||
|
||||
it_behaves_like 'returns variables for triggers'
|
||||
end
|
||||
it { is_expected.to include(predefined_trigger_variable) }
|
||||
end
|
||||
|
||||
context 'when pipeline has a variable' do
|
||||
|
|
|
@ -726,7 +726,7 @@ RSpec.describe Group do
|
|||
|
||||
context 'when user is a member of private group' do
|
||||
before do
|
||||
private_group.add_user(user, Gitlab::Access::DEVELOPER)
|
||||
private_group.add_member(user, Gitlab::Access::DEVELOPER)
|
||||
end
|
||||
|
||||
it { is_expected.to match_array([private_group, internal_group, group]) }
|
||||
|
@ -736,7 +736,7 @@ RSpec.describe Group do
|
|||
let!(:private_subgroup) { create(:group, :private, parent: private_group) }
|
||||
|
||||
before do
|
||||
private_subgroup.add_user(user, Gitlab::Access::DEVELOPER)
|
||||
private_subgroup.add_member(user, Gitlab::Access::DEVELOPER)
|
||||
end
|
||||
|
||||
it { is_expected.to match_array([private_subgroup, internal_group, group]) }
|
||||
|
@ -848,7 +848,7 @@ RSpec.describe Group do
|
|||
expect(member).to receive(:refresh_member_authorized_projects).with(blocking: true)
|
||||
end
|
||||
|
||||
group.add_user(user, GroupMember::MAINTAINER)
|
||||
group.add_member(user, GroupMember::MAINTAINER)
|
||||
|
||||
expect(group.group_members.maintainers.map(&:user)).to include(user)
|
||||
end
|
||||
|
@ -858,7 +858,7 @@ RSpec.describe Group do
|
|||
expect(member).to receive(:refresh_member_authorized_projects).with(blocking: false)
|
||||
end
|
||||
|
||||
group.add_user(user, GroupMember::MAINTAINER, blocking_refresh: false)
|
||||
group.add_member(user, GroupMember::MAINTAINER, blocking_refresh: false)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -866,12 +866,12 @@ RSpec.describe Group do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
group.add_users([user.id], GroupMember::GUEST)
|
||||
group.add_members([user.id], GroupMember::GUEST)
|
||||
end
|
||||
|
||||
it "updates the group permission" do
|
||||
expect(group.group_members.guests.map(&:user)).to include(user)
|
||||
group.add_users([user.id], GroupMember::DEVELOPER)
|
||||
group.add_members([user.id], GroupMember::DEVELOPER)
|
||||
expect(group.group_members.developers.map(&:user)).to include(user)
|
||||
expect(group.group_members.guests.map(&:user)).not_to include(user)
|
||||
end
|
||||
|
@ -880,7 +880,7 @@ RSpec.describe Group do
|
|||
let!(:project) { create(:project, group: group) }
|
||||
|
||||
before do
|
||||
group.add_users([create(:user)], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: project.id)
|
||||
group.add_members([create(:user)], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: project.id)
|
||||
end
|
||||
|
||||
it 'creates a member_task with the correct attributes', :aggregate_failures do
|
||||
|
@ -896,7 +896,7 @@ RSpec.describe Group do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
group.add_user(user, GroupMember::MAINTAINER)
|
||||
group.add_member(user, GroupMember::MAINTAINER)
|
||||
end
|
||||
|
||||
it "is true if avatar is image" do
|
||||
|
@ -993,7 +993,7 @@ RSpec.describe Group do
|
|||
|
||||
context 'there is also a project_bot owner' do
|
||||
before do
|
||||
group.add_user(create(:user, :project_bot), GroupMember::OWNER)
|
||||
group.add_member(create(:user, :project_bot), GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it { expect(group.last_owner?(@members[:owner])).to be_truthy }
|
||||
|
@ -1024,7 +1024,7 @@ RSpec.describe Group do
|
|||
let(:member) { blocked_user.group_members.last }
|
||||
|
||||
before do
|
||||
group.add_user(blocked_user, GroupMember::OWNER)
|
||||
group.add_member(blocked_user, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
context 'when last_blocked_owner is set' do
|
||||
|
@ -1050,7 +1050,7 @@ RSpec.describe Group do
|
|||
|
||||
context 'with another active owner' do
|
||||
before do
|
||||
group.add_user(create(:user), GroupMember::OWNER)
|
||||
group.add_member(create(:user), GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it { expect(group.member_last_blocked_owner?(member)).to be(false) }
|
||||
|
@ -1058,7 +1058,7 @@ RSpec.describe Group do
|
|||
|
||||
context 'with 2 blocked owners' do
|
||||
before do
|
||||
group.add_user(create(:user, :blocked), GroupMember::OWNER)
|
||||
group.add_member(create(:user, :blocked), GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it { expect(group.member_last_blocked_owner?(member)).to be(false) }
|
||||
|
@ -1082,7 +1082,7 @@ RSpec.describe Group do
|
|||
describe '#single_blocked_owner?' do
|
||||
context 'when there is only one blocked owner' do
|
||||
before do
|
||||
group.add_user(blocked_user, GroupMember::OWNER)
|
||||
group.add_member(blocked_user, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
|
@ -1094,8 +1094,8 @@ RSpec.describe Group do
|
|||
let_it_be(:blocked_user_2) { create(:user, :blocked) }
|
||||
|
||||
before do
|
||||
group.add_user(blocked_user, GroupMember::OWNER)
|
||||
group.add_user(blocked_user_2, GroupMember::OWNER)
|
||||
group.add_member(blocked_user, GroupMember::OWNER)
|
||||
group.add_member(blocked_user_2, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
|
@ -1114,8 +1114,8 @@ RSpec.describe Group do
|
|||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
group.add_user(blocked_user, GroupMember::OWNER)
|
||||
group.add_user(user, GroupMember::OWNER)
|
||||
group.add_member(blocked_user, GroupMember::OWNER)
|
||||
group.add_member(user, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it 'has only blocked owners' do
|
||||
|
@ -1129,7 +1129,7 @@ RSpec.describe Group do
|
|||
|
||||
context 'when there is only one owner' do
|
||||
let!(:owner) do
|
||||
group.add_user(user, GroupMember::OWNER)
|
||||
group.add_member(user, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it 'returns the owner' do
|
||||
|
@ -1138,7 +1138,7 @@ RSpec.describe Group do
|
|||
|
||||
context 'and there is also a project_bot owner' do
|
||||
before do
|
||||
group.add_user(create(:user, :project_bot), GroupMember::OWNER)
|
||||
group.add_member(create(:user, :project_bot), GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it 'returns only the human owner' do
|
||||
|
@ -1151,11 +1151,11 @@ RSpec.describe Group do
|
|||
let_it_be(:user_2) { create(:user) }
|
||||
|
||||
let!(:owner) do
|
||||
group.add_user(user, GroupMember::OWNER)
|
||||
group.add_member(user, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
let!(:owner2) do
|
||||
group.add_user(user_2, GroupMember::OWNER)
|
||||
group.add_member(user_2, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it 'returns both owners' do
|
||||
|
@ -1164,7 +1164,7 @@ RSpec.describe Group do
|
|||
|
||||
context 'and there is also a project_bot owner' do
|
||||
before do
|
||||
group.add_user(create(:user, :project_bot), GroupMember::OWNER)
|
||||
group.add_member(create(:user, :project_bot), GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it 'returns only the human owners' do
|
||||
|
@ -1186,7 +1186,7 @@ RSpec.describe Group do
|
|||
let(:member) { group.members.last }
|
||||
|
||||
before do
|
||||
group.add_user(user, GroupMember::OWNER)
|
||||
group.add_member(user, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
context 'when last_owner is set' do
|
||||
|
@ -1284,11 +1284,11 @@ RSpec.describe Group do
|
|||
requester: create(:user)
|
||||
}
|
||||
|
||||
group.add_user(members[:owner], GroupMember::OWNER)
|
||||
group.add_user(members[:maintainer], GroupMember::MAINTAINER)
|
||||
group.add_user(members[:developer], GroupMember::DEVELOPER)
|
||||
group.add_user(members[:reporter], GroupMember::REPORTER)
|
||||
group.add_user(members[:guest], GroupMember::GUEST)
|
||||
group.add_member(members[:owner], GroupMember::OWNER)
|
||||
group.add_member(members[:maintainer], GroupMember::MAINTAINER)
|
||||
group.add_member(members[:developer], GroupMember::DEVELOPER)
|
||||
group.add_member(members[:reporter], GroupMember::REPORTER)
|
||||
group.add_member(members[:guest], GroupMember::GUEST)
|
||||
group.request_access(members[:requester])
|
||||
|
||||
members
|
||||
|
@ -1464,8 +1464,8 @@ RSpec.describe Group do
|
|||
|
||||
describe '#direct_members' do
|
||||
let_it_be(:group) { create(:group, :nested) }
|
||||
let_it_be(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
|
||||
let_it_be(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
|
||||
let_it_be(:maintainer) { group.parent.add_member(create(:user), GroupMember::MAINTAINER) }
|
||||
let_it_be(:developer) { group.add_member(create(:user), GroupMember::DEVELOPER) }
|
||||
|
||||
it 'does not return members of the parent' do
|
||||
expect(group.direct_members).not_to include(maintainer)
|
||||
|
@ -1491,8 +1491,8 @@ RSpec.describe Group do
|
|||
|
||||
shared_examples_for 'members_with_parents' do
|
||||
let!(:group) { create(:group, :nested) }
|
||||
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
|
||||
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
|
||||
let!(:maintainer) { group.parent.add_member(create(:user), GroupMember::MAINTAINER) }
|
||||
let!(:developer) { group.add_member(create(:user), GroupMember::DEVELOPER) }
|
||||
let!(:pending_maintainer) { create(:group_member, :awaiting, :maintainer, group: group.parent) }
|
||||
let!(:pending_developer) { create(:group_member, :awaiting, :developer, group: group) }
|
||||
|
||||
|
@ -1603,9 +1603,9 @@ RSpec.describe Group do
|
|||
context 'members-related methods' do
|
||||
let!(:group) { create(:group, :nested) }
|
||||
let!(:sub_group) { create(:group, parent: group) }
|
||||
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
|
||||
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
|
||||
let!(:other_developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
|
||||
let!(:maintainer) { group.parent.add_member(create(:user), GroupMember::MAINTAINER) }
|
||||
let!(:developer) { group.add_member(create(:user), GroupMember::DEVELOPER) }
|
||||
let!(:other_developer) { group.add_member(create(:user), GroupMember::DEVELOPER) }
|
||||
|
||||
describe '#direct_and_indirect_members' do
|
||||
it 'returns parents members' do
|
||||
|
@ -1619,7 +1619,7 @@ RSpec.describe Group do
|
|||
end
|
||||
|
||||
describe '#direct_and_indirect_members_with_inactive' do
|
||||
let!(:maintainer_blocked) { group.parent.add_user(create(:user, :blocked), GroupMember::MAINTAINER) }
|
||||
let!(:maintainer_blocked) { group.parent.add_member(create(:user, :blocked), GroupMember::MAINTAINER) }
|
||||
|
||||
it 'returns parents members' do
|
||||
expect(group.direct_and_indirect_members_with_inactive).to include(developer)
|
||||
|
@ -1795,8 +1795,8 @@ RSpec.describe Group do
|
|||
maintainer = create(:user)
|
||||
developer = create(:user)
|
||||
|
||||
group.add_user(maintainer, GroupMember::MAINTAINER)
|
||||
group.add_user(developer, GroupMember::DEVELOPER)
|
||||
group.add_member(maintainer, GroupMember::MAINTAINER)
|
||||
group.add_member(developer, GroupMember::DEVELOPER)
|
||||
|
||||
expect(group.user_ids_for_project_authorizations)
|
||||
.to include(maintainer.id, developer.id)
|
||||
|
@ -1847,7 +1847,7 @@ RSpec.describe Group do
|
|||
|
||||
context 'group membership' do
|
||||
before do
|
||||
group.add_user(user, GroupMember::OWNER)
|
||||
group.add_member(user, GroupMember::OWNER)
|
||||
end
|
||||
|
||||
it 'is called when require_two_factor_authentication is changed' do
|
||||
|
@ -1870,7 +1870,7 @@ RSpec.describe Group do
|
|||
|
||||
it 'calls #update_two_factor_requirement on each group member' do
|
||||
other_user = create(:user)
|
||||
group.add_user(other_user, GroupMember::OWNER)
|
||||
group.add_member(other_user, GroupMember::OWNER)
|
||||
|
||||
calls = 0
|
||||
allow_any_instance_of(User).to receive(:update_two_factor_requirement) do
|
||||
|
@ -1885,7 +1885,7 @@ RSpec.describe Group do
|
|||
|
||||
context 'sub groups and projects' do
|
||||
it 'enables two_factor_requirement for group member' do
|
||||
group.add_user(user, GroupMember::OWNER)
|
||||
group.add_member(user, GroupMember::OWNER)
|
||||
|
||||
group.update!(require_two_factor_authentication: true)
|
||||
|
||||
|
@ -1899,7 +1899,7 @@ RSpec.describe Group do
|
|||
context 'two_factor_requirement is also enabled for ancestor group' do
|
||||
it 'enables two_factor_requirement for subgroup member' do
|
||||
subgroup = create(:group, :nested, parent: group)
|
||||
subgroup.add_user(indirect_user, GroupMember::OWNER)
|
||||
subgroup.add_member(indirect_user, GroupMember::OWNER)
|
||||
|
||||
group.update!(require_two_factor_authentication: true)
|
||||
|
||||
|
@ -1910,7 +1910,7 @@ RSpec.describe Group do
|
|||
context 'two_factor_requirement is disabled for ancestor group' do
|
||||
it 'enables two_factor_requirement for subgroup member' do
|
||||
subgroup = create(:group, :nested, parent: group, require_two_factor_authentication: true)
|
||||
subgroup.add_user(indirect_user, GroupMember::OWNER)
|
||||
subgroup.add_member(indirect_user, GroupMember::OWNER)
|
||||
|
||||
group.update!(require_two_factor_authentication: false)
|
||||
|
||||
|
@ -1919,7 +1919,7 @@ RSpec.describe Group do
|
|||
|
||||
it 'enable two_factor_requirement for ancestor group member' do
|
||||
ancestor_group = create(:group)
|
||||
ancestor_group.add_user(indirect_user, GroupMember::OWNER)
|
||||
ancestor_group.add_member(indirect_user, GroupMember::OWNER)
|
||||
group.update!(parent: ancestor_group)
|
||||
|
||||
group.update!(require_two_factor_authentication: true)
|
||||
|
@ -1933,7 +1933,7 @@ RSpec.describe Group do
|
|||
context 'two_factor_requirement is enabled for ancestor group' do
|
||||
it 'enables two_factor_requirement for subgroup member' do
|
||||
subgroup = create(:group, :nested, parent: group)
|
||||
subgroup.add_user(indirect_user, GroupMember::OWNER)
|
||||
subgroup.add_member(indirect_user, GroupMember::OWNER)
|
||||
|
||||
group.update!(require_two_factor_authentication: true)
|
||||
|
||||
|
@ -1944,7 +1944,7 @@ RSpec.describe Group do
|
|||
context 'two_factor_requirement is also disabled for ancestor group' do
|
||||
it 'disables two_factor_requirement for subgroup member' do
|
||||
subgroup = create(:group, :nested, parent: group)
|
||||
subgroup.add_user(indirect_user, GroupMember::OWNER)
|
||||
subgroup.add_member(indirect_user, GroupMember::OWNER)
|
||||
|
||||
group.update!(require_two_factor_authentication: false)
|
||||
|
||||
|
@ -1954,7 +1954,7 @@ RSpec.describe Group do
|
|||
it 'disables two_factor_requirement for ancestor group member' do
|
||||
ancestor_group = create(:group, require_two_factor_authentication: false)
|
||||
indirect_user.update!(require_two_factor_authentication_from_group: true)
|
||||
ancestor_group.add_user(indirect_user, GroupMember::OWNER)
|
||||
ancestor_group.add_member(indirect_user, GroupMember::OWNER)
|
||||
|
||||
group.update!(require_two_factor_authentication: false)
|
||||
|
||||
|
|
|
@ -219,7 +219,7 @@ RSpec.describe GroupMember do
|
|||
end
|
||||
|
||||
context 'on create' do
|
||||
let(:action) { group.add_user(user, Gitlab::Access::GUEST) }
|
||||
let(:action) { group.add_member(user, Gitlab::Access::GUEST) }
|
||||
let(:blocking) { true }
|
||||
|
||||
it 'changes access level', :sidekiq_inline do
|
||||
|
@ -241,7 +241,7 @@ RSpec.describe GroupMember do
|
|||
|
||||
context 'on update' do
|
||||
before do
|
||||
group.add_user(user, Gitlab::Access::GUEST)
|
||||
group.add_member(user, Gitlab::Access::GUEST)
|
||||
end
|
||||
|
||||
let(:action) { group.members.find_by(user: user).update!(access_level: Gitlab::Access::DEVELOPER) }
|
||||
|
@ -266,7 +266,7 @@ RSpec.describe GroupMember do
|
|||
|
||||
context 'on destroy' do
|
||||
before do
|
||||
group.add_user(user, Gitlab::Access::GUEST)
|
||||
group.add_member(user, Gitlab::Access::GUEST)
|
||||
end
|
||||
|
||||
let(:action) { group.members.find_by(user: user).destroy! }
|
||||
|
|
|
@ -111,12 +111,12 @@ RSpec.describe ProjectMember do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.add_users_to_projects' do
|
||||
describe '.add_members_to_projects' do
|
||||
it 'adds the given users to the given projects' do
|
||||
projects = create_list(:project, 2)
|
||||
users = create_list(:user, 2)
|
||||
|
||||
described_class.add_users_to_projects(
|
||||
described_class.add_members_to_projects(
|
||||
[projects.first.id, projects.second.id],
|
||||
[users.first.id, users.second],
|
||||
described_class::MAINTAINER)
|
||||
|
@ -236,7 +236,7 @@ RSpec.describe ProjectMember do
|
|||
end
|
||||
|
||||
context 'on create' do
|
||||
let(:action) { project.add_user(user, Gitlab::Access::GUEST) }
|
||||
let(:action) { project.add_member(user, Gitlab::Access::GUEST) }
|
||||
|
||||
it 'changes access level' do
|
||||
expect { action }.to change { user.can?(:guest_access, project) }.from(false).to(true)
|
||||
|
@ -250,7 +250,7 @@ RSpec.describe ProjectMember do
|
|||
let(:action) { project.members.find_by(user: user).update!(access_level: Gitlab::Access::DEVELOPER) }
|
||||
|
||||
before do
|
||||
project.add_user(user, Gitlab::Access::GUEST)
|
||||
project.add_member(user, Gitlab::Access::GUEST)
|
||||
end
|
||||
|
||||
it 'changes access level' do
|
||||
|
@ -265,7 +265,7 @@ RSpec.describe ProjectMember do
|
|||
let(:action) { project.members.find_by(user: user).destroy! }
|
||||
|
||||
before do
|
||||
project.add_user(user, Gitlab::Access::GUEST)
|
||||
project.add_member(user, Gitlab::Access::GUEST)
|
||||
end
|
||||
|
||||
it 'changes access level', :sidekiq_inline do
|
||||
|
|
|
@ -13,50 +13,53 @@ RSpec.describe MergeRequestDiffFile do
|
|||
let(:invalid_items_for_bulk_insertion) { [] } # class does not have any validations defined
|
||||
end
|
||||
|
||||
let(:unpacked) { 'unpacked' }
|
||||
let(:packed) { [unpacked].pack('m0') }
|
||||
let(:file) { create(:merge_request).merge_request_diff.merge_request_diff_files.first }
|
||||
|
||||
describe '#diff' do
|
||||
let(:file) { build(:merge_request_diff_file) }
|
||||
|
||||
context 'when diff is not stored' do
|
||||
let(:unpacked) { 'unpacked' }
|
||||
let(:packed) { [unpacked].pack('m0') }
|
||||
|
||||
before do
|
||||
subject.diff = packed
|
||||
file.diff = packed
|
||||
end
|
||||
|
||||
context 'when the diff is marked as binary' do
|
||||
before do
|
||||
subject.binary = true
|
||||
file.binary = true
|
||||
end
|
||||
|
||||
it 'unpacks from base 64' do
|
||||
expect(subject.diff).to eq(unpacked)
|
||||
expect(file.diff).to eq(unpacked)
|
||||
end
|
||||
|
||||
context 'invalid base64' do
|
||||
let(:packed) { '---/dev/null' }
|
||||
|
||||
it 'returns the raw diff' do
|
||||
expect(subject.diff).to eq(packed)
|
||||
expect(file.diff).to eq(packed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the diff is not marked as binary' do
|
||||
it 'returns the raw diff' do
|
||||
expect(subject.diff).to eq(packed)
|
||||
expect(file.diff).to eq(packed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when diff is stored in DB' do
|
||||
let(:file) { create(:merge_request).merge_request_diff.merge_request_diff_files.first }
|
||||
|
||||
it 'returns UTF-8 string' do
|
||||
expect(file.diff.encoding).to eq Encoding::UTF_8
|
||||
end
|
||||
end
|
||||
|
||||
context 'when diff is stored in external storage' do
|
||||
let(:file) { create(:merge_request).merge_request_diff.merge_request_diff_files.first }
|
||||
let(:test_dir) { 'tmp/tests/external-diffs' }
|
||||
|
||||
around do |example|
|
||||
|
@ -81,17 +84,132 @@ RSpec.describe MergeRequestDiffFile do
|
|||
|
||||
describe '#utf8_diff' do
|
||||
it 'does not raise error when the diff is binary' do
|
||||
subject.diff = "\x05\x00\x68\x65\x6c\x6c\x6f"
|
||||
file = build(:merge_request_diff_file)
|
||||
file.diff = "\x05\x00\x68\x65\x6c\x6c\x6f"
|
||||
|
||||
expect { subject.utf8_diff }.not_to raise_error
|
||||
expect { file.utf8_diff }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'calls #diff once' do
|
||||
allow(subject).to receive(:diff).and_return('test')
|
||||
allow(file).to receive(:diff).and_return('test')
|
||||
|
||||
expect(subject).to receive(:diff).once
|
||||
expect(file).to receive(:diff).once
|
||||
|
||||
subject.utf8_diff
|
||||
file.utf8_diff
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diff_export' do
|
||||
context 'when diff is externally stored' do
|
||||
let(:test_dir) { 'tmp/tests/external-diffs' }
|
||||
|
||||
around do |example|
|
||||
FileUtils.mkdir_p(test_dir)
|
||||
|
||||
begin
|
||||
example.run
|
||||
ensure
|
||||
FileUtils.rm_rf(test_dir)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_external_diffs_setting(enabled: true, storage_path: test_dir)
|
||||
end
|
||||
|
||||
context 'when external diff is not cached' do
|
||||
it 'caches external diffs' do
|
||||
expect(file.merge_request_diff).to receive(:cache_external_diff).and_call_original
|
||||
|
||||
expect(file.diff_export).to eq(file.utf8_diff)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when external diff is already cached' do
|
||||
it 'reads diff from cached external diff' do
|
||||
file_stub = double
|
||||
|
||||
allow(file.merge_request_diff).to receive(:cached_external_diff).and_yield(file_stub)
|
||||
expect(file_stub).to receive(:seek).with(file.external_diff_offset)
|
||||
expect(file_stub).to receive(:read).with(file.external_diff_size)
|
||||
|
||||
file.diff_export
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the diff is marked as binary' do
|
||||
let(:file) { build(:merge_request_diff_file) }
|
||||
|
||||
before do
|
||||
allow(file.merge_request_diff).to receive(:stored_externally?).and_return(true)
|
||||
allow(file.merge_request_diff).to receive(:cached_external_diff).and_return(packed)
|
||||
end
|
||||
|
||||
context 'when the diff is marked as binary' do
|
||||
before do
|
||||
file.binary = true
|
||||
end
|
||||
|
||||
it 'unpacks from base 64' do
|
||||
expect(file.diff_export).to eq(unpacked)
|
||||
end
|
||||
|
||||
context 'invalid base64' do
|
||||
let(:packed) { '---/dev/null' }
|
||||
|
||||
it 'returns the raw diff' do
|
||||
expect(file.diff_export).to eq(packed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the diff is not marked as binary' do
|
||||
it 'returns the raw diff' do
|
||||
expect(file.diff_export).to eq(packed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when content responds to #encoding' do
|
||||
it 'encodes content to utf8 encoding' do
|
||||
expect(file.diff_export.encoding).to eq(Encoding::UTF_8)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when content is blank' do
|
||||
it 'returns an empty string' do
|
||||
allow(file.merge_request_diff).to receive(:cached_external_diff).and_return(nil)
|
||||
|
||||
expect(file.diff_export).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when exception is raised' do
|
||||
it 'falls back to #utf8_diff' do
|
||||
allow(file).to receive(:binary?).and_raise(StandardError)
|
||||
expect(file).to receive(:utf8_diff)
|
||||
|
||||
file.diff_export
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when externally_stored_diffs_caching_export feature flag is disabled' do
|
||||
it 'calls #utf8_diff' do
|
||||
stub_feature_flags(externally_stored_diffs_caching_export: false)
|
||||
|
||||
expect(file).to receive(:utf8_diff)
|
||||
|
||||
file.diff_export
|
||||
end
|
||||
end
|
||||
|
||||
context 'when diff is not stored externally' do
|
||||
it 'calls #utf8_diff' do
|
||||
expect(file).to receive(:utf8_diff)
|
||||
|
||||
file.diff_export
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1120,4 +1120,101 @@ RSpec.describe MergeRequestDiff do
|
|||
expect(described_class.latest_diff_for_merge_requests(nil)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'external diff caching' do
|
||||
let(:test_dir) { 'tmp/tests/external-diffs' }
|
||||
let(:cache_dir) { File.join(Dir.tmpdir, "project-#{diff.project.id}-external-mr-#{diff.merge_request_id}-diff-#{diff.id}-cache") }
|
||||
let(:cache_filepath) { File.join(cache_dir, "diff-#{diff.id}") }
|
||||
let(:external_diff_content) { diff.opening_external_diff { |diff| diff.read } }
|
||||
|
||||
around do |example|
|
||||
FileUtils.mkdir_p(test_dir)
|
||||
|
||||
begin
|
||||
example.run
|
||||
ensure
|
||||
FileUtils.rm_rf(test_dir)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_external_diffs_setting(enabled: true, storage_path: test_dir)
|
||||
end
|
||||
|
||||
subject(:diff) { diff_with_commits }
|
||||
|
||||
describe '#cached_external_diff' do
|
||||
context 'when diff is externally stored' do
|
||||
context 'when diff is already cached' do
|
||||
it 'yields cached file' do
|
||||
Dir.mkdir(cache_dir)
|
||||
File.open(cache_filepath, 'wb') { |f| f.write(external_diff_content) }
|
||||
|
||||
expect(diff).not_to receive(:cache_external_diff)
|
||||
|
||||
expect { |b| diff.cached_external_diff(&b) }.to yield_with_args(File)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when diff is not cached' do
|
||||
it 'caches external diff in tmp storage' do
|
||||
expect(diff).to receive(:cache_external_diff).and_call_original
|
||||
expect(File.exist?(cache_filepath)).to eq(false)
|
||||
expect { |b| diff.cached_external_diff(&b) }.to yield_with_args(File)
|
||||
expect(File.exist?(cache_filepath)).to eq(true)
|
||||
expect(File.read(cache_filepath)).to eq(external_diff_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when diff is not externally stored' do
|
||||
it 'yields nil' do
|
||||
stub_external_diffs_setting(enabled: false)
|
||||
|
||||
expect { |b| diff.cached_external_diff(&b) }.to yield_with_args(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove_cached_external_diff' do
|
||||
before do
|
||||
diff.cached_external_diff { |diff| diff }
|
||||
end
|
||||
|
||||
it 'removes external diff cache diff' do
|
||||
expect(Dir.exist?(cache_dir)).to eq(true)
|
||||
|
||||
diff.remove_cached_external_diff
|
||||
|
||||
expect(Dir.exist?(cache_dir)).to eq(false)
|
||||
end
|
||||
|
||||
context 'when path is traversed' do
|
||||
it 'raises' do
|
||||
allow(diff).to receive(:external_diff_cache_dir).and_return(File.join(cache_dir, '..'))
|
||||
|
||||
expect { diff.remove_cached_external_diff }.to raise_error(Gitlab::Utils::PathTraversalAttackError, 'Invalid path')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when path is not allowed' do
|
||||
it 'raises' do
|
||||
allow(diff).to receive(:external_diff_cache_dir).and_return('/')
|
||||
|
||||
expect { diff.remove_cached_external_diff }.to raise_error(StandardError, 'path / is not allowed')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dir does not exist' do
|
||||
it 'returns' do
|
||||
FileUtils.rm_rf(cache_dir)
|
||||
|
||||
expect(Dir.exist?(cache_dir)).to eq(false)
|
||||
expect(FileUtils).not_to receive(:rm_rf).with(cache_dir)
|
||||
|
||||
diff.remove_cached_external_diff
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -658,7 +658,7 @@ RSpec.describe MergeRequest, factory_default: :keep do
|
|||
end
|
||||
|
||||
before do
|
||||
project.add_user(user, :developer)
|
||||
project.add_member(user, :developer)
|
||||
end
|
||||
|
||||
describe '.total_time_to_merge' do
|
||||
|
|
|
@ -821,7 +821,7 @@ RSpec.describe Project, factory_default: :keep do
|
|||
end
|
||||
|
||||
describe 'delegation' do
|
||||
[:add_guest, :add_reporter, :add_developer, :add_maintainer, :add_user, :add_users].each do |method|
|
||||
[:add_guest, :add_reporter, :add_developer, :add_maintainer, :add_member, :add_members].each do |method|
|
||||
it { is_expected.to delegate_method(method).to(:team) }
|
||||
end
|
||||
|
||||
|
@ -1862,7 +1862,7 @@ RSpec.describe Project, factory_default: :keep do
|
|||
|
||||
describe 'when a user has access to a project' do
|
||||
before do
|
||||
project.add_user(user, Gitlab::Access::MAINTAINER)
|
||||
project.add_member(user, Gitlab::Access::MAINTAINER)
|
||||
end
|
||||
|
||||
it { is_expected.to eq([project]) }
|
||||
|
|
|
@ -251,13 +251,13 @@ RSpec.describe ProjectTeam do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#add_users' do
|
||||
describe '#add_members' do
|
||||
let(:user1) { create(:user) }
|
||||
let(:user2) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'add the given users to the team' do
|
||||
project.team.add_users([user1, user2], :reporter)
|
||||
project.team.add_members([user1, user2], :reporter)
|
||||
|
||||
expect(project.team.reporter?(user1)).to be(true)
|
||||
expect(project.team.reporter?(user2)).to be(true)
|
||||
|
@ -265,7 +265,7 @@ RSpec.describe ProjectTeam do
|
|||
|
||||
context 'when `tasks_to_be_done` and `tasks_project_id` are passed' do
|
||||
before do
|
||||
project.team.add_users([user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: project.id)
|
||||
project.team.add_members([user1], :developer, tasks_to_be_done: %w(ci code), tasks_project_id: project.id)
|
||||
end
|
||||
|
||||
it 'creates a member_task with the correct attributes', :aggregate_failures do
|
||||
|
@ -277,12 +277,12 @@ RSpec.describe ProjectTeam do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#add_user' do
|
||||
describe '#add_member' do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'add the given user to the team' do
|
||||
project.team.add_user(user, :reporter)
|
||||
project.team.add_member(user, :reporter)
|
||||
|
||||
expect(project.team.reporter?(user)).to be(true)
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue