diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md index 3e09b0ef7022820420ed7b63914d9544c0e7d4bc..f1224820fe4adac2139b120003a1dc44c1f24bf0 100644 --- a/CHANGELOG-EE.md +++ b/CHANGELOG-EE.md @@ -106,6 +106,10 @@ Please view this file on the master branch, on stable branches it's out of date. - Remove "creations" in gitlab_subscription_histories on gitlab.com. !22278 +## 12.6.7 + +- No changes. + ## 12.6.6 - No changes. diff --git a/Gemfile b/Gemfile index c203a98567c76302ed0c060de139b090c1204955..031389dcc908671ad80fa301d49f44f53e45e853 100644 --- a/Gemfile +++ b/Gemfile @@ -455,7 +455,7 @@ group :ed25519 do end # Gitaly GRPC protocol definitions -gem 'gitaly', '~> 1.85.0' +gem 'gitaly', '~> 1.86.0' gem 'grpc', '~> 1.24.0' diff --git a/Gemfile.lock b/Gemfile.lock index 276f9da13c7808208ebae714678df8e55422f0a4..817648c606f5e3698a9d4369fca34072bcb7fc17 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -375,7 +375,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) git (1.5.0) - gitaly (1.85.0) + gitaly (1.86.0) grpc (~> 1.0) github-markup (1.7.0) gitlab-chronic (0.10.5) @@ -1230,7 +1230,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly (~> 1.85.0) + gitaly (~> 1.86.0) github-markup (~> 1.7.0) gitlab-chronic (~> 0.10.5) gitlab-labkit (= 0.9.1) diff --git a/app/assets/javascripts/ide/components/error_message.vue b/app/assets/javascripts/ide/components/error_message.vue index 500f67378391135dcd96552452fc602748d38bc4..d36adbd798e7205ef232f7a8b0a650f5a1e1e083 100644 --- a/app/assets/javascripts/ide/components/error_message.vue +++ b/app/assets/javascripts/ide/components/error_message.vue @@ -1,9 +1,10 @@ - - - - - {{ message.actionText }} - - - - + + + + diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 4dc6e51d2fc8209c4353008e6fc665765f0dabf4..6a836adba014476ac0f939814e1f637762571d32 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -1,5 +1,4 @@ - - - - - - - - - {{ __('Cancel') }} - - {{ __('Apply') }} - + + + + + + + + + {{ __('Cancel') }} + + {{ __('Apply') }} + + - - - - {{ __('Quick range') }} - + + + {{ __('Quick range') }} + - - - {{ option.label }} - - - - + + + {{ option.label }} + + + + + diff --git a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue index 69eb791d1951f3acbe33d5fffb46424abb43523b..4ea3d162da2ea69c797eca48c90385624a7c1e5f 100644 --- a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue +++ b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue @@ -1,5 +1,5 @@ diff --git a/app/models/project.rb b/app/models/project.rb index bc652a199861051aa913cfdfe7d9feee178e4408..a215b6c881c18752c812d13b4c19b8b10c724d6f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -901,7 +901,9 @@ class Project < ApplicationRecord if Gitlab::UrlSanitizer.valid?(value) import_url = Gitlab::UrlSanitizer.new(value) super(import_url.sanitized_url) - create_or_update_import_data(credentials: import_url.credentials) + + credentials = import_url.credentials.to_h.transform_values { |value| CGI.unescape(value.to_s) } + create_or_update_import_data(credentials: credentials) else super(value) end diff --git a/app/models/repository.rb b/app/models/repository.rb index c439d0700f1684e5fb570fc116865868c55d7060..37aceeae5f840012dd69f9a172ed83146d03816c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -134,15 +134,6 @@ class Repository end end - # the opts are: - # - :path - # - :limit - # - :offset - # - :skip_merges - # - :after - # - :before - # - :all - # - :first_parent def commits(ref = nil, opts = {}) options = { repo: raw_repository, @@ -155,7 +146,8 @@ class Repository after: opts[:after], before: opts[:before], all: !!opts[:all], - first_parent: !!opts[:first_parent] + first_parent: !!opts[:first_parent], + order: opts[:order] } commits = Gitlab::Git::Commit.where(options) diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb index ae67b4f525691c36e98bf2b4b18f1901807a0acb..0e7a4821bdfa91758401dec85d68fa60df34d34d 100644 --- a/app/services/users/refresh_authorized_projects_service.rb +++ b/app/services/users/refresh_authorized_projects_service.rb @@ -19,8 +19,10 @@ module Users LEASE_TIMEOUT = 1.minute.to_i # user - The User for which to refresh the authorized projects. - def initialize(user) + def initialize(user, incorrect_auth_found_callback: nil, missing_auth_found_callback: nil) @user = user + @incorrect_auth_found_callback = incorrect_auth_found_callback + @missing_auth_found_callback = missing_auth_found_callback # We need an up to date User object that has access to all relations that # may have been created earlier. The only way to ensure this is to reload @@ -55,6 +57,10 @@ module Users # rows not in the new list or with a different access level should be # removed. if !fresh[project_id] || fresh[project_id] != row.access_level + if incorrect_auth_found_callback + incorrect_auth_found_callback.call(project_id, row.access_level) + end + array << row.project_id end end @@ -63,6 +69,10 @@ module Users # rows not in the old list or with a different access level should be # added. if !current[project_id] || current[project_id].access_level != level + if missing_auth_found_callback + missing_auth_found_callback.call(project_id, level) + end + array << [user.id, project_id, level] end end @@ -104,5 +114,9 @@ module Users def fresh_authorizations Gitlab::ProjectAuthorizations.new(user).calculate end + + private + + attr_reader :incorrect_auth_found_callback, :missing_auth_found_callback end end diff --git a/changelogs/unreleased/198460-date-time-picker-custom-dates-make-the-date-field-too-long-truncat.yml b/changelogs/unreleased/198460-date-time-picker-custom-dates-make-the-date-field-too-long-truncat.yml new file mode 100644 index 0000000000000000000000000000000000000000..93d1f7537fd6f4f61671828df1d9a7ae1a3865e8 --- /dev/null +++ b/changelogs/unreleased/198460-date-time-picker-custom-dates-make-the-date-field-too-long-truncat.yml @@ -0,0 +1,5 @@ +--- +title: Add tooltip when dates in date picker are too long +merge_request: 24664 +author: +type: added diff --git a/changelogs/unreleased/35671-api-repository-commits-with-order.yml b/changelogs/unreleased/35671-api-repository-commits-with-order.yml new file mode 100644 index 0000000000000000000000000000000000000000..3ca59a2265f2b98c9d92c547dabbe11c5f826533 --- /dev/null +++ b/changelogs/unreleased/35671-api-repository-commits-with-order.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Ability to list commits in order (--topo-order)' +merge_request: 24702 +author: +type: added diff --git a/changelogs/unreleased/36854-webide-use-gl-alert.yml b/changelogs/unreleased/36854-webide-use-gl-alert.yml new file mode 100644 index 0000000000000000000000000000000000000000..4f6cc63de3fa1c37ddeb00665187c707953b7cf4 --- /dev/null +++ b/changelogs/unreleased/36854-webide-use-gl-alert.yml @@ -0,0 +1,5 @@ +--- +title: Fix Web IDE alert message look and feel +merge_request: 23300 +author: Sean Nichols +type: fixed diff --git a/changelogs/unreleased/avoid-double-credential-encoding-on-project-importing.yml b/changelogs/unreleased/avoid-double-credential-encoding-on-project-importing.yml new file mode 100644 index 0000000000000000000000000000000000000000..5118be38704dae4361992af0d8e0712deb3d6f24 --- /dev/null +++ b/changelogs/unreleased/avoid-double-credential-encoding-on-project-importing.yml @@ -0,0 +1,5 @@ +--- +title: Avoid double encoding of credential while importing a Project by URL +merge_request: 24514 +author: +type: fixed diff --git a/changelogs/unreleased/leipert-fix-user-label-bug.yml b/changelogs/unreleased/leipert-fix-user-label-bug.yml deleted file mode 100644 index 67e1a4011af1f91b8b74fcbf630aca88ff00d6c5..0000000000000000000000000000000000000000 --- a/changelogs/unreleased/leipert-fix-user-label-bug.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix autocomplete limitation bug -merge_request: 25127 -author: -type: fixed diff --git a/changelogs/unreleased/refactoring-entities-file-15.yml b/changelogs/unreleased/refactoring-entities-file-15.yml new file mode 100644 index 0000000000000000000000000000000000000000..89c2da2cf866052fcb3e4e42ce248ea7bb064edc --- /dev/null +++ b/changelogs/unreleased/refactoring-entities-file-15.yml @@ -0,0 +1,5 @@ +--- +title: Separate access entities into own class files +merge_request: 24845 +author: Rajendra Kadam +type: added diff --git a/changelogs/unreleased/refactoring-entities-file-23.yml b/changelogs/unreleased/refactoring-entities-file-23.yml new file mode 100644 index 0000000000000000000000000000000000000000..c771b80811ffaa36d32b19fc92c3a5c849f0fe1e --- /dev/null +++ b/changelogs/unreleased/refactoring-entities-file-23.yml @@ -0,0 +1,5 @@ +--- +title: Separate environment entities into own class files +merge_request: 24951 +author: Rajendra Kadam +type: added diff --git a/changelogs/unreleased/refactoring-entities-file-26.yml b/changelogs/unreleased/refactoring-entities-file-26.yml new file mode 100644 index 0000000000000000000000000000000000000000..050ab6678a5678bc903836e5b0402b9212883416 --- /dev/null +++ b/changelogs/unreleased/refactoring-entities-file-26.yml @@ -0,0 +1,5 @@ +--- +title: Separate JobRequest entities into own class files +merge_request: 24977 +author: Rajendra Kadam +type: added diff --git a/changelogs/unreleased/refactoring-entities-file-28.yml b/changelogs/unreleased/refactoring-entities-file-28.yml new file mode 100644 index 0000000000000000000000000000000000000000..604dfa7627d1f4390ad1f6fda412b9aea0a4d75e --- /dev/null +++ b/changelogs/unreleased/refactoring-entities-file-28.yml @@ -0,0 +1,5 @@ +--- +title: Separate page domain entities into own class files +merge_request: 24987 +author: Rajendra Kadam +type: added diff --git a/changelogs/unreleased/refactoring-entities-file-30.yml b/changelogs/unreleased/refactoring-entities-file-30.yml new file mode 100644 index 0000000000000000000000000000000000000000..c60c63b9843d0cdd3d6b9ad218788798323a2570 --- /dev/null +++ b/changelogs/unreleased/refactoring-entities-file-30.yml @@ -0,0 +1,5 @@ +--- +title: Separate badge entities into own class files +merge_request: 25116 +author: Rajendra Kadam +type: added diff --git a/changelogs/unreleased/refactoring-entities-file-32.yml b/changelogs/unreleased/refactoring-entities-file-32.yml new file mode 100644 index 0000000000000000000000000000000000000000..e5116a80263778285f345e2c4e77bf7e73d64aac --- /dev/null +++ b/changelogs/unreleased/refactoring-entities-file-32.yml @@ -0,0 +1,5 @@ +--- +title: Separate cluster entities into own class files +merge_request: 25121 +author: Rajendra Kadam +type: added diff --git a/changelogs/unreleased/resolve_gitlab_issue_196651.yml b/changelogs/unreleased/resolve_gitlab_issue_196651.yml new file mode 100644 index 0000000000000000000000000000000000000000..169d8e5038357da9dd109390f8edcdb506a9de17 --- /dev/null +++ b/changelogs/unreleased/resolve_gitlab_issue_196651.yml @@ -0,0 +1,5 @@ +--- +title: Replace underscore with lodash for ./app/assets/javascripts/serverless +merge_request: 25011 +author: Tobias Spagert +type: other diff --git a/db/post_migrate/20200204113223_schedule_recalculate_project_authorizations.rb b/db/post_migrate/20200204113223_schedule_recalculate_project_authorizations.rb new file mode 100644 index 0000000000000000000000000000000000000000..83b58300115936b2ad6d5ec268585709ef2f205b --- /dev/null +++ b/db/post_migrate/20200204113223_schedule_recalculate_project_authorizations.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class ScheduleRecalculateProjectAuthorizations < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + MIGRATION = 'RecalculateProjectAuthorizations' + BATCH_SIZE = 2_500 + DELAY_INTERVAL = 2.minutes.to_i + + disable_ddl_transaction! + + class Namespace < ActiveRecord::Base + include ::EachBatch + + self.table_name = 'namespaces' + end + + class ProjectAuthorization < ActiveRecord::Base + include ::EachBatch + + self.table_name = 'project_authorizations' + end + + def up + say "Scheduling #{MIGRATION} jobs" + + max_group_id = Namespace.where(type: 'Group').maximum(:id) + project_authorizations = ProjectAuthorization.where('project_id <= ?', max_group_id) + .select(:user_id) + .distinct + + project_authorizations.each_batch(of: BATCH_SIZE, column: :user_id) do |authorizations, index| + delay = index * DELAY_INTERVAL + user_ids = authorizations.map(&:user_id) + BackgroundMigrationWorker.perform_in(delay, MIGRATION, [user_ids]) + end + end + + def down + end +end diff --git a/doc/api/commits.md b/doc/api/commits.md index fb090f51a2ea75a4a89d07535110db0c83086f20..eb3fb7b2195e8e30f23e7f9e443885fcee6a1cdb 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -18,6 +18,7 @@ GET /projects/:id/repository/commits | `all` | boolean | no | Retrieve every commit from the repository | | `with_stats` | boolean | no | Stats about each commit will be added to the response | | `first_parent` | boolean | no | Follow only the first parent commit upon seeing a merge commit | +| `order` | string | no | List commits in order. Possible value: [`topo`](https://git-scm.com/docs/git-log#Documentation/git-log.txt---topo-order). | ```shell curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/projects/5/repository/commits" diff --git a/doc/user/project/operations/error_tracking.md b/doc/user/project/operations/error_tracking.md index e87b5d034389bfb62b3b071c7a02f7193517c960..e7565835be77fe167fd9a4a8bcaebdf8ba59aa47 100644 --- a/doc/user/project/operations/error_tracking.md +++ b/doc/user/project/operations/error_tracking.md @@ -84,5 +84,6 @@ Ignoring an error will prevent it from appearing in the [Error Tracking List](#e From within the [Error Details](#error-details) page you can resolve a Sentry error by clicking the **Resolve** button near the top of the page. -Marking an error as resolved indicates that the error has stopped firing events. If another event -occurs, the error reverts to unresolved. +Marking an error as resolved indicates that the error has stopped firing events. If a GitLab issue is linked to the error, then the issue will be closed. + +If another event occurs, the error reverts to unresolved. diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 9dcf9b015aa5edac0341856f9dc321b9e8a233b5..4e04a99e97c7e96b4214dca5d349fb22e962770a 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -38,6 +38,7 @@ module API optional :all, type: Boolean, desc: 'Every commit will be returned' optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response' optional :first_parent, type: Boolean, desc: 'Only include the first parent of merges' + optional :order, type: String, desc: 'List commits in order', values: %w[topo] use :pagination end get ':id/repository/commits' do @@ -49,6 +50,7 @@ module API all = params[:all] with_stats = params[:with_stats] first_parent = params[:first_parent] + order = params[:order] commits = user_project.repository.commits(ref, path: path, @@ -57,7 +59,8 @@ module API before: before, after: after, all: all, - first_parent: first_parent) + first_parent: first_parent, + order: order) commit_count = if all || path || before || after || first_parent diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 5128ffa6a1fbaacc6e817f626062becee6d2a7de..b9805973c543cc98c317678996e808fc301846b0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -129,40 +129,6 @@ module API end end - class Namespace < NamespaceBasic - expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _| - namespace.users_with_descendants.count - end - - def expose_members_count_with_descendants?(namespace, opts) - namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace) - end - end - - class MemberAccess < Grape::Entity - expose :access_level - expose :notification_level do |member, options| - if member.notification_setting - ::NotificationSetting.levels[member.notification_setting.level] - end - end - end - - class ProjectAccess < MemberAccess - end - - class GroupAccess < MemberAccess - end - - class NotificationSetting < Grape::Entity - expose :level - expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do - ::NotificationSetting.email_events.each do |event| - expose event - end - end - end - class Trigger < Grape::Entity include ::API::Helpers::Presentable @@ -204,39 +170,6 @@ module API expose :variables, using: Entities::Variable end - class EnvironmentBasic < Grape::Entity - expose :id, :name, :slug, :external_url - end - - class Deployment < Grape::Entity - expose :id, :iid, :ref, :sha, :created_at, :updated_at - expose :user, using: Entities::UserBasic - expose :environment, using: Entities::EnvironmentBasic - expose :deployable, using: Entities::Job - expose :status - end - - class Environment < EnvironmentBasic - expose :project, using: Entities::BasicProjectDetails - expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true } - expose :state - end - - class LicenseBasic < Grape::Entity - expose :key, :name, :nickname - expose :url, as: :html_url - expose(:source_url) { |license| license.meta['source'] } - end - - class License < LicenseBasic - expose :popular?, as: :popular - expose(:description) { |license| license.meta['description'] } - expose(:conditions) { |license| license.meta['conditions'] } - expose(:permissions) { |license| license.meta['permissions'] } - expose(:limitations) { |license| license.meta['limitations'] } - expose :content - end - class ImpersonationToken < PersonalAccessToken expose :impersonation end @@ -267,93 +200,6 @@ module API end end - module JobRequest - class JobInfo < Grape::Entity - expose :name, :stage - expose :project_id, :project_name - end - - class GitInfo < Grape::Entity - expose :repo_url, :ref, :sha, :before_sha - expose :ref_type - expose :refspecs - expose :git_depth, as: :depth - end - - class RunnerInfo < Grape::Entity - expose :metadata_timeout, as: :timeout - expose :runner_session_url - end - - class Step < Grape::Entity - expose :name, :script, :timeout, :when, :allow_failure - end - - class Port < Grape::Entity - expose :number, :protocol, :name - end - - class Image < Grape::Entity - expose :name, :entrypoint - expose :ports, using: JobRequest::Port - end - - class Service < Image - expose :alias, :command - end - - class Artifacts < Grape::Entity - expose :name - expose :untracked - expose :paths - expose :when - expose :expire_in - expose :artifact_type - expose :artifact_format - end - - class Cache < Grape::Entity - expose :key, :untracked, :paths, :policy - end - - class Credentials < Grape::Entity - expose :type, :url, :username, :password - end - - class Dependency < Grape::Entity - expose :id, :name, :token - expose :artifacts_file, using: JobArtifactFile, if: ->(job, _) { job.artifacts? } - end - - class Response < Grape::Entity - expose :id - expose :token - expose :allow_git_fetch - - expose :job_info, using: JobInfo do |model| - model - end - - expose :git_info, using: GitInfo do |model| - model - end - - expose :runner_info, using: RunnerInfo do |model| - model - end - - expose :variables - expose :steps, using: Step - expose :image, using: Image - expose :services, using: Service - expose :artifacts, using: Artifacts - expose :cache, using: Cache - expose :credentials, using: Credentials - expose :all_dependencies, as: :dependencies, using: Dependency - expose :features - end - end - class UserAgentDetail < Grape::Entity expose :user_agent expose :ip_address @@ -370,45 +216,6 @@ module API expose :expiration end - class PagesDomainCertificate < Grape::Entity - expose :subject - expose :expired?, as: :expired - expose :certificate - expose :certificate_text - end - - class PagesDomainBasic < Grape::Entity - expose :domain - expose :url - expose :project_id - expose :verified?, as: :verified - expose :verification_code, as: :verification_code - expose :enabled_until - expose :auto_ssl_enabled - - expose :certificate, - as: :certificate_expiration, - if: ->(pages_domain, _) { pages_domain.certificate? }, - using: PagesDomainCertificateExpiration do |pages_domain| - pages_domain - end - end - - class PagesDomain < Grape::Entity - expose :domain - expose :url - expose :verified?, as: :verified - expose :verification_code, as: :verification_code - expose :enabled_until - expose :auto_ssl_enabled - - expose :certificate, - if: ->(pages_domain, _) { pages_domain.certificate? }, - using: PagesDomainCertificate do |pages_domain| - pages_domain - end - end - class Application < Grape::Entity expose :id expose :uid, as: :application_id @@ -437,49 +244,6 @@ module API expose :project_id end - class BasicBadgeDetails < Grape::Entity - expose :name - expose :link_url - expose :image_url - expose :rendered_link_url do |badge, options| - badge.rendered_link_url(options.fetch(:project, nil)) - end - expose :rendered_image_url do |badge, options| - badge.rendered_image_url(options.fetch(:project, nil)) - end - end - - class Badge < BasicBadgeDetails - expose :id - expose :kind do |badge| - badge.type == 'ProjectBadge' ? 'project' : 'group' - end - end - - class ResourceLabelEvent < Grape::Entity - expose :id - expose :user, using: Entities::UserBasic - expose :created_at - expose :resource_type do |event, options| - event.issuable.class.name - end - expose :resource_id do |event, options| - event.issuable.id - end - expose :label, using: Entities::LabelBasic - expose :action - end - - class Suggestion < Grape::Entity - expose :id - expose :from_line - expose :to_line - expose :appliable?, as: :appliable - expose :applied - expose :from_content - expose :to_content - end - module Platform class Kubernetes < Grape::Entity expose :api_url @@ -501,23 +265,6 @@ module API end end - class Cluster < Grape::Entity - expose :id, :name, :created_at, :domain - expose :provider_type, :platform_type, :environment_scope, :cluster_type - expose :user, using: Entities::UserBasic - expose :platform_kubernetes, using: Entities::Platform::Kubernetes - expose :provider_gcp, using: Entities::Provider::Gcp - expose :management_project, using: Entities::ProjectIdentity - end - - class ClusterProject < Cluster - expose :project, using: Entities::BasicProjectDetails - end - - class ClusterGroup < Cluster - expose :group, using: Entities::BasicGroupDetails - end - module InternalPostReceive class Message < Grape::Entity expose :message diff --git a/lib/api/entities/badge.rb b/lib/api/entities/badge.rb new file mode 100644 index 0000000000000000000000000000000000000000..1e3e2ec469aac111e759ffff4e6bcb454daffd33 --- /dev/null +++ b/lib/api/entities/badge.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + class Badge < Entities::BasicBadgeDetails + expose :id + expose :kind do |badge| + badge.type == 'ProjectBadge' ? 'project' : 'group' + end + end + end +end diff --git a/lib/api/entities/basic_badge_details.rb b/lib/api/entities/basic_badge_details.rb new file mode 100644 index 0000000000000000000000000000000000000000..273dc57fe67890c1cd61ea0f18b78f68d1aa778b --- /dev/null +++ b/lib/api/entities/basic_badge_details.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module API + module Entities + class BasicBadgeDetails < Grape::Entity + expose :name + expose :link_url + expose :image_url + expose :rendered_link_url do |badge, options| + badge.rendered_link_url(options.fetch(:project, nil)) + end + expose :rendered_image_url do |badge, options| + badge.rendered_image_url(options.fetch(:project, nil)) + end + end + end +end diff --git a/lib/api/entities/cluster.rb b/lib/api/entities/cluster.rb new file mode 100644 index 0000000000000000000000000000000000000000..4cb54e988ce77ef62e21587b1b27bea0ffa25933 --- /dev/null +++ b/lib/api/entities/cluster.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class Cluster < Grape::Entity + expose :id, :name, :created_at, :domain + expose :provider_type, :platform_type, :environment_scope, :cluster_type + expose :user, using: Entities::UserBasic + expose :platform_kubernetes, using: Entities::Platform::Kubernetes + expose :provider_gcp, using: Entities::Provider::Gcp + expose :management_project, using: Entities::ProjectIdentity + end + end +end diff --git a/lib/api/entities/cluster_group.rb b/lib/api/entities/cluster_group.rb new file mode 100644 index 0000000000000000000000000000000000000000..8f71438cf3da471317c6cba33534f1316c2f3c54 --- /dev/null +++ b/lib/api/entities/cluster_group.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class ClusterGroup < Entities::Cluster + expose :group, using: Entities::BasicGroupDetails + end + end +end diff --git a/lib/api/entities/cluster_project.rb b/lib/api/entities/cluster_project.rb new file mode 100644 index 0000000000000000000000000000000000000000..2fd3e35e2a29892df0ce66ccb6108a6981e9d212 --- /dev/null +++ b/lib/api/entities/cluster_project.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class ClusterProject < Entities::Cluster + expose :project, using: Entities::BasicProjectDetails + end + end +end diff --git a/lib/api/entities/deployment.rb b/lib/api/entities/deployment.rb new file mode 100644 index 0000000000000000000000000000000000000000..3a97d3e3c091c77bafd4194baf7b7f8ea05b161d --- /dev/null +++ b/lib/api/entities/deployment.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module API + module Entities + class Deployment < Grape::Entity + expose :id, :iid, :ref, :sha, :created_at, :updated_at + expose :user, using: Entities::UserBasic + expose :environment, using: Entities::EnvironmentBasic + expose :deployable, using: Entities::Job + expose :status + end + end +end diff --git a/lib/api/entities/environment.rb b/lib/api/entities/environment.rb new file mode 100644 index 0000000000000000000000000000000000000000..cb39ce1b13af4b670b5f3c1e6cb29fc3b85fdbbf --- /dev/null +++ b/lib/api/entities/environment.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + class Environment < Entities::EnvironmentBasic + expose :project, using: Entities::BasicProjectDetails + expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true } + expose :state + end + end +end diff --git a/lib/api/entities/environment_basic.rb b/lib/api/entities/environment_basic.rb new file mode 100644 index 0000000000000000000000000000000000000000..061d4739874bd3377d7fe251be14a216ccc4428a --- /dev/null +++ b/lib/api/entities/environment_basic.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class EnvironmentBasic < Grape::Entity + expose :id, :name, :slug, :external_url + end + end +end diff --git a/lib/api/entities/group_access.rb b/lib/api/entities/group_access.rb new file mode 100644 index 0000000000000000000000000000000000000000..5e53e9645c2a706cb925aaa3717278e2390e889d --- /dev/null +++ b/lib/api/entities/group_access.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module API + module Entities + class GroupAccess < MemberAccess + end + end +end diff --git a/lib/api/entities/job_request/artifacts.rb b/lib/api/entities/job_request/artifacts.rb new file mode 100644 index 0000000000000000000000000000000000000000..c6871fdd8750c080acb5e518d59eaaad2526f909 --- /dev/null +++ b/lib/api/entities/job_request/artifacts.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class Artifacts < Grape::Entity + expose :name + expose :untracked + expose :paths + expose :when + expose :expire_in + expose :artifact_type + expose :artifact_format + end + end + end +end diff --git a/lib/api/entities/job_request/cache.rb b/lib/api/entities/job_request/cache.rb new file mode 100644 index 0000000000000000000000000000000000000000..a75affbaf8410bb05ebc3833e0029b47380e9f7d --- /dev/null +++ b/lib/api/entities/job_request/cache.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class Cache < Grape::Entity + expose :key, :untracked, :paths, :policy + end + end + end +end diff --git a/lib/api/entities/job_request/credentials.rb b/lib/api/entities/job_request/credentials.rb new file mode 100644 index 0000000000000000000000000000000000000000..cdac5566cbd1ebfd5c6f851b88de27daadf68866 --- /dev/null +++ b/lib/api/entities/job_request/credentials.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class Credentials < Grape::Entity + expose :type, :url, :username, :password + end + end + end +end diff --git a/lib/api/entities/job_request/dependency.rb b/lib/api/entities/job_request/dependency.rb new file mode 100644 index 0000000000000000000000000000000000000000..64d779f6575881536b44f46fb10f8d108641b01b --- /dev/null +++ b/lib/api/entities/job_request/dependency.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class Dependency < Grape::Entity + expose :id, :name, :token + expose :artifacts_file, using: Entities::JobArtifactFile, if: ->(job, _) { job.artifacts? } + end + end + end +end diff --git a/lib/api/entities/job_request/git_info.rb b/lib/api/entities/job_request/git_info.rb new file mode 100644 index 0000000000000000000000000000000000000000..e07099263b5d7ec70371dd06a0b1d3684bfa8fc6 --- /dev/null +++ b/lib/api/entities/job_request/git_info.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class GitInfo < Grape::Entity + expose :repo_url, :ref, :sha, :before_sha + expose :ref_type + expose :refspecs + expose :git_depth, as: :depth + end + end + end +end diff --git a/lib/api/entities/job_request/image.rb b/lib/api/entities/job_request/image.rb new file mode 100644 index 0000000000000000000000000000000000000000..47f4542d2d567c0917dc7208e4ba2d20849d9720 --- /dev/null +++ b/lib/api/entities/job_request/image.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class Image < Grape::Entity + expose :name, :entrypoint + expose :ports, using: Entities::JobRequest::Port + end + end + end +end diff --git a/lib/api/entities/job_request/job_info.rb b/lib/api/entities/job_request/job_info.rb new file mode 100644 index 0000000000000000000000000000000000000000..09c13aa847168b0bef69c471dbe3e9f46bc0a3cb --- /dev/null +++ b/lib/api/entities/job_request/job_info.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class JobInfo < Grape::Entity + expose :name, :stage + expose :project_id, :project_name + end + end + end +end diff --git a/lib/api/entities/job_request/port.rb b/lib/api/entities/job_request/port.rb new file mode 100644 index 0000000000000000000000000000000000000000..ee427da86570fc0c13750a349fa8757724aea599 --- /dev/null +++ b/lib/api/entities/job_request/port.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class Port < Grape::Entity + expose :number, :protocol, :name + end + end + end +end diff --git a/lib/api/entities/job_request/response.rb b/lib/api/entities/job_request/response.rb new file mode 100644 index 0000000000000000000000000000000000000000..fdacd3af2da3b9f980ef5d4d563a51229a32f652 --- /dev/null +++ b/lib/api/entities/job_request/response.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class Response < Grape::Entity + expose :id + expose :token + expose :allow_git_fetch + + expose :job_info, using: Entities::JobRequest::JobInfo do |model| + model + end + + expose :git_info, using: Entities::JobRequest::GitInfo do |model| + model + end + + expose :runner_info, using: Entities::JobRequest::RunnerInfo do |model| + model + end + + expose :variables + expose :steps, using: Entities::JobRequest::Step + expose :image, using: Entities::JobRequest::Image + expose :services, using: Entities::JobRequest::Service + expose :artifacts, using: Entities::JobRequest::Artifacts + expose :cache, using: Entities::JobRequest::Cache + expose :credentials, using: Entities::JobRequest::Credentials + expose :all_dependencies, as: :dependencies, using: Entities::JobRequest::Dependency + expose :features + end + end + end +end diff --git a/lib/api/entities/job_request/runner_info.rb b/lib/api/entities/job_request/runner_info.rb new file mode 100644 index 0000000000000000000000000000000000000000..e6d2e8d9e85ea0d431be2d96aec502a353eab8f7 --- /dev/null +++ b/lib/api/entities/job_request/runner_info.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class RunnerInfo < Grape::Entity + expose :metadata_timeout, as: :timeout + expose :runner_session_url + end + end + end +end diff --git a/lib/api/entities/job_request/service.rb b/lib/api/entities/job_request/service.rb new file mode 100644 index 0000000000000000000000000000000000000000..9ad5abf4e9ec4cac4d5d1e1de17f0b914d0598e6 --- /dev/null +++ b/lib/api/entities/job_request/service.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class Service < Entities::JobRequest::Image + expose :alias, :command + end + end + end +end diff --git a/lib/api/entities/job_request/step.rb b/lib/api/entities/job_request/step.rb new file mode 100644 index 0000000000000000000000000000000000000000..498dd017fb4c42f44f48dcd8e0d884d0af109981 --- /dev/null +++ b/lib/api/entities/job_request/step.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module JobRequest + class Step < Grape::Entity + expose :name, :script, :timeout, :when, :allow_failure + end + end + end +end diff --git a/lib/api/entities/license.rb b/lib/api/entities/license.rb new file mode 100644 index 0000000000000000000000000000000000000000..d7a414344c10bc182875758261fa9ab8b2a0a4df --- /dev/null +++ b/lib/api/entities/license.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class License < Entities::LicenseBasic + expose :popular?, as: :popular + expose(:description) { |license| license.meta['description'] } + expose(:conditions) { |license| license.meta['conditions'] } + expose(:permissions) { |license| license.meta['permissions'] } + expose(:limitations) { |license| license.meta['limitations'] } + expose :content + end + end +end diff --git a/lib/api/entities/license_basic.rb b/lib/api/entities/license_basic.rb new file mode 100644 index 0000000000000000000000000000000000000000..08af68785a96fe6e50fbf61633f27721f0db21ff --- /dev/null +++ b/lib/api/entities/license_basic.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + class LicenseBasic < Grape::Entity + expose :key, :name, :nickname + expose :url, as: :html_url + expose(:source_url) { |license| license.meta['source'] } + end + end +end diff --git a/lib/api/entities/member_access.rb b/lib/api/entities/member_access.rb new file mode 100644 index 0000000000000000000000000000000000000000..097c72bf6176e6bcd589792aecac758593159c3c --- /dev/null +++ b/lib/api/entities/member_access.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class MemberAccess < Grape::Entity + expose :access_level + expose :notification_level do |member, options| + if member.notification_setting + ::NotificationSetting.levels[member.notification_setting.level] + end + end + end + end +end diff --git a/lib/api/entities/namespace.rb b/lib/api/entities/namespace.rb new file mode 100644 index 0000000000000000000000000000000000000000..b21dd29c7b465dbae59719dd51cf2e2b12aab42f --- /dev/null +++ b/lib/api/entities/namespace.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module API + module Entities + class Namespace < Entities::NamespaceBasic + expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _| + namespace.users_with_descendants.count + end + + def expose_members_count_with_descendants?(namespace, opts) + namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace) + end + end + end +end diff --git a/lib/api/entities/notification_setting.rb b/lib/api/entities/notification_setting.rb new file mode 100644 index 0000000000000000000000000000000000000000..cdff4f2f5c5dd082cf4729167f67fbb74bafe6d5 --- /dev/null +++ b/lib/api/entities/notification_setting.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class NotificationSetting < Grape::Entity + expose :level + expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do + ::NotificationSetting.email_events.each do |event| + expose event + end + end + end + end +end diff --git a/lib/api/entities/pages_domain.rb b/lib/api/entities/pages_domain.rb new file mode 100644 index 0000000000000000000000000000000000000000..87af8c7b0a464bd25a5d0375918ef7d152cf28c3 --- /dev/null +++ b/lib/api/entities/pages_domain.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module API + module Entities + class PagesDomain < Grape::Entity + expose :domain + expose :url + expose :verified?, as: :verified + expose :verification_code, as: :verification_code + expose :enabled_until + expose :auto_ssl_enabled + + expose :certificate, + if: ->(pages_domain, _) { pages_domain.certificate? }, + using: Entities::PagesDomainCertificate do |pages_domain| + pages_domain + end + end + end +end diff --git a/lib/api/entities/pages_domain_basic.rb b/lib/api/entities/pages_domain_basic.rb new file mode 100644 index 0000000000000000000000000000000000000000..6f8901fe715f33266e7a92b02150bddf5f7dd097 --- /dev/null +++ b/lib/api/entities/pages_domain_basic.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module API + module Entities + class PagesDomainBasic < Grape::Entity + expose :domain + expose :url + expose :project_id + expose :verified?, as: :verified + expose :verification_code, as: :verification_code + expose :enabled_until + expose :auto_ssl_enabled + + expose :certificate, + as: :certificate_expiration, + if: ->(pages_domain, _) { pages_domain.certificate? }, + using: Entities::PagesDomainCertificateExpiration do |pages_domain| + pages_domain + end + end + end +end diff --git a/lib/api/entities/pages_domain_certificate.rb b/lib/api/entities/pages_domain_certificate.rb new file mode 100644 index 0000000000000000000000000000000000000000..82c4729d45466cb2a2f1550346d74049b894724a --- /dev/null +++ b/lib/api/entities/pages_domain_certificate.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + class PagesDomainCertificate < Grape::Entity + expose :subject + expose :expired?, as: :expired + expose :certificate + expose :certificate_text + end + end +end diff --git a/lib/api/entities/project_access.rb b/lib/api/entities/project_access.rb new file mode 100644 index 0000000000000000000000000000000000000000..29f85fda620697ffd700c406b66e775148909515 --- /dev/null +++ b/lib/api/entities/project_access.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module API + module Entities + class ProjectAccess < Entities::MemberAccess + end + end +end diff --git a/lib/api/entities/resource_label_event.rb b/lib/api/entities/resource_label_event.rb new file mode 100644 index 0000000000000000000000000000000000000000..890264abf935e18383f3a89b43087353b02afa48 --- /dev/null +++ b/lib/api/entities/resource_label_event.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module API + module Entities + class ResourceLabelEvent < Grape::Entity + expose :id + expose :user, using: Entities::UserBasic + expose :created_at + expose :resource_type do |event, options| + event.issuable.class.name + end + expose :resource_id do |event, options| + event.issuable.id + end + expose :label, using: Entities::LabelBasic + expose :action + end + end +end diff --git a/lib/api/entities/suggestion.rb b/lib/api/entities/suggestion.rb new file mode 100644 index 0000000000000000000000000000000000000000..59f94099d7fed7618c9028df926279bd1b994c95 --- /dev/null +++ b/lib/api/entities/suggestion.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module API + module Entities + class Suggestion < Grape::Entity + expose :id + expose :from_line + expose :to_line + expose :appliable?, as: :appliable + expose :applied + expose :from_content + expose :to_content + end + end +end diff --git a/lib/gitlab/background_migration/recalculate_project_authorizations.rb b/lib/gitlab/background_migration/recalculate_project_authorizations.rb new file mode 100644 index 0000000000000000000000000000000000000000..3d2ce9fc10c6cce27d82aa4fd1e5570d71cc2f60 --- /dev/null +++ b/lib/gitlab/background_migration/recalculate_project_authorizations.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # rubocop:disable Style/Documentation + class RecalculateProjectAuthorizations + def perform(user_ids) + user_ids.each do |user_id| + user = User.find_by(id: user_id) + + next unless user + + service = Users::RefreshAuthorizedProjectsService.new( + user, + incorrect_auth_found_callback: + ->(project_id, access_level) do + logger.info(message: 'Removing ProjectAuthorizations', + user_id: user.id, + project_id: project_id, + access_level: access_level) + end, + missing_auth_found_callback: + ->(project_id, access_level) do + logger.info(message: 'Creating ProjectAuthorizations', + user_id: user.id, + project_id: project_id, + access_level: access_level) + end + ) + + service.execute + end + end + + private + + def logger + @logger ||= Gitlab::BackgroundMigration::Logger.build + end + end + end +end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 17a7be311cadb8f0fe2bc06160211ea745e5df02..4fc5bfddf0c4e6e20c7f78111e9a408090e857cf 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -353,11 +353,7 @@ module Gitlab def fetch_blob(sha, path) return unless sha - # Load only patch_hard_limit_bytes number of bytes for the blob - # Because otherwise, it is too large to be displayed - Blob.lazy( - repository.project, sha, path, - blob_size_limit: Gitlab::Git::Diff.patch_hard_limit_bytes) + Blob.lazy(repository.project, sha, path) end def total_blob_lines(blob) diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 48da838366fa8c2992b80d8ff20f1d46557fa73e..0b999197cd85f4d69420c0963fd2e2631c83467a 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -130,8 +130,7 @@ module Gitlab # :skip is the number of commits to skip # :order is the commits order and allowed value is :none (default), :date, # :topo, or any combination of them (in an array). Commit ordering types - # are documented here: - # http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant) + # are documented here: https://git-scm.com/docs/git-log#_commit_ordering def find_all(repo, options = {}) wrapped_gitaly_errors do Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options) diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 15318bc817a93c1b8c67f4d826116da0dde0b3a5..b981b9cf1bcbb21dd52805ee135364f98a90617b 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -324,6 +324,7 @@ module Gitlab request.after = GitalyClient.timestamp(options[:after]) if options[:after] request.before = GitalyClient.timestamp(options[:before]) if options[:before] request.revision = encode_binary(options[:ref]) if options[:ref] + request.order = options[:order].upcase if options[:order].present? request.paths = encode_repeated(Array(options[:path])) if options[:path].present? diff --git a/lib/gitlab/project_authorizations.rb b/lib/gitlab/project_authorizations.rb index e2271b1492c4ca57792766b5652134cafa40933c..d65e8759ec906fab889fdb65ca0b6e31539162c8 100644 --- a/lib/gitlab/project_authorizations.rb +++ b/lib/gitlab/project_authorizations.rb @@ -68,12 +68,10 @@ module Gitlab .select([namespaces[:id], members[:access_level]]) .except(:order) - if Feature.enabled?(:share_group_with_group, default_enabled: true) - # Namespaces shared with any of the group - cte << Group.select([namespaces[:id], 'group_group_links.group_access AS access_level']) - .joins(join_group_group_links) - .joins(join_members_on_group_group_links) - end + # Namespaces shared with any of the group + cte << Group.select([namespaces[:id], 'group_group_links.group_access AS access_level']) + .joins(join_group_group_links) + .joins(join_members_on_group_group_links) # Sub groups of any groups the user is a member of. cte << Group.select([ @@ -114,6 +112,8 @@ module Gitlab members = Member.arel_table cond = group_group_links[:shared_with_group_id].eq(members[:source_id]) + .and(members[:source_type].eq('Namespace')) + .and(members[:requested_at].eq(nil)) .and(members[:user_id].eq(user.id)) Arel::Nodes::InnerJoin.new(members, Arel::Nodes::On.new(cond)) end diff --git a/package.json b/package.json index b6c2d56ca0a32b1c4a46121bfbae62b62b54243c..3a5f4edb4efe6d8ee44fd6a2041142a27875ea24 100644 --- a/package.json +++ b/package.json @@ -201,7 +201,6 @@ "yarn-deduplicate": "^1.1.1" }, "resolutions": { - "at.js": "https://gitlab.com/gitlab-org/frontend/At.js.git#121ce9a557b51c33f5693ac8df52d2bda1e53cbe", "vue-jest/ts-jest": "24.0.0", "monaco-editor": "0.18.1" }, diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb index 277e7364adae9d768c92e1c040f99d3ee7de999c..b0b511eb23afee8cc81c4127127202a16bf63796 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create', :smoke do + context 'Create', :smoke, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/205511', type: :bug } do describe 'Snippet creation' do it 'User creates a snippet' do Flow::Login.sign_in diff --git a/spec/factories/project_authorizations.rb b/spec/factories/project_authorizations.rb new file mode 100644 index 0000000000000000000000000000000000000000..ffdf5576f842f48a0f6ebee51bed76242ec1b1f1 --- /dev/null +++ b/spec/factories/project_authorizations.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :project_authorization do + user + project + access_level { Gitlab::Access::REPORTER } + end +end diff --git a/spec/frontend/ide/components/error_message_spec.js b/spec/frontend/ide/components/error_message_spec.js index 1de496ba3f8afc382ad9a503690279a724b9f16d..3a4dcc5873d245fe50c7b910019ad98ec4e3ed1b 100644 --- a/spec/frontend/ide/components/error_message_spec.js +++ b/spec/frontend/ide/components/error_message_spec.js @@ -1,4 +1,4 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; import { GlLoadingIcon } from '@gitlab/ui'; import ErrorMessage from '~/ide/components/error_message.vue'; @@ -15,7 +15,7 @@ describe('IDE error message component', () => { actions: { setErrorMessage: setErrorMessageMock }, }); - wrapper = shallowMount(ErrorMessage, { + wrapper = mount(ErrorMessage, { propsData: { message: { text: 'some text', @@ -38,15 +38,18 @@ describe('IDE error message component', () => { wrapper = null; }); + const findDismissButton = () => wrapper.find('button[aria-label=Dismiss]'); + const findActionButton = () => wrapper.find('button.gl-alert-action'); + it('renders error message', () => { const text = 'error message'; createComponent({ text }); expect(wrapper.text()).toContain(text); }); - it('clears error message on click', () => { + it('clears error message on dismiss click', () => { createComponent(); - wrapper.trigger('click'); + findDismissButton().trigger('click'); expect(setErrorMessageMock).toHaveBeenCalledWith(expect.any(Object), null, undefined); }); @@ -68,29 +71,27 @@ describe('IDE error message component', () => { }); it('renders action button', () => { - const button = wrapper.find('button'); + const button = findActionButton(); expect(button.exists()).toBe(true); expect(button.text()).toContain(message.actionText); }); - it('does not clear error message on click', () => { - wrapper.trigger('click'); - - expect(setErrorMessageMock).not.toHaveBeenCalled(); + it('does not show dismiss button', () => { + expect(findDismissButton().exists()).toBe(false); }); it('dispatches action', () => { - wrapper.find('button').trigger('click'); + findActionButton().trigger('click'); expect(actionMock).toHaveBeenCalledWith(message.actionPayload); }); it('does not dispatch action when already loading', () => { - wrapper.find('button').trigger('click'); + findActionButton().trigger('click'); actionMock.mockReset(); return wrapper.vm.$nextTick(() => { - wrapper.find('button').trigger('click'); + findActionButton().trigger('click'); return wrapper.vm.$nextTick().then(() => { expect(actionMock).not.toHaveBeenCalled(); @@ -106,7 +107,7 @@ describe('IDE error message component', () => { resolveAction = resolve; }), ); - wrapper.find('button').trigger('click'); + findActionButton().trigger('click'); return wrapper.vm.$nextTick(() => { expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true); @@ -115,7 +116,7 @@ describe('IDE error message component', () => { }); it('hides loading icon when operation finishes', () => { - wrapper.find('button').trigger('click'); + findActionButton().trigger('click'); return actionMock() .then(() => wrapper.vm.$nextTick()) .then(() => { diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js index 048db4a75338bcd984995c645dfb1363c01b78fc..4241b994cbaaa0608a4f6e440c13b0936ded8f21 100644 --- a/spec/javascripts/ide/components/ide_spec.js +++ b/spec/javascripts/ide/components/ide_spec.js @@ -61,14 +61,14 @@ describe('ide component, non-empty repo', () => { }); it('shows error message when set', done => { - expect(vm.$el.querySelector('.flash-container')).toBe(null); + expect(vm.$el.querySelector('.gl-alert')).toBe(null); vm.$store.state.errorMessage = { text: 'error', }; vm.$nextTick(() => { - expect(vm.$el.querySelector('.flash-container')).not.toBe(null); + expect(vm.$el.querySelector('.gl-alert')).not.toBe(null); done(); }); diff --git a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js b/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js index a8d39b7b5fea64992f14bf864d4214470e5bffae..5f432f2a1b5faa4f1adb5da81665f30d1ce47b91 100644 --- a/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js +++ b/spec/javascripts/vue_shared/components/tooltip_on_truncate_spec.js @@ -1,149 +1,160 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { mount, shallowMount } from '@vue/test-utils'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; -const TEST_TITLE = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do'; -const STYLE_TRUNCATED = 'display: inline-block; max-width: 20px;'; -const STYLE_NORMAL = 'display: inline-block; max-width: 1000px;'; +const TEXT_SHORT = 'lorem'; +const TEXT_LONG = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do'; -const localVue = createLocalVue(); +const TEXT_TRUNCATE = 'white-space: nowrap; overflow:hidden;'; +const STYLE_NORMAL = `${TEXT_TRUNCATE} display: inline-block; max-width: 1000px;`; // does not overflows +const STYLE_OVERFLOWED = `${TEXT_TRUNCATE} display: inline-block; max-width: 50px;`; // overflowed when text is long const createElementWithStyle = (style, content) => `${content}`; describe('TooltipOnTruncate component', () => { let wrapper; + let parent; const createComponent = ({ propsData, ...options } = {}) => { - wrapper = shallowMount(localVue.extend(TooltipOnTruncate), { - localVue, + wrapper = shallowMount(TooltipOnTruncate, { attachToDocument: true, propsData: { - title: TEST_TITLE, ...propsData, }, + attrs: { + style: STYLE_OVERFLOWED, + }, ...options, }); }; + const createWrappedComponent = ({ propsData, ...options }) => { + // set a parent around the tested component + parent = mount( + { + props: { + title: { default: '' }, + }, + template: ` + + {{title}} + + `, + components: { + TooltipOnTruncate, + }, + }, + { + propsData: { ...propsData }, + attachToDocument: true, + ...options, + }, + ); + + wrapper = parent.find(TooltipOnTruncate); + }; + + const hasTooltip = () => wrapper.classes('js-show-tooltip'); + afterEach(() => { wrapper.destroy(); }); - const hasTooltip = () => wrapper.classes('js-show-tooltip'); - describe('with default target', () => { - it('renders tooltip if truncated', done => { + it('renders tooltip if truncated', () => { createComponent({ - attrs: { - style: STYLE_TRUNCATED, + propsData: { + title: TEXT_LONG, }, slots: { - default: [TEST_TITLE], + default: [TEXT_LONG], }, }); - wrapper.vm - .$nextTick() - .then(() => { - expect(hasTooltip()).toBe(true); - expect(wrapper.attributes('data-original-title')).toEqual(TEST_TITLE); - expect(wrapper.attributes('data-placement')).toEqual('top'); - }) - .then(done) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(hasTooltip()).toBe(true); + expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG); + expect(wrapper.attributes('data-placement')).toEqual('top'); + }); }); - it('does not render tooltip if normal', done => { + it('does not render tooltip if normal', () => { createComponent({ - attrs: { - style: STYLE_NORMAL, + propsData: { + title: TEXT_SHORT, }, slots: { - default: [TEST_TITLE], + default: [TEXT_SHORT], }, }); - wrapper.vm - .$nextTick() - .then(() => { - expect(hasTooltip()).toBe(false); - }) - .then(done) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(hasTooltip()).toBe(false); + }); }); }); describe('with child target', () => { - it('renders tooltip if truncated', done => { + it('renders tooltip if truncated', () => { createComponent({ attrs: { style: STYLE_NORMAL, }, propsData: { + title: TEXT_LONG, truncateTarget: 'child', }, slots: { - default: createElementWithStyle(STYLE_TRUNCATED, TEST_TITLE), + default: createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG), }, }); - wrapper.vm - .$nextTick() - .then(() => { - expect(hasTooltip()).toBe(true); - }) - .then(done) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(hasTooltip()).toBe(true); + }); }); - it('does not render tooltip if normal', done => { + it('does not render tooltip if normal', () => { createComponent({ propsData: { truncateTarget: 'child', }, slots: { - default: createElementWithStyle(STYLE_NORMAL, TEST_TITLE), + default: createElementWithStyle(STYLE_NORMAL, TEXT_LONG), }, }); - wrapper.vm - .$nextTick() - .then(() => { - expect(hasTooltip()).toBe(false); - }) - .then(done) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(hasTooltip()).toBe(false); + }); }); }); describe('with fn target', () => { - it('renders tooltip if truncated', done => { + it('renders tooltip if truncated', () => { createComponent({ attrs: { style: STYLE_NORMAL, }, propsData: { + title: TEXT_LONG, truncateTarget: el => el.childNodes[1], }, slots: { default: [ - createElementWithStyle('', TEST_TITLE), - createElementWithStyle(STYLE_TRUNCATED, TEST_TITLE), + createElementWithStyle('', TEXT_LONG), + createElementWithStyle(STYLE_OVERFLOWED, TEXT_LONG), ], }, }); - wrapper.vm - .$nextTick() - .then(() => { - expect(hasTooltip()).toBe(true); - }) - .then(done) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(hasTooltip()).toBe(true); + }); }); }); describe('placement', () => { - it('sets data-placement when tooltip is rendered', done => { + it('sets data-placement when tooltip is rendered', () => { const placement = 'bottom'; createComponent({ @@ -151,21 +162,75 @@ describe('TooltipOnTruncate component', () => { placement, }, attrs: { - style: STYLE_TRUNCATED, + style: STYLE_OVERFLOWED, }, slots: { - default: TEST_TITLE, + default: TEXT_LONG, }, }); - wrapper.vm - .$nextTick() - .then(() => { - expect(hasTooltip()).toBe(true); - expect(wrapper.attributes('data-placement')).toEqual(placement); - }) - .then(done) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(hasTooltip()).toBe(true); + expect(wrapper.attributes('data-placement')).toEqual(placement); + }); + }); + }); + + describe('updates when title and slot content changes', () => { + describe('is initialized with a long text', () => { + beforeEach(() => { + createWrappedComponent({ + propsData: { title: TEXT_LONG }, + }); + return parent.vm.$nextTick(); + }); + + it('renders tooltip', () => { + expect(hasTooltip()).toBe(true); + expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG); + expect(wrapper.attributes('data-placement')).toEqual('top'); + }); + + it('does not render tooltip after updated to a short text', () => { + parent.setProps({ + title: TEXT_SHORT, + }); + + return wrapper.vm + .$nextTick() + .then(() => wrapper.vm.$nextTick()) // wait 2 times to get an updated slot + .then(() => { + expect(hasTooltip()).toBe(false); + }); + }); + }); + + describe('is initialized with a short text', () => { + beforeEach(() => { + createWrappedComponent({ + propsData: { title: TEXT_SHORT }, + }); + return wrapper.vm.$nextTick(); + }); + + it('does not render tooltip', () => { + expect(hasTooltip()).toBe(false); + }); + + it('renders tooltip after updated to a long text', () => { + parent.setProps({ + title: TEXT_LONG, + }); + + return wrapper.vm + .$nextTick() + .then(() => wrapper.vm.$nextTick()) // wait 2 times to get an updated slot + .then(() => { + expect(hasTooltip()).toBe(true); + expect(wrapper.attributes('data-original-title')).toEqual(TEXT_LONG); + expect(wrapper.attributes('data-placement')).toEqual('top'); + }); + }); }); }); }); diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..1ef2c451aa210dbdd2061e954e6d4960bc32d01d --- /dev/null +++ b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb @@ -0,0 +1,243 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, :migration, schema: 20200204113223 do + let(:users_table) { table(:users) } + let(:namespaces_table) { table(:namespaces) } + let(:projects_table) { table(:projects) } + let(:project_authorizations_table) { table(:project_authorizations) } + let(:members_table) { table(:members) } + let(:group_group_links) { table(:group_group_links) } + let(:project_group_links) { table(:project_group_links) } + + let(:user) { users_table.create!(id: 1, email: 'user@example.com', projects_limit: 10) } + let(:group) { namespaces_table.create!(type: 'Group', name: 'group', path: 'group') } + + subject { described_class.new.perform([user.id]) } + + context 'missing authorization' do + context 'personal project' do + before do + user_namespace = namespaces_table.create!(owner_id: user.id, name: 'User', path: 'user') + projects_table.create!(id: 1, + name: 'personal-project', + path: 'personal-project', + visibility_level: 0, + namespace_id: user_namespace.id) + end + + it 'creates correct authorization' do + expect { subject }.to change { project_authorizations_table.count }.from(0).to(1) + expect(project_authorizations_table.all).to( + match_array([have_attributes(user_id: 1, project_id: 1, access_level: 40)])) + end + end + + context 'group membership' do + before do + projects_table.create!(id: 1, name: 'group-project', path: 'group-project', + visibility_level: 0, namespace_id: group.id) + members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace', + type: 'GroupMember', access_level: 20, notification_level: 3) + end + + it 'creates correct authorization' do + expect { subject }.to change { project_authorizations_table.count }.from(0).to(1) + expect(project_authorizations_table.all).to( + match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)])) + end + end + + context 'inherited group membership' do + before do + sub_group = namespaces_table.create!(type: 'Group', name: 'subgroup', + path: 'subgroup', parent_id: group.id) + projects_table.create!(id: 1, name: 'group-project', path: 'group-project', + visibility_level: 0, namespace_id: sub_group.id) + members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace', + type: 'GroupMember', access_level: 20, notification_level: 3) + end + + it 'creates correct authorization' do + expect { subject }.to change { project_authorizations_table.count }.from(0).to(1) + expect(project_authorizations_table.all).to( + match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)])) + end + end + + context 'project membership' do + before do + project = projects_table.create!(id: 1, name: 'group-project', path: 'group-project', + visibility_level: 0, namespace_id: group.id) + members_table.create!(user_id: user.id, source_id: project.id, source_type: 'Project', + type: 'ProjectMember', access_level: 20, notification_level: 3) + end + + it 'creates correct authorization' do + expect { subject }.to change { project_authorizations_table.count }.from(0).to(1) + expect(project_authorizations_table.all).to( + match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)])) + end + end + + context 'shared group' do + before do + members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace', + type: 'GroupMember', access_level: 30, notification_level: 3) + + shared_group = namespaces_table.create!(type: 'Group', name: 'shared group', + path: 'shared-group') + projects_table.create!(id: 1, name: 'project', path: 'project', visibility_level: 0, + namespace_id: shared_group.id) + + group_group_links.create(shared_group_id: shared_group.id, shared_with_group_id: group.id, + group_access: 20) + end + + it 'creates correct authorization' do + expect { subject }.to change { project_authorizations_table.count }.from(0).to(1) + expect(project_authorizations_table.all).to( + match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)])) + end + end + + context 'shared project' do + before do + members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace', + type: 'GroupMember', access_level: 30, notification_level: 3) + + another_group = namespaces_table.create!(type: 'Group', name: 'another group', path: 'another-group') + shared_project = projects_table.create!(id: 1, name: 'shared project', path: 'shared-project', + visibility_level: 0, namespace_id: another_group.id) + + project_group_links.create(project_id: shared_project.id, group_id: group.id, group_access: 20) + end + + it 'creates correct authorization' do + expect { subject }.to change { project_authorizations_table.count }.from(0).to(1) + expect(project_authorizations_table.all).to( + match_array([have_attributes(user_id: 1, project_id: 1, access_level: 20)])) + end + end + end + + context 'unapproved access requests' do + context 'group membership' do + before do + projects_table.create!(id: 1, name: 'group-project', path: 'group-project', + visibility_level: 0, namespace_id: group.id) + members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace', + type: 'GroupMember', access_level: 20, requested_at: Time.now, notification_level: 3) + end + + it 'does not create authorization' do + expect { subject }.not_to change { project_authorizations_table.count }.from(0) + end + end + + context 'inherited group membership' do + before do + sub_group = namespaces_table.create!(type: 'Group', name: 'subgroup', path: 'subgroup', + parent_id: group.id) + projects_table.create!(id: 1, name: 'group-project', path: 'group-project', + visibility_level: 0, namespace_id: sub_group.id) + members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace', + type: 'GroupMember', access_level: 20, requested_at: Time.now, notification_level: 3) + end + + it 'does not create authorization' do + expect { subject }.not_to change { project_authorizations_table.count }.from(0) + end + end + + context 'project membership' do + before do + project = projects_table.create!(id: 1, name: 'group-project', path: 'group-project', + visibility_level: 0, namespace_id: group.id) + members_table.create!(user_id: user.id, source_id: project.id, source_type: 'Project', + type: 'ProjectMember', access_level: 20, requested_at: Time.now, notification_level: 3) + end + + it 'does not create authorization' do + expect { subject }.not_to change { project_authorizations_table.count }.from(0) + end + end + + context 'shared group' do + before do + members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace', + type: 'GroupMember', access_level: 30, requested_at: Time.now, notification_level: 3) + + shared_group = namespaces_table.create!(type: 'Group', name: 'shared group', + path: 'shared-group') + projects_table.create!(id: 1, name: 'project', path: 'project', visibility_level: 0, + namespace_id: shared_group.id) + + group_group_links.create(shared_group_id: shared_group.id, shared_with_group_id: group.id, + group_access: 20) + end + + it 'does not create authorization' do + expect { subject }.not_to change { project_authorizations_table.count }.from(0) + end + end + + context 'shared project' do + before do + members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace', + type: 'GroupMember', access_level: 30, requested_at: Time.now, notification_level: 3) + + another_group = namespaces_table.create!(type: 'Group', name: 'another group', path: 'another-group') + shared_project = projects_table.create!(id: 1, name: 'shared project', path: 'shared-project', + visibility_level: 0, namespace_id: another_group.id) + + project_group_links.create(project_id: shared_project.id, group_id: group.id, group_access: 20) + end + + it 'does not create authorization' do + expect { subject }.not_to change { project_authorizations_table.count }.from(0) + end + end + end + + context 'incorrect authorization' do + before do + project = projects_table.create!(id: 1, name: 'group-project', path: 'group-project', + visibility_level: 0, namespace_id: group.id) + members_table.create!(user_id: user.id, source_id: group.id, source_type: 'Namespace', + type: 'GroupMember', access_level: 30, notification_level: 3) + + project_authorizations_table.create!(user_id: user.id, project_id: project.id, + access_level: 10) + end + + it 'fixes authorization' do + expect { subject }.not_to change { project_authorizations_table.count }.from(1) + expect(project_authorizations_table.all).to( + match_array([have_attributes(user_id: 1, project_id: 1, access_level: 30)])) + end + end + + context 'unwanted authorization' do + before do + project = projects_table.create!(name: 'group-project', path: 'group-project', + visibility_level: 0, namespace_id: group.id) + + project_authorizations_table.create!(user_id: user.id, project_id: project.id, + access_level: 10) + end + + it 'deletes authorization' do + expect { subject }.to change { project_authorizations_table.count }.from(1).to(0) + end + end + + context 'deleted user' do + let(:nonexistent_user_id) { User.maximum(:id).to_i + 999 } + + it 'does not fail' do + expect { described_class.new.perform([nonexistent_user_id]) }.not_to raise_error + end + end +end diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 4733607ccc04452b7a147950aa314f1feb7d3c6b..61d7400b95ebd561fb50ff03f1c3603d5b6b5d9a 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -175,7 +175,7 @@ describe Gitlab::Diff::File do [diff_file.new_content_sha, diff_file.new_path], [diff_file.old_content_sha, diff_file.old_path] ] - expect(project.repository).to receive(:blobs_at).with(items, blob_size_limit: 100 * 1024).and_call_original + expect(project.repository).to receive(:blobs_at).with(items, blob_size_limit: 10.megabytes).and_call_original old_data = diff_file.old_blob.data data = diff_file.new_blob.data diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 820578dfc6e75491edf23bf501871c1ed601fbe6..5e1d6199c3cc7f74bea6aac9f4a014be8fe25ed9 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -279,4 +279,19 @@ describe Gitlab::GitalyClient::CommitService do expect(subject.deletions).to eq(15) end end + + describe '#find_commits' do + it 'sends an RPC request' do + request = Gitaly::FindCommitsRequest.new( + repository: repository_message, + disable_walk: true, + order: 'TOPO' + ) + + expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits) + .with(request, kind_of(Hash)).and_return([]) + + client.find_commits(order: 'topo') + end + end end diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb index 6e5c36172e2622992d5a9172f662c8c19eac8504..1c579128223b29efc65ad124498bf6e403938d71 100644 --- a/spec/lib/gitlab/project_authorizations_spec.rb +++ b/spec/lib/gitlab/project_authorizations_spec.rb @@ -97,87 +97,68 @@ describe Gitlab::ProjectAuthorizations do create(:group_group_link, shared_group: shared_group, shared_with_group: group) end - context 'when feature flag share_group_with_group is enabled' do - before do - stub_feature_flags(share_group_with_group: true) - end - - context 'group user' do - let(:user) { group_user } + context 'group user' do + let(:user) { group_user } - it 'creates proper authorizations' do - mapping = map_access_levels(authorizations) + it 'creates proper authorizations' do + mapping = map_access_levels(authorizations) - expect(mapping[project_parent.id]).to be_nil - expect(mapping[project.id]).to eq(Gitlab::Access::DEVELOPER) - expect(mapping[project_child.id]).to eq(Gitlab::Access::DEVELOPER) - end + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to eq(Gitlab::Access::DEVELOPER) + expect(mapping[project_child.id]).to eq(Gitlab::Access::DEVELOPER) end + end - context 'parent group user' do - let(:user) { parent_group_user } + context 'parent group user' do + let(:user) { parent_group_user } - it 'creates proper authorizations' do - mapping = map_access_levels(authorizations) + it 'creates proper authorizations' do + mapping = map_access_levels(authorizations) - expect(mapping[project_parent.id]).to be_nil - expect(mapping[project.id]).to be_nil - expect(mapping[project_child.id]).to be_nil - end + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to be_nil + expect(mapping[project_child.id]).to be_nil end + end - context 'child group user' do - let(:user) { child_group_user } + context 'child group user' do + let(:user) { child_group_user } - it 'creates proper authorizations' do - mapping = map_access_levels(authorizations) + it 'creates proper authorizations' do + mapping = map_access_levels(authorizations) - expect(mapping[project_parent.id]).to be_nil - expect(mapping[project.id]).to be_nil - expect(mapping[project_child.id]).to be_nil - end + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to be_nil + expect(mapping[project_child.id]).to be_nil end end - context 'when feature flag share_group_with_group is disabled' do - before do - stub_feature_flags(share_group_with_group: false) - end - - context 'group user' do - let(:user) { group_user } - - it 'creates proper authorizations' do - mapping = map_access_levels(authorizations) + context 'user without accepted access request' do + let!(:user) { create(:user) } - expect(mapping[project_parent.id]).to be_nil - expect(mapping[project.id]).to be_nil - expect(mapping[project_child.id]).to be_nil - end - end + it 'does not have access to group and its projects' do + create(:group_member, :developer, :access_request, user: user, group: group) - context 'parent group user' do - let(:user) { parent_group_user } + mapping = map_access_levels(authorizations) - it 'creates proper authorizations' do - mapping = map_access_levels(authorizations) - - expect(mapping[project_parent.id]).to be_nil - expect(mapping[project.id]).to be_nil - expect(mapping[project_child.id]).to be_nil - end + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to be_nil + expect(mapping[project_child.id]).to be_nil end + end - context 'child group user' do - let(:user) { child_group_user } + context 'unrelated project owner' do + let(:common_id) { [Project.maximum(:id).to_i, Namespace.maximum(:id).to_i].max + 999 } + let!(:group) { create(:group, id: common_id) } + let!(:unrelated_project) { create(:project, id: common_id) } + let(:user) { unrelated_project.owner } - it 'creates proper authorizations' do - mapping = map_access_levels(authorizations) + it 'does not have access to group and its projects' do + mapping = map_access_levels(authorizations) - expect(mapping[project_parent.id]).to be_nil - expect(mapping[project.id]).to be_nil - expect(mapping[project_child.id]).to be_nil - end + expect(mapping[project_parent.id]).to be_nil + expect(mapping[project.id]).to be_nil + expect(mapping[project_child.id]).to be_nil end end end diff --git a/spec/migrations/schedule_recalculate_project_authorizations_spec.rb b/spec/migrations/schedule_recalculate_project_authorizations_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..77ad2b2dc8e7b6c7215b8cd1ce023aa708a6a272 --- /dev/null +++ b/spec/migrations/schedule_recalculate_project_authorizations_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200204113223_schedule_recalculate_project_authorizations.rb') + +describe ScheduleRecalculateProjectAuthorizations, :migration do + let(:users_table) { table(:users) } + let(:namespaces_table) { table(:namespaces) } + let(:projects_table) { table(:projects) } + let(:project_authorizations_table) { table(:project_authorizations) } + + let(:user1) { users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 1) } + let(:user2) { users_table.create!(name: 'user2', email: 'user2@example.com', projects_limit: 1) } + let(:group) { namespaces_table.create!(id: 1, type: 'Group', name: 'group', path: 'group') } + let(:project) do + projects_table.create!(id: 1, name: 'project', path: 'project', + visibility_level: 0, namespace_id: group.id) + end + + before do + stub_const("#{described_class}::BATCH_SIZE", 1) + + project_authorizations_table.create!(user_id: user1.id, project_id: project.id, access_level: 30) + project_authorizations_table.create!(user_id: user2.id, project_id: project.id, access_level: 30) + end + + it 'schedules background migration' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + expect(described_class::MIGRATION).to be_scheduled_migration([user1.id]) + expect(described_class::MIGRATION).to be_scheduled_migration([user2.id]) + end + end + end + + it 'ignores projects with higher id than maximum group id' do + another_user = users_table.create!(name: 'another user', email: 'another-user@example.com', + projects_limit: 1) + ignored_project = projects_table.create!(id: 2, name: 'ignored-project', path: 'ignored-project', + visibility_level: 0, namespace_id: group.id) + project_authorizations_table.create!(user_id: another_user.id, project_id: ignored_project.id, + access_level: 30) + + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + expect(described_class::MIGRATION).to be_scheduled_migration([user1.id]) + expect(described_class::MIGRATION).to be_scheduled_migration([user2.id]) + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5c56d1aa757ceb2067e20a30a5eea6b50968fad8..79d9e42666c7629a7171f3fa86e68041e6b4ae17 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1980,6 +1980,23 @@ describe Project do expect(project.reload.import_url).to eq('http://test.com') end + + it 'saves the url credentials percent decoded' do + url = 'http://user:pass%21%3F%40@github.com/t.git' + project = build(:project, import_url: url) + + # When the credentials are not decoded this expectation fails + expect(project.import_url).to eq(url) + expect(project.import_data.credentials).to eq(user: 'user', password: 'pass!?@') + end + + it 'saves url with no credentials' do + url = 'http://github.com/t.git' + project = build(:project, import_url: url) + + expect(project.import_url).to eq(url) + expect(project.import_data.credentials).to eq(user: nil, password: nil) + end end describe '#container_registry_url' do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 0adf3fc8e8581f9842622b1ba4f13e0c833be0be..00ffc3cae540af603cc22f290927482912768b4b 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -325,6 +325,14 @@ describe Repository do expect(repository.commits(nil, all: true, limit: 60).size).to eq(60) end end + + context "when 'order' flag is set" do + it 'passes order option to perform the query' do + expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(order: 'topo')).and_call_original + + repository.commits('master', limit: 1, order: 'topo') + end + end end describe '#new_commits' do diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index e390f3945a97622a5b39a0356dd0b936c58aa29e..170b9ccccf8650ac91aa8f6e357c7c68829e7eb8 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -12,7 +12,6 @@ describe API::Commits do let(:project) { create(:project, :repository, creator: user, path: 'my.project') } let(:branch_with_dot) { project.repository.find_branch('ends-with.json') } let(:branch_with_slash) { project.repository.find_branch('improve/awesome') } - let(:project_id) { project.id } let(:current_user) { nil } @@ -241,6 +240,40 @@ describe API::Commits do end end end + + context 'with order parameter' do + let(:route) { "/projects/#{project_id}/repository/commits?ref_name=0031876&per_page=6&order=#{order}" } + + context 'set to topo' do + let(:order) { 'topo' } + + # git log --graph -n 6 --pretty=format:"%h" --topo-order 0031876 + # * 0031876 + # |\ + # | * 48ca272 + # | * 335bc94 + # * | bf6e164 + # * | 9d526f8 + # |/ + # * 1039376 + it 'returns project commits ordered by topo order' do + commits = project.repository.commits("0031876", limit: 6, order: 'topo') + + get api(route, current_user) + + expect(json_response.size).to eq(6) + expect(json_response.map { |entry| entry["id"] }).to eq(commits.map(&:id)) + end + end + + context 'set to blank' do + let(:order) { '' } + + it_behaves_like '400 response' do + let(:request) { get api(route, current_user) } + end + end + end end end diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 09e005398a9f2aa4f0d074fb8010903d8a06b2d4..19422d4ca39df19bea9270cb05f22e8f9a5340e7 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -795,13 +795,13 @@ describe API::Issues do it 'returns issues from non archived projects only by default' do get api("/groups/#{group1.id}/issues", user), params: { scope: 'all' } - expect_response_contain_exactly(issue2, issue1) + expect_paginated_array_response([issue2.id, issue1.id]) end it 'returns issues from archived and non archived projects when non_archived is false' do get api("/groups/#{group1.id}/issues", user), params: { non_archived: false, scope: 'all' } - expect_response_contain_exactly(issue1, issue2, issue3) + expect_paginated_array_response([issue3.id, issue2.id, issue1.id]) end end end @@ -888,9 +888,4 @@ describe API::Issues do include_examples 'time tracking endpoints', 'issue' end - - def expect_response_contain_exactly(*items) - expect(json_response.length).to eq(items.size) - expect(json_response.map { |element| element['id'] }).to contain_exactly(*items.map(&:id)) - end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 427a361295c5c161654c7cd7e340f6dd89ed17e8..00af0937dd791a190aac52060e5a85ff5ff04d07 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -41,8 +41,7 @@ describe API::MergeRequests do it 'returns merge requests for public projects' do get api(endpoint_path) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array end end @@ -87,10 +86,11 @@ describe API::MergeRequests do it 'returns an array of all merge_requests' do get api(endpoint_path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(4) + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, + merge_request_closed.id, merge_request.id + ]) + expect(json_response.last['title']).to eq(merge_request.title) expect(json_response.last).to have_key('web_url') expect(json_response.last['sha']).to eq(merge_request.diff_head_sha) @@ -111,7 +111,7 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) + expect_successful_response_with_paginated_array expect(json_response.last['labels'].pluck('name')).to eq([label2.title, label.title]) expect(json_response.last['labels'].first).to match_schema('/public_api/v4/label_basic') end @@ -139,11 +139,11 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, + merge_request_closed.id, merge_request.id + ]) expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at)) - expect(json_response).to be_an Array - expect(json_response.length).to eq(4) expect(json_response.last['iid']).to eq(merge_request.iid) expect(json_response.last['title']).to eq(merge_request.title) expect(json_response.last).to have_key('web_url') @@ -157,10 +157,10 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(4) + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, + merge_request_closed.id, merge_request.id + ]) expect(json_response.last['title']).to eq(merge_request.title) end @@ -169,10 +169,7 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response([merge_request.id]) expect(json_response.last['title']).to eq(merge_request.title) end @@ -181,10 +178,7 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response([merge_request_closed.id]) expect(json_response.first['title']).to eq(merge_request_closed.title) end @@ -193,10 +187,7 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response([merge_request_merged.id]) expect(json_response.first['title']).to eq(merge_request_merged.title) end @@ -210,17 +201,13 @@ describe API::MergeRequests do it 'returns an empty array if no issue matches milestone' do get api(endpoint_path, user), params: { milestone: '1.0.0' } - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_empty_array_response end it 'returns an empty array if milestone does not exist' do get api(endpoint_path, user), params: { milestone: 'foo' } - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_empty_array_response end it 'returns an array of merge requests in given milestone' do @@ -234,9 +221,7 @@ describe API::MergeRequests do it 'returns an array of merge requests matching state in milestone' do get api(endpoint_path, user), params: { milestone: '0.9', state: 'closed' } - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response([merge_request_closed.id]) expect(json_response.first['id']).to eq(merge_request_closed.id) end @@ -248,8 +233,7 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label2.title, label.title]) end @@ -259,9 +243,7 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_empty_array_response end it 'returns an empty array if no merge request matches labels' do @@ -269,9 +251,7 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_empty_array_response end it 'returns an array of labeled merge requests where all labels match' do @@ -279,8 +259,7 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label2.title, label.title]) end @@ -288,8 +267,7 @@ describe API::MergeRequests do it 'returns an array of merge requests with any label when filtering by any label' do get api(endpoint_path, user), params: { labels: [" #{label.title} ", " #{label2.title} "] } - expect_paginated_array_response - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label2.title, label.title]) expect(json_response.first['id']).to eq(merge_request.id) @@ -298,8 +276,7 @@ describe API::MergeRequests do it 'returns an array of merge requests with any label when filtering by any label' do get api(endpoint_path, user), params: { labels: ["#{label.title} , #{label2.title}"] } - expect_paginated_array_response - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array expect(json_response.length).to eq(1) expect(json_response.first['labels']).to eq([label2.title, label.title]) expect(json_response.first['id']).to eq(merge_request.id) @@ -308,7 +285,7 @@ describe API::MergeRequests do it 'returns an array of merge requests with any label when filtering by any label' do get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_ANY } - expect_paginated_array_response + expect_successful_response_with_paginated_array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(merge_request.id) end @@ -316,10 +293,9 @@ describe API::MergeRequests do it 'returns an array of merge requests without a label when filtering by no label' do get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_NONE } - response_ids = json_response.map { |merge_request| merge_request['id'] } - - expect_paginated_array_response - expect(response_ids).to contain_exactly(merge_request_closed.id, merge_request_merged.id, merge_request_locked.id) + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, merge_request_closed.id + ]) end end @@ -339,10 +315,7 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(mr2.id) + expect_paginated_array_response([mr2.id]) end context 'with ordering' do @@ -356,10 +329,10 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(4) + expect_paginated_array_response([ + merge_request_closed.id, merge_request_locked.id, + merge_request_merged.id, merge_request.id + ]) response_dates = json_response.map { |merge_request| merge_request['created_at'] } expect(response_dates).to eq(response_dates.sort) end @@ -369,10 +342,10 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(4) + expect_paginated_array_response([ + merge_request.id, merge_request_merged.id, + merge_request_locked.id, merge_request_closed.id + ]) response_dates = json_response.map { |merge_request| merge_request['created_at'] } expect(response_dates).to eq(response_dates.sort.reverse) end @@ -414,10 +387,10 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(4) + expect_paginated_array_response([ + merge_request.id, merge_request_locked.id, + merge_request_merged.id, merge_request_closed.id + ]) response_dates = json_response.map { |merge_request| merge_request['updated_at'] } expect(response_dates).to eq(response_dates.sort.reverse) end @@ -427,10 +400,10 @@ describe API::MergeRequests do get api(path, user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(4) + expect_paginated_array_response([ + merge_request_closed.id, merge_request_locked.id, + merge_request_merged.id, merge_request.id + ]) response_dates = json_response.map { |merge_request| merge_request['created_at'] } expect(response_dates).to eq(response_dates.sort) end @@ -440,7 +413,9 @@ describe API::MergeRequests do it 'returns merge requests with the given source branch' do get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' } - expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked) + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, merge_request_closed.id + ]) end end @@ -448,7 +423,9 @@ describe API::MergeRequests do it 'returns merge requests with the given target branch' do get api(endpoint_path, user), params: { target_branch: merge_request_closed.target_branch, state: 'all' } - expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked) + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, merge_request_closed.id + ]) end end end @@ -471,7 +448,10 @@ describe API::MergeRequests do it 'returns an array of all merge requests' do get api('/merge_requests', user), params: { scope: 'all' } - expect_paginated_array_response + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, + merge_request_closed.id, merge_request.id + ]) end it "returns authentication error without any scope" do @@ -507,30 +487,23 @@ describe API::MergeRequests do it 'returns an array of all merge requests except unauthorized ones' do get api('/merge_requests', user), params: { scope: :all } - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request_locked.id, merge_request2.id) + expect_paginated_array_response([ + merge_request_merged.id, merge_request2.id, merge_request_locked.id, merge_request_closed.id, merge_request.id + ]) end it "returns an array of no merge_requests when wip=yes" do get api("/merge_requests", user), params: { wip: 'yes' } - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_empty_array_response end it "returns an array of no merge_requests when wip=no" do get api("/merge_requests", user), params: { wip: 'no' } - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.map { |mr| mr['id'] }) - .to contain_exactly(merge_request.id, merge_request_closed.id, merge_request_merged.id, merge_request_locked.id, merge_request2.id) + expect_paginated_array_response([ + merge_request_merged.id, merge_request2.id, merge_request_locked.id, merge_request_closed.id, merge_request.id + ]) end it 'does not return unauthorized merge requests' do @@ -539,7 +512,9 @@ describe API::MergeRequests do get api('/merge_requests', user), params: { scope: :all } - expect_response_contain_exactly(merge_request2, merge_request_merged, merge_request_closed, merge_request, merge_request_locked) + expect_paginated_array_response([ + merge_request_merged.id, merge_request2.id, merge_request_locked.id, merge_request_closed.id, merge_request.id + ]) expect(json_response.map { |mr| mr['id'] }).not_to include(merge_request3.id) end @@ -548,7 +523,7 @@ describe API::MergeRequests do get api('/merge_requests', user2) - expect_response_ordered_exactly(merge_request3) + expect_paginated_array_response([merge_request3.id]) end it 'returns an array of merge requests authored by the given user' do @@ -556,7 +531,7 @@ describe API::MergeRequests do get api('/merge_requests', user), params: { author_id: user2.id, scope: :all } - expect_response_ordered_exactly(merge_request3) + expect_paginated_array_response([merge_request3.id]) end it 'returns an array of merge requests assigned to the given user' do @@ -564,7 +539,7 @@ describe API::MergeRequests do get api('/merge_requests', user), params: { assignee_id: user2.id, scope: :all } - expect_response_ordered_exactly(merge_request3) + expect_paginated_array_response([merge_request3.id]) end it 'returns an array of merge requests with no assignee' do @@ -572,7 +547,7 @@ describe API::MergeRequests do get api('/merge_requests', user), params: { assignee_id: 'None', scope: :all } - expect_response_ordered_exactly(merge_request3) + expect_paginated_array_response([merge_request3.id]) end it 'returns an array of merge requests with any assignee' do @@ -581,7 +556,10 @@ describe API::MergeRequests do get api('/merge_requests', user), params: { assignee_id: 'Any', scope: :all } - expect_response_contain_exactly(merge_request, merge_request2, merge_request_closed, merge_request_merged, merge_request_locked) + expect_paginated_array_response([ + merge_request_merged.id, merge_request2.id, merge_request_locked.id, + merge_request_closed.id, merge_request.id + ]) end it 'returns an array of merge requests assigned to me' do @@ -589,7 +567,7 @@ describe API::MergeRequests do get api('/merge_requests', user2), params: { scope: 'assigned_to_me' } - expect_response_ordered_exactly(merge_request3) + expect_paginated_array_response([merge_request3.id]) end it 'returns an array of merge requests assigned to me (kebab-case)' do @@ -597,7 +575,7 @@ describe API::MergeRequests do get api('/merge_requests', user2), params: { scope: 'assigned-to-me' } - expect_response_ordered_exactly(merge_request3) + expect_paginated_array_response([merge_request3.id]) end it 'returns an array of merge requests created by me' do @@ -605,7 +583,7 @@ describe API::MergeRequests do get api('/merge_requests', user2), params: { scope: 'created_by_me' } - expect_response_ordered_exactly(merge_request3) + expect_paginated_array_response([merge_request3.id]) end it 'returns an array of merge requests created by me (kebab-case)' do @@ -613,7 +591,7 @@ describe API::MergeRequests do get api('/merge_requests', user2), params: { scope: 'created-by-me' } - expect_response_ordered_exactly(merge_request3) + expect_paginated_array_response([merge_request3.id]) end it 'returns merge requests reacted by the authenticated user by the given emoji' do @@ -622,14 +600,16 @@ describe API::MergeRequests do get api('/merge_requests', user2), params: { my_reaction_emoji: award_emoji.name, scope: 'all' } - expect_response_ordered_exactly(merge_request3) + expect_paginated_array_response([merge_request3.id]) end context 'source_branch param' do it 'returns merge requests with the given source branch' do get api('/merge_requests', user), params: { source_branch: merge_request_closed.source_branch, state: 'all' } - expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked) + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, merge_request_closed.id + ]) end end @@ -637,7 +617,9 @@ describe API::MergeRequests do it 'returns merge requests with the given target branch' do get api('/merge_requests', user), params: { target_branch: merge_request_closed.target_branch, state: 'all' } - expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked) + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, merge_request_closed.id + ]) end end @@ -646,7 +628,7 @@ describe API::MergeRequests do get api('/merge_requests?created_before=2000-01-02T00:00:00.060Z', user) - expect_response_ordered_exactly(merge_request2) + expect_paginated_array_response([merge_request2.id]) end it 'returns merge requests created after a specific date' do @@ -654,7 +636,7 @@ describe API::MergeRequests do get api("/merge_requests?created_after=#{merge_request2.created_at}", user) - expect_response_ordered_exactly(merge_request2) + expect_paginated_array_response([merge_request2.id]) end it 'returns merge requests updated before a specific date' do @@ -662,7 +644,7 @@ describe API::MergeRequests do get api('/merge_requests?updated_before=2000-01-02T00:00:00.060Z', user) - expect_response_ordered_exactly(merge_request2) + expect_paginated_array_response([merge_request2.id]) end it 'returns merge requests updated after a specific date' do @@ -670,7 +652,7 @@ describe API::MergeRequests do get api("/merge_requests?updated_after=#{merge_request2.updated_at}", user) - expect_response_ordered_exactly(merge_request2) + expect_paginated_array_response([merge_request2.id]) end context 'search params' do @@ -681,25 +663,25 @@ describe API::MergeRequests do it 'returns merge requests matching given search string for title' do get api("/merge_requests", user), params: { search: merge_request.title } - expect_response_ordered_exactly(merge_request) + expect_paginated_array_response([merge_request.id]) end it 'returns merge requests matching given search string for title and scoped in title' do get api("/merge_requests", user), params: { search: merge_request.title, in: 'title' } - expect_response_ordered_exactly(merge_request) + expect_paginated_array_response([merge_request.id]) end - it 'returns an empty array if no merge reques matches given search string for description and scoped in title' do + it 'returns an empty array if no merge request matches given search string for description and scoped in title' do get api("/merge_requests", user), params: { search: merge_request.description, in: 'title' } - expect_response_contain_exactly + expect_empty_array_response end it 'returns merge requests for project matching given search string for description' do get api("/merge_requests", user), params: { project_id: project.id, search: merge_request.description } - expect_response_ordered_exactly(merge_request) + expect_paginated_array_response([merge_request.id]) end end @@ -707,7 +689,7 @@ describe API::MergeRequests do it 'returns merge requests with the given state' do get api('/merge_requests', user), params: { state: 'locked' } - expect_response_contain_exactly(merge_request_locked) + expect_paginated_array_response([merge_request_locked.id]) end end end @@ -729,18 +711,13 @@ describe API::MergeRequests do it "returns an array of no merge_requests when wip=yes" do get api("/projects/#{project.id}/merge_requests", user), params: { wip: 'yes' } - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_empty_array_response end it 'returns merge_request by "iids" array' do get api(endpoint_path, user), params: { iids: [merge_request.iid, merge_request_closed.iid] } - expect(response).to have_gitlab_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response([merge_request_closed.id, merge_request.id]) expect(json_response.first['title']).to eq merge_request_closed.title expect(json_response.first['id']).to eq merge_request_closed.id end @@ -815,12 +792,10 @@ describe API::MergeRequests do it 'returns an array excluding merge_requests from archived projects' do get api(endpoint_path, user) - expect_response_contain_exactly( - merge_request_merged, - merge_request_locked, - merge_request_closed, - merge_request - ) + expect_paginated_array_response([ + merge_request_merged.id, merge_request_locked.id, + merge_request_closed.id, merge_request.id + ]) end context 'with non_archived param set as false' do @@ -829,13 +804,10 @@ describe API::MergeRequests do get api(path, user) - expect_response_contain_exactly( - merge_request_merged, - merge_request_locked, - merge_request_closed, - merge_request, - merge_request_archived - ) + expect_paginated_array_response([ + merge_request_merged.id, merge_request_archived.id, merge_request_locked.id, + merge_request_closed.id, merge_request.id + ]) end end end @@ -1079,9 +1051,7 @@ describe API::MergeRequests do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits", user) commit = merge_request.commits.first - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array expect(json_response.size).to eq(merge_request.commits.size) expect(json_response.first['id']).to eq(commit.id) expect(json_response.first['title']).to eq(commit.title) @@ -1105,9 +1075,7 @@ describe API::MergeRequests do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/context_commits", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array expect(json_response.size).to eq(merge_request.context_commits.size) expect(json_response.first['id']).to eq(context_commit.id) expect(json_response.first['title']).to eq(context_commit.title) @@ -1147,9 +1115,7 @@ describe API::MergeRequests do it 'returns a paginated array of corresponding pipelines' do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines") - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array expect(json_response.count).to eq(1) expect(json_response.first['id']).to eq(pipeline.id) end @@ -1395,7 +1361,7 @@ describe API::MergeRequests do expect(json_response['labels']).to eq([]) end - xit 'empty label param as array, does not add any labels' do + it 'empty label param as array, does not add any labels' do params[:labels] = [] post api("/projects/#{project.id}/merge_requests", user), params: params @@ -2232,7 +2198,7 @@ describe API::MergeRequests do expect(json_response['labels']).to eq [] end - xit 'empty label as array, removes labels' do + it 'empty label as array, removes labels' do put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { title: 'new issue', @@ -2240,7 +2206,6 @@ describe API::MergeRequests do } expect(response.status).to eq(200) - # fails, as grape ommits for some reason empty array as optional param value, so nothing it passed along expect(json_response['labels']).to eq [] end @@ -2306,9 +2271,7 @@ describe API::MergeRequests do get api("/projects/#{project.id}/merge_requests/#{mr.iid}/closes_issues", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(issue.id) end @@ -2316,10 +2279,7 @@ describe API::MergeRequests do it 'returns an empty array when there are no issues to be closed' do get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_empty_array_response end it 'handles external issues' do @@ -2332,9 +2292,7 @@ describe API::MergeRequests do get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user) - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + expect_successful_response_with_paginated_array expect(json_response.length).to eq(2) expect(json_response.second['title']).to eq(ext_issue.title) expect(json_response.second['id']).to eq(ext_issue.id) @@ -2546,22 +2504,4 @@ describe API::MergeRequests do merge_request_closed.save merge_request_closed end - - def expect_response_contain_exactly(*items) - expect_paginated_array_response - expect(json_response.length).to eq(items.size) - expect(json_response.map { |element| element['id'] }).to contain_exactly(*items.map(&:id)) - end - - def expect_response_ordered_exactly(*items) - expect_paginated_array_response - expect(json_response.length).to eq(items.size) - expect(json_response.map { |element| element['id'] }).to eq(items.map(&:id)) - end - - def expect_paginated_array_response - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - end end diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb index f5a914bb482b04672b742d68ef8a9bf724447753..d7ba7f5f69e7e034ffcb84844a747d3eb5ad3171 100644 --- a/spec/services/users/refresh_authorized_projects_service_spec.rb +++ b/spec/services/users/refresh_authorized_projects_service_spec.rb @@ -22,6 +22,42 @@ describe Users::RefreshAuthorizedProjectsService do service.execute end + + context 'callbacks' do + let(:callback) { double('callback') } + + context 'incorrect_auth_found_callback callback' do + let(:user) { create(:user) } + let(:service) do + described_class.new(user, + incorrect_auth_found_callback: callback) + end + + it 'is called' do + access_level = Gitlab::Access::DEVELOPER + create(:project_authorization, user: user, project: project, access_level: access_level) + + expect(callback).to receive(:call).with(project.id, access_level).once + + service.execute + end + end + + context 'missing_auth_found_callback callback' do + let(:service) do + described_class.new(user, + missing_auth_found_callback: callback) + end + + it 'is called' do + ProjectAuthorization.delete_all + + expect(callback).to receive(:call).with(project.id, Gitlab::Access::MAINTAINER).once + + service.execute + end + end + end end describe '#execute_without_lease' do diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb index 4bf6a17c03ef76905e67339e0849d53592b1e222..44c38df71b0ec155b4c8eb51b38e203b09f7fe76 100644 --- a/spec/support/helpers/api_helpers.rb +++ b/spec/support/helpers/api_helpers.rb @@ -40,6 +40,17 @@ module ApiHelpers end end + def expect_empty_array_response + expect_successful_response_with_paginated_array + expect(json_response.length).to eq(0) + end + + def expect_successful_response_with_paginated_array + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + end + def expect_paginated_array_response(items) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers diff --git a/yarn.lock b/yarn.lock index 3989da9f8009a7c78fb51e830b74cacdb0750ed5..ce6a0e94fc1b9f1e3a0e5b0fdcf4082dcf28f168 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1774,9 +1774,10 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -at.js@^1.5.4, "at.js@https://gitlab.com/gitlab-org/frontend/At.js.git#121ce9a557b51c33f5693ac8df52d2bda1e53cbe": +at.js@^1.5.4: version "1.5.4" - resolved "https://gitlab.com/gitlab-org/frontend/At.js.git#121ce9a557b51c33f5693ac8df52d2bda1e53cbe" + resolved "https://registry.yarnpkg.com/at.js/-/at.js-1.5.4.tgz#8fc60cc80eadbe4874449b166a818e7ae1d784c1" + integrity sha512-G8mgUb/PqShPoH8AyjuxsTGvIr1o716BtQUKDM44C8qN2W615y7KGJ68MlTGamd0J0D/m28emUkzagaHTdrGZw== atob@^2.1.1: version "2.1.2"