diff options
69 files changed, 772 insertions, 518 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 1eea9729550..707d98a0ad9 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -57,7 +57,7 @@ GITALY_SERVER_VERSION @project_278964_bot6 @gitlab-org/maintainers/rails-backend /ee/app/finders/ @gitlab-org/maintainers/database /rubocop/rubocop-migrations.yml @gitlab-org/maintainers/database -^[Engineering Productivity] +[Engineering Productivity] /.gitlab-ci.yml @gl-quality/eng-prod /.gitlab/ci/ @gl-quality/eng-prod /.gitlab/ci/docs.gitlab-ci.yml @gl-quality/eng-prod @gl-docsteam @@ -390,12 +390,12 @@ Dangerfile @gl-quality/eng-prod /lib/gitlab/utils.rb @gitlab-com/gl-security/appsec ^[Gitaly] -lib/gitlab/git_access.rb @proglottis @toon @zj-gitlab -lib/gitlab/git_access_*.rb @proglottis @toon @zj-gitlab -ee/lib/ee/gitlab/git_access.rb @proglottis @toon @zj-gitlab -ee/lib/ee/gitlab/git_access_*.rb @proglottis @toon @zj-gitlab -ee/lib/ee/gitlab/checks/** @proglottis @toon @zj-gitlab -lib/gitlab/checks/** @proglottis @toon @zj-gitlab +lib/gitlab/git_access.rb @proglottis @toon +lib/gitlab/git_access_*.rb @proglottis @toon +ee/lib/ee/gitlab/git_access.rb @proglottis @toon +ee/lib/ee/gitlab/git_access_*.rb @proglottis @toon +ee/lib/ee/gitlab/checks/** @proglottis @toon +lib/gitlab/checks/** @proglottis @toon ^[Documentation Directories] /doc/ @gl-docsteam diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 8f8935b658b..168cb96d4c5 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -c84d44f21189bbe95d5878bfb3c703915da80ee7 +8421e7ceb0d9cb7164eb48f02b6e91a0f6d92e05 diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js index b1bf6ebcb13..b2348cf0bad 100644 --- a/app/assets/javascripts/behaviors/markdown/render_math.js +++ b/app/assets/javascripts/behaviors/markdown/render_math.js @@ -97,7 +97,7 @@ class SafeMathRenderer { <button class="js-lazy-render-math btn gl-alert-action btn-confirm btn-md gl-button">Display anyway</button> </div> </div> - <button type="button" class="close" data-dismiss="alert" aria-label="Close"> + <button type="button" class="close js-close" aria-label="Close"> ${spriteIcon('close', 's16')} </button> </div> @@ -184,17 +184,24 @@ class SafeMathRenderer { attachEvents() { document.body.addEventListener('click', (event) => { - if (!event.target.classList.contains('js-lazy-render-math')) { + const alert = event.target.closest('.js-lazy-render-math-container'); + + if (!alert) { return; } - const parent = event.target.closest('.js-lazy-render-math-container'); - - const pre = parent.nextElementSibling; - - parent.remove(); + // Handle alert close + if (event.target.closest('.js-close')) { + alert.remove(); + return; + } - this.renderElement(pre); + // Handle "render anyway" + if (event.target.classList.contains('js-lazy-render-math')) { + const pre = alert.nextElementSibling; + alert.remove(); + this.renderElement(pre); + } }); } } diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue index 16034cce381..59a22f90050 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_modal.vue @@ -450,7 +450,9 @@ export default { data-testid="aws-guidance-tip" @dismiss="dismissTip" > - <div class="gl-display-flex gl-flex-direction-row gl-md-flex-wrap-nowraps gl-gap-3"> + <div + class="gl-display-flex gl-flex-direction-row gl-flex-wrap gl-md-flex-wrap-nowrap gl-gap-3" + > <div> <p> <gl-sprintf :message="$options.i18n.awsTipMessage"> diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js index e5e23f2fb5e..17c9f55a8a0 100644 --- a/app/assets/javascripts/commons/bootstrap.js +++ b/app/assets/javascripts/commons/bootstrap.js @@ -1,10 +1,6 @@ import $ from 'jquery'; // bootstrap jQuery plugins -import 'bootstrap/js/dist/alert'; -import 'bootstrap/js/dist/button'; -import 'bootstrap/js/dist/collapse'; -import 'bootstrap/js/dist/modal'; import 'bootstrap/js/dist/dropdown'; import 'bootstrap/js/dist/tab'; diff --git a/app/assets/javascripts/ide/lib/languages/codeowners.js b/app/assets/javascripts/ide/lib/languages/codeowners.js new file mode 100644 index 00000000000..9c64fa7e550 --- /dev/null +++ b/app/assets/javascripts/ide/lib/languages/codeowners.js @@ -0,0 +1,33 @@ +const conf = { + comments: { + lineComment: '#', + }, + autoClosingPairs: [{ open: '[', close: ']' }], + surroundingPairs: [{ open: '[', close: ']' }], +}; + +const language = { + tokenizer: { + root: [ + // comment + [/^#.*$/, 'comment'], + + // section + [/^\^\[[\s\S]+\]$/, 'namespace'], + + // pattern + [/^\s*(\S+)/, 'regexp'], + + // owner + [/\S*@.*$/, 'variable.value'], + ], + }, +}; + +export default { + id: 'codeowners', + extensions: ['codeowners'], + aliases: ['CODEOWNERS'], + conf, + language, +}; diff --git a/app/assets/javascripts/ide/lib/languages/index.js b/app/assets/javascripts/ide/lib/languages/index.js index f758cb7dd86..c2ab954eb73 100644 --- a/app/assets/javascripts/ide/lib/languages/index.js +++ b/app/assets/javascripts/ide/lib/languages/index.js @@ -1,6 +1,7 @@ import hcl from './hcl'; import vue from './vue'; +import codeowners from './codeowners'; -const languages = [vue, hcl]; +const languages = [vue, hcl, codeowners]; export default languages; diff --git a/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/app/assets/javascripts/pages/projects/settings/repository/show/index.js index d2263fa815d..087808c33da 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/repository/show/index.js @@ -1,3 +1,4 @@ +import 'bootstrap/js/dist/collapse'; import MirrorRepos from '~/mirrors/mirror_repos'; import mountBranchRules from '~/projects/settings/repository/branch_rules/mount_branch_rules'; import mountDefaultBranchSelector from '~/projects/settings/mount_default_branch_selector'; diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js index 050b004f657..107bfd159dd 100644 --- a/app/assets/javascripts/profile/gl_crop.js +++ b/app/assets/javascripts/profile/gl_crop.js @@ -3,6 +3,8 @@ import $ from 'jquery'; import 'cropper'; import { isString } from 'lodash'; +import { s__ } from '~/locale'; +import { createAlert } from '~/alert'; import { loadCSSFile } from '../lib/utils/css_utils'; (() => { @@ -139,11 +141,20 @@ import { loadCSSFile } from '../lib/utils/css_utils'; } readFile(input) { - const _this = this; const reader = new FileReader(); reader.onload = () => { - _this.modalCropImg.attr('src', reader.result); - return _this.modalCrop.modal('show'); + this.modalCropImg.attr('src', reader.result); + import(/* webpackChunkName: 'bootstrapModal' */ 'bootstrap/js/dist/modal') + .then(() => { + this.modalCrop.modal('show'); + }) + .catch(() => { + createAlert({ + message: s__( + 'UserProfile|Failed to set avatar. Please reload the page to try again.', + ), + }); + }); }; return reader.readAsDataURL(input.files[0]); } diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 2d78ab82b7d..3f6c4b15b8e 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -1,23 +1,4 @@ -.integration-settings-form { - .card.card-body, - .info-well { - padding: $gl-padding / 2; - box-shadow: none; - } - - .svg-container { - max-width: 150px; - } -} - .visibility-level-setting { - .option-title { - font-weight: $gl-font-weight-normal; - display: inline-block; - color: var(--gl-text-color, $gl-text-color); - vertical-align: top; - } - .option-description, .option-disabled-reason { color: var(--gray-700, $gray-700); @@ -69,30 +50,16 @@ } } -.push-pull-table { - margin-top: 1em; -} - .ci-variable-table, .deploy-freeze-table, .ci-secure-files-table { table { - thead { - border-bottom: 1px solid var(--gray-50, $gray-50); - } - tr { td, th { padding-left: 0; } - th { - background-color: transparent; - font-weight: $gl-font-weight-bold; - border: 0; - } - // When tables are "stacked", restore td padding @media(max-width: map-get($grid-breakpoints, lg)) { td { diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2b0c79aab87..1f65e283b49 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -682,7 +682,7 @@ module Ci # rubocop: enable CodeReuse/ServiceClass def lazy_ref_commit - BatchLoader.for(ref).batch do |refs, loader| + BatchLoader.for(ref).batch(key: project.id) do |refs, loader| next unless project.repository_exists? project.repository.list_commits_by_ref_name(refs).then do |commits| diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb index 2c41b4a9d2e..3e6ec0b6f29 100644 --- a/app/validators/addressable_url_validator.rb +++ b/app/validators/addressable_url_validator.rb @@ -17,8 +17,6 @@ # validates :ftp_url, addressable_url: { schemes: %w(ftp) } # # validates :git_url, addressable_url: { schemes: %w(http https ssh git) } -# -# validates :smtp_adderss, addressable_url: { schemes: :none } # end # # This validator can also block urls pointing to localhost or the local network to @@ -27,7 +25,7 @@ # Configuration options: # * <tt>message</tt> - A custom error message, used when the URL is blank. (default is: "must be a valid URL"). # * <tt>blocked_message</tt> - A custom error message, used when the URL is blocked. Default: +'is blocked: %{exception_message}'+. -# * <tt>schemes</tt> - Array of URI schemes or `:none`. Default: +['http', 'https']+ +# * <tt>schemes</tt> - Array of URI schemes. Default: +['http', 'https']+ # * <tt>allow_localhost</tt> - Allow urls pointing to +localhost+. Default: +true+ # * <tt>allow_local_network</tt> - Allow urls pointing to private network addresses. Default: +true+ # * <tt>allow_blank</tt> - Allow urls to be +blank+. Default: +false+ diff --git a/app/views/projects/mirrors/_mirror_repos_list.html.haml b/app/views/projects/mirrors/_mirror_repos_list.html.haml index 5dbbb72db56..b06aca063fd 100644 --- a/app/views/projects/mirrors/_mirror_repos_list.html.haml +++ b/app/views/projects/mirrors/_mirror_repos_list.html.haml @@ -10,7 +10,7 @@ - c.body do = _('There are currently no mirrored repositories.') - else - %table.table.push-pull-table + %table.table.gl-table.gl-mt-5 %thead %tr %th diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml index a7875f9b089..ce82a5e1614 100644 --- a/app/views/users/_overview.html.haml +++ b/app/views/users/_overview.html.haml @@ -14,9 +14,10 @@ .gl-display-flex %ol.breadcrumb.gl-breadcrumb-list.gl-mb-4 %li.breadcrumb-item.gl-breadcrumb-item - = link_to @user.username, project_path(@user.user_project) - %span.gl-breadcrumb-separator - = sprite_icon("chevron-right", size: 16) + = link_to project_path(@user.user_project) do + = @user.username + %span.gl-breadcrumb-separator + = sprite_icon("chevron-right", size: 16) %li.breadcrumb-item.gl-breadcrumb-item = link_to @user.user_readme.path, @user.user_project.readme_url - if current_user == @user diff --git a/config/webpack.vendor.config.js b/config/webpack.vendor.config.js index b05e5a7cdef..e02448b59af 100644 --- a/config/webpack.vendor.config.js +++ b/config/webpack.vendor.config.js @@ -39,7 +39,6 @@ module.exports = { 'three', 'moment-mini', 'dompurify', - 'bootstrap/dist/js/bootstrap.js', 'sortablejs/modular/sortable.esm.js', 'popper.js', '@apollo/client/core', diff --git a/db/post_migrate/20230301020246_ensure_mr_user_mentions_note_id_bigint_backfill_is_finished_for_gitlab_dot_com.rb b/db/post_migrate/20230301020246_ensure_mr_user_mentions_note_id_bigint_backfill_is_finished_for_gitlab_dot_com.rb new file mode 100644 index 00000000000..90941af4eb2 --- /dev/null +++ b/db/post_migrate/20230301020246_ensure_mr_user_mentions_note_id_bigint_backfill_is_finished_for_gitlab_dot_com.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class EnsureMrUserMentionsNoteIdBigintBackfillIsFinishedForGitlabDotCom < Gitlab::Database::Migration[2.1] + include Gitlab::Database::MigrationHelpers::ConvertToBigint + + restrict_gitlab_migration gitlab_schema: :gitlab_main + disable_ddl_transaction! + + def up + return unless should_run? + + ensure_batched_background_migration_is_finished( + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: 'merge_request_user_mentions', + column_name: 'id', + job_arguments: [['note_id'], ['note_id_convert_to_bigint']] + ) + end + + def down + # no-op + end + + private + + def should_run? + com_or_dev_or_test_but_not_jh? + end +end diff --git a/db/post_migrate/20230301020356_swap_merge_request_user_mentions_note_id_to_bigint.rb b/db/post_migrate/20230301020356_swap_merge_request_user_mentions_note_id_to_bigint.rb new file mode 100644 index 00000000000..11468a5844e --- /dev/null +++ b/db/post_migrate/20230301020356_swap_merge_request_user_mentions_note_id_to_bigint.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +class SwapMergeRequestUserMentionsNoteIdToBigint < Gitlab::Database::Migration[2.1] + include Gitlab::Database::MigrationHelpers::ConvertToBigint + + disable_ddl_transaction! + + TABLE_NAME = 'merge_request_user_mentions' + + def up + return unless should_run? + + swap + end + + def down + return unless should_run? + + swap + + add_concurrent_index TABLE_NAME, :note_id_convert_to_bigint, unique: true, + name: 'index_merge_request_user_mentions_note_id_convert_to_bigint', + where: 'note_id_convert_to_bigint IS NOT NULL' + + add_concurrent_foreign_key TABLE_NAME, :notes, column: :note_id_convert_to_bigint, + name: 'fk_merge_request_user_mentions_note_id_convert_to_bigint', + on_delete: :cascade, + validate: false + end + + def swap + # This will replace the existing index_merge_request_user_mentions_on_note_id + add_concurrent_index TABLE_NAME, :note_id_convert_to_bigint, unique: true, + name: 'index_merge_request_user_mentions_note_id_convert_to_bigint', + where: 'note_id_convert_to_bigint IS NOT NULL' + + # This will replace the existing merge_request_user_mentions_on_mr_id_and_note_id_index + add_concurrent_index TABLE_NAME, [:merge_request_id, :note_id_convert_to_bigint], unique: true, + name: 'mr_user_mentions_on_mr_id_and_note_id_convert_to_bigint_index' + + # This will replace the existing merge_request_user_mentions_on_mr_id_index + add_concurrent_index TABLE_NAME, :merge_request_id, unique: true, + name: 'merge_request_user_mentions_on_mr_id_index_convert_to_bigint', + where: 'note_id_convert_to_bigint IS NULL' + + # This will replace the existing fk_rails_c440b9ea31 + add_concurrent_foreign_key TABLE_NAME, :notes, column: :note_id_convert_to_bigint, + name: 'fk_merge_request_user_mentions_note_id_convert_to_bigint', + on_delete: :cascade + + with_lock_retries(raise_on_exhaustion: true) do + execute "LOCK TABLE notes, #{TABLE_NAME} IN ACCESS EXCLUSIVE MODE" + + execute "ALTER TABLE #{TABLE_NAME} RENAME COLUMN note_id TO note_id_tmp" + execute "ALTER TABLE #{TABLE_NAME} RENAME COLUMN note_id_convert_to_bigint TO note_id" + execute "ALTER TABLE #{TABLE_NAME} RENAME COLUMN note_id_tmp TO note_id_convert_to_bigint" + + function_name = Gitlab::Database::UnidirectionalCopyTrigger + .on_table(TABLE_NAME, connection: connection) + .name(:note_id, :note_id_convert_to_bigint) + execute "ALTER FUNCTION #{quote_table_name(function_name)} RESET ALL" + + execute 'DROP INDEX IF EXISTS index_merge_request_user_mentions_on_note_id' + rename_index TABLE_NAME, 'index_merge_request_user_mentions_note_id_convert_to_bigint', + 'index_merge_request_user_mentions_on_note_id' + + execute 'DROP INDEX IF EXISTS merge_request_user_mentions_on_mr_id_and_note_id_index' + rename_index TABLE_NAME, 'mr_user_mentions_on_mr_id_and_note_id_convert_to_bigint_index', + 'merge_request_user_mentions_on_mr_id_and_note_id_index' + + execute 'DROP INDEX IF EXISTS merge_request_user_mentions_on_mr_id_index' + rename_index TABLE_NAME, 'merge_request_user_mentions_on_mr_id_index_convert_to_bigint', + 'merge_request_user_mentions_on_mr_id_index' + + execute "ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT IF EXISTS fk_rails_c440b9ea31" + rename_constraint(TABLE_NAME, 'fk_merge_request_user_mentions_note_id_convert_to_bigint', 'fk_rails_c440b9ea31') + end + end + + def should_run? + com_or_dev_or_test_but_not_jh? + end +end diff --git a/db/schema_migrations/20230301020246 b/db/schema_migrations/20230301020246 new file mode 100644 index 00000000000..678aa0c10ee --- /dev/null +++ b/db/schema_migrations/20230301020246 @@ -0,0 +1 @@ +750ba0f9e60a72a8f8b2901d55d3fc763fe24ac2e8b6422b833d529230ec3a7f
\ No newline at end of file diff --git a/db/schema_migrations/20230301020356 b/db/schema_migrations/20230301020356 new file mode 100644 index 00000000000..d9feebb01c6 --- /dev/null +++ b/db/schema_migrations/20230301020356 @@ -0,0 +1 @@ +8a2a061050e8c2209e70498c6d1ebc68f557643565bbf93852ab6599ff819aa7
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index effbb09213e..527f20b0c99 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -18125,11 +18125,11 @@ ALTER SEQUENCE merge_request_reviewers_id_seq OWNED BY merge_request_reviewers.i CREATE TABLE merge_request_user_mentions ( id bigint NOT NULL, merge_request_id integer NOT NULL, - note_id integer, + note_id_convert_to_bigint integer, mentioned_users_ids integer[], mentioned_projects_ids integer[], mentioned_groups_ids integer[], - note_id_convert_to_bigint bigint + note_id bigint ); CREATE SEQUENCE merge_request_user_mentions_id_seq @@ -30888,8 +30888,6 @@ CREATE UNIQUE INDEX index_merge_request_reviewers_on_merge_request_id_and_user_i CREATE INDEX index_merge_request_reviewers_on_user_id ON merge_request_reviewers USING btree (user_id); -CREATE UNIQUE INDEX index_merge_request_user_mentions_note_id_convert_to_bigint ON merge_request_user_mentions USING btree (note_id_convert_to_bigint) WHERE (note_id_convert_to_bigint IS NOT NULL); - CREATE UNIQUE INDEX index_merge_request_user_mentions_on_note_id ON merge_request_user_mentions USING btree (note_id) WHERE (note_id IS NOT NULL); CREATE INDEX index_merge_requests_closing_issues_on_issue_id ON merge_requests_closing_issues USING btree (issue_id); @@ -35057,9 +35055,6 @@ ALTER TABLE ONLY issues ALTER TABLE ONLY geo_event_log ADD CONSTRAINT fk_geo_event_log_on_geo_event_id FOREIGN KEY (geo_event_id) REFERENCES geo_events(id) ON DELETE CASCADE; -ALTER TABLE ONLY merge_request_user_mentions - ADD CONSTRAINT fk_merge_request_user_mentions_note_id_convert_to_bigint FOREIGN KEY (note_id_convert_to_bigint) REFERENCES notes(id) ON DELETE CASCADE NOT VALID; - ALTER TABLE ONLY path_locks ADD CONSTRAINT fk_path_locks_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; diff --git a/doc/administration/postgresql/replication_and_failover.md b/doc/administration/postgresql/replication_and_failover.md index 46890b0b2ca..8ac3ac40a75 100644 --- a/doc/administration/postgresql/replication_and_failover.md +++ b/doc/administration/postgresql/replication_and_failover.md @@ -1255,8 +1255,6 @@ To do the switch on **all** PgBouncer nodes: ``` 1. Run `gitlab-ctl reconfigure`. -1. You must also run `rm /var/opt/gitlab/consul/config.d/watcher_postgresql.json`. - This is a [known issue](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/7293). #### Clean up diff --git a/doc/api/jobs.md b/doc/api/jobs.md index 2d65ea82edd..19d28420069 100644 --- a/doc/api/jobs.md +++ b/doc/api/jobs.md @@ -331,9 +331,9 @@ In earlier GitLab versions, jobs are sorted by ID in ascending order (oldest fir In GitLab 13.9 and later, this endpoint can include retried jobs in the response with `include_retried` set to `true`. -## List pipeline bridges +## List pipeline trigger jobs -Get a list of bridge jobs for a pipeline. +Get a list of trigger jobs for a pipeline. ```plaintext GET /projects/:id/pipelines/:pipeline_id/bridges diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 6ba9d455278..9730b575fee 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -2613,9 +2613,9 @@ In this example: #### `needs:pipeline` -You can mirror the pipeline status from an upstream pipeline to a bridge job by +You can mirror the pipeline status from an upstream pipeline to a job by using the `needs:pipeline` keyword. The latest pipeline status from the default branch is -replicated to the bridge job. +replicated to the job. **Keyword type**: Job keyword. You can use it only as part of a job. @@ -2628,7 +2628,7 @@ replicated to the bridge job. **Example of `needs:pipeline`**: ```yaml -upstream_bridge: +upstream_status: stage: test needs: pipeline: other/project diff --git a/doc/development/database/table_partitioning.md b/doc/development/database/table_partitioning.md index 0d5e3c233f6..2ff7ee74f8d 100644 --- a/doc/development/database/table_partitioning.md +++ b/doc/development/database/table_partitioning.md @@ -6,6 +6,13 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Database table partitioning +WARNING: +If you have questions not answered below, check for and add them +to [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/398650). +Tag `@gitlab-org/database-team/triage` and we'll get back to you with an +answer as soon as possible. If you get an answer in Slack, document +it on the issue as well so we can update this document in the future. + Table partitioning is a powerful database feature that allows a table's data to be split into smaller physical tables that act as a single large table. If the application is designed to work with partitioning in mind, diff --git a/doc/user/analytics/dora_metrics.md b/doc/user/analytics/dora_metrics.md index 0fdb4ea6948..bdbfe5072d2 100644 --- a/doc/user/analytics/dora_metrics.md +++ b/doc/user/analytics/dora_metrics.md @@ -15,19 +15,14 @@ Using these metrics helps improve DevOps efficiency and communicate performance DORA includes four key metrics, divided into two core areas of DevOps: -- [Deployment Frequency](#deployment-frequency) and [Lead Time for Change](#lead-time-for-changes) measure team velocity. -- [Change Failure Rate](#change-failure-rate) and [Time to Restore Service](#time-to-restore-service) measure stability. +- [Deployment frequency](#deployment-frequency) and [Lead time for changes](#lead-time-for-changes) measure team velocity. +- [Change failure rate](#change-failure-rate) and [Time to restore service](#time-to-restore-service) measure stability. For software leaders, tracking velocity alongside quality metrics ensures they're not sacrificing quality for speed. -<div class="video-fallback"> - For an overview, see <a href="https://www.youtube.com/watch?v=1BrcMV6rCDw">GitLab Speed Run: DORA metrics in GitLab One DevOps Platform</a>. -</div> -<figure class="video-container"> - <iframe src="https://www.youtube-nocookie.com/embed/1BrcMV6rCDw" frameborder="0" allowfullscreen> </iframe> -</figure> +For a video explanation, see [DORA metrics: User analytics](https://www.youtube.com/watch?v=lM_FbVYuN8s) and [GitLab speed run: DORA metrics](https://www.youtube.com/watch?v=1BrcMV6rCDw). -## DORA Metrics dashboard in Value Stream Analytics +## DORA metrics dashboard in Value Stream Analytics The four DORA metrics are available out-of-the-box in the [Value Stream Analytics (VSA) overview dashboard](../group/value_stream_analytics/index.md#view-value-stream-analytics). This helps you visualize the engineering work in the context of end-to-end value delivery. @@ -158,6 +153,21 @@ These deployment records are not created for pull-based deployments, for example To track DORA metrics in these cases, you can [create a deployment record](../../api/deployments.md#create-a-deployment) using the Deployments API. See also the documentation page for [Track deployments of an external deployment tool](../../ci/environments/external_deployment_tools.md). +### Measure DORA Time to restore service and Change failure rate with external incidents + +[Time to restore service](#time-to-restore-service) and [Change failure rate](#change-failure-rate) +require [GitLab incidents](../../operations/incident_management/manage_incidents.md) to calculate the metrics. + +For PagerDuty, you can set up a [webhook to automatically create a GitLab incident for each PagerDuty incident](../../operations/incident_management/manage_incidents.md#using-the-pagerduty-webhook). +This configuration requires you to make changes in both PagerDuty and GitLab. + +For others incident management tools, you can set up the +[HTTP integration](../../operations/incident_management/integrations.md#http-endpoints), +and use it to automatically: + +1. [Create an incident when an alert is triggered](../../operations/incident_management/manage_incidents.md#automatically-when-an-alert-is-triggered). +1. [Close incidents via recovery alerts](../../operations/incident_management/manage_incidents.md#automatically-close-incidents-via-recovery-alerts). + ### Supported DORA metrics in GitLab | Metric | Level | API | UI chart | Comments | diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c32f61c6704..b2e144ee165 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -708,7 +708,7 @@ module API requires :group_access, type: Integer, values: Gitlab::Access.values, as: :link_group_access, desc: 'The group access level' optional :expires_at, type: Date, desc: 'Share expiration date' end - post ":id/share", feature_category: :system_access do + post ":id/share", feature_category: :projects do authorize! :admin_project, user_project shared_with_group = Group.find_by_id(params[:group_id]) @@ -738,7 +738,7 @@ module API requires :group_id, type: Integer, desc: 'The ID of the group' end # rubocop: disable CodeReuse/ActiveRecord - delete ":id/share/:group_id", feature_category: :system_access do + delete ":id/share/:group_id", feature_category: :projects do authorize! :admin_project, user_project link = user_project.project_group_links.find_by(group_id: params[:group_id]) diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb index ba36b332e5a..938ca3ca317 100644 --- a/lib/gitlab/url_blocker.rb +++ b/lib/gitlab/url_blocker.rb @@ -12,7 +12,6 @@ module Gitlab class << self # Validates the given url according to the constraints specified by arguments. # - # schemes - Array of URI schemes or `:none` if scheme must not be set. # ports - Raises error if the given URL port is not between given ports. # allow_localhost - Raises error if URL resolves to a localhost IP address and argument is false. # allow_local_network - Raises error if URL resolves to a link-local address and argument is false. @@ -230,12 +229,6 @@ module Gitlab end def validate_scheme(scheme, schemes) - if schemes == :none - return if scheme.nil? - - raise BlockedUrlError, "No scheme allowed but got `#{scheme}://`" - end - if scheme.blank? || (schemes.any? && schemes.exclude?(scheme)) raise BlockedUrlError, "Only allowed schemes are #{schemes.join(', ')}" end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e58bf15ecab..a794c64a788 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -47356,6 +47356,9 @@ msgstr "" msgid "UserProfile|Explore public groups to find projects to contribute to." msgstr "" +msgid "UserProfile|Failed to set avatar. Please reload the page to try again." +msgstr "" + msgid "UserProfile|Followers" msgstr "" diff --git a/package.json b/package.json index a13b2742e21..c6a266ac3d4 100644 --- a/package.json +++ b/package.json @@ -187,13 +187,13 @@ "url-loader": "^4.1.1", "uuid": "8.1.0", "visibilityjs": "^1.2.4", - "vue": "2.6.14", + "vue": "2.7.14", "vue-apollo": "^3.0.7", - "vue-loader": "15.9.6", + "vue-loader": "15.10.1", "vue-observe-visibility": "^1.0.0", "vue-resize": "^1.0.1", "vue-router": "3.4.9", - "vue-template-compiler": "2.6.14", + "vue-template-compiler": "2.7.14", "vue-virtual-scroll-list": "^1.4.7", "vuedraggable": "^2.23.0", "vuex": "^3.6.2", diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 2c0bac9c801..6d775f1ebff 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -371,11 +371,9 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do it_behaves_like 'shows no runners found' - it 'shows active tab' do + it 'shows active tab with no runner' do expect(page).to have_link('Instance', class: 'active') - end - it 'shows no runner' do expect(page).not_to have_content 'runner-project' expect(page).not_to have_content 'runner-group' end @@ -469,10 +467,12 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do it_behaves_like 'shows no runners registered' it 'shows tabs with total counts equal to 0' do - expect(page).to have_link('All 0') - expect(page).to have_link('Instance 0') - expect(page).to have_link('Group 0') - expect(page).to have_link('Project 0') + aggregate_failures do + expect(page).to have_link('All 0') + expect(page).to have_link('Instance 0') + expect(page).to have_link('Group 0') + expect(page).to have_link('Project 0') + end end end @@ -567,11 +567,8 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end end - it 'deletes runner' do + it 'deletes runner and redirects to runner list' do expect(page.find('[data-testid="alert-success"]')).to have_content('deleted') - end - - it 'redirects to runner list' do expect(current_url).to match(admin_runners_path) end end @@ -614,12 +611,9 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do wait_for_requests end - it 'show success alert' do - expect(page.find('[data-testid="alert-success"]')).to have_content('saved') - end - - it 'redirects to runner page' do + it 'show success alert and redirects to runner page' do expect(current_url).to match(admin_runner_path(project_runner)) + expect(page.find('[data-testid="alert-success"]')).to have_content('saved') end end @@ -658,7 +652,7 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end context 'with project runner' do - let(:project_runner) { create(:ci_runner, :project, projects: [project1]) } + let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project1]) } before do visit edit_admin_runner_path(project_runner) @@ -668,7 +662,7 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end context 'with locked runner' do - let(:locked_runner) { create(:ci_runner, :project, projects: [project1], locked: true) } + let_it_be(:locked_runner) { create(:ci_runner, :project, projects: [project1], locked: true) } before do visit edit_admin_runner_path(locked_runner) @@ -679,7 +673,7 @@ RSpec.describe "Admin Runners", feature_category: :runner_fleet do end describe 'disable/destroy' do - let(:runner) { create(:ci_runner, :project, projects: [project1]) } + let_it_be(:runner) { create(:ci_runner, :project, projects: [project1]) } before do visit edit_admin_runner_path(runner) diff --git a/spec/features/groups/group_runners_spec.rb b/spec/features/groups/group_runners_spec.rb index 171e4025f49..3c4b8a01c13 100644 --- a/spec/features/groups/group_runners_spec.rb +++ b/spec/features/groups/group_runners_spec.rb @@ -16,9 +16,9 @@ RSpec.describe "Group Runners", feature_category: :runner_fleet do end describe "Group runners page", :js do - let!(:group_registration_token) { group.runners_token } - describe "runners registration" do + let_it_be(:group_registration_token) { group.runners_token } + before do visit group_runners_path(group) end @@ -60,15 +60,11 @@ RSpec.describe "Group Runners", feature_category: :runner_fleet do let(:runner) { group_runner } end - it 'shows a group badge' do - within_runner_row(group_runner.id) do - expect(page).to have_selector '.badge', text: s_('Runners|Group') - end - end - - it 'can edit runner information' do + it 'shows an editable group badge' do within_runner_row(group_runner.id) do expect(find_link('Edit')[:href]).to end_with(edit_group_runner_path(group, group_runner)) + + expect(page).to have_selector '.badge', text: s_('Runners|Group') end end @@ -102,15 +98,11 @@ RSpec.describe "Group Runners", feature_category: :runner_fleet do let(:runner) { project_runner } end - it 'shows a project badge' do - within_runner_row(project_runner.id) do - expect(page).to have_selector '.badge', text: s_('Runners|Project') - end - end - - it 'can edit runner information' do + it 'shows an editable project runner' do within_runner_row(project_runner.id) do expect(find_link('Edit')[:href]).to end_with(edit_group_runner_path(group, project_runner)) + + expect(page).to have_selector '.badge', text: s_('Runners|Project') end end end diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js index 8f3fccc2804..8a5f62008fd 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js @@ -152,7 +152,7 @@ describe('Ci variable modal', () => { findModal().vm.$emit('shown'); }); - it('keeps the value as false', async () => { + it('keeps the value as false', () => { expect( findProtectedVariableCheckbox().attributes('data-is-protected-checked'), ).toBeUndefined(); diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js index 87192006efc..ab5f3711139 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js @@ -70,7 +70,7 @@ describe('Ci Variable Shared Component', () => { const findCiSettings = () => wrapper.findComponent(ciVariableSettings); // eslint-disable-next-line consistent-return - async function createComponentWithApollo({ + function createComponentWithApollo({ customHandlers = null, isLoading = false, props = { ...createProjectProps() }, @@ -409,7 +409,7 @@ describe('Ci Variable Shared Component', () => { describe('queryData', () => { let error; - beforeEach(async () => { + beforeEach(() => { mockVariables.mockResolvedValue(mockGroupVariables); }); @@ -447,7 +447,7 @@ describe('Ci Variable Shared Component', () => { describe('mutationData', () => { let error; - beforeEach(async () => { + beforeEach(() => { mockVariables.mockResolvedValue(mockGroupVariables); }); diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js index 2ef789e89c3..0b28cb06cec 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js @@ -82,11 +82,11 @@ describe('Ci variable table', () => { expect(findRevealButton().exists()).toBe(true); }); - it('displays the correct amount of variables', async () => { + it('displays the correct amount of variables', () => { expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(defaultProps.variables.length); }); - it('displays the correct variable options', async () => { + it('displays the correct variable options', () => { expect(findOptionsValues(0)).toBe('Protected, Expanded'); expect(findOptionsValues(1)).toBe('Masked'); }); diff --git a/spec/frontend/ci/pipeline_editor/components/commit/commit_form_spec.js b/spec/frontend/ci/pipeline_editor/components/commit/commit_form_spec.js index b2dfa900b1d..03f346181e4 100644 --- a/spec/frontend/ci/pipeline_editor/components/commit/commit_form_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/commit/commit_form_spec.js @@ -34,7 +34,7 @@ describe('Pipeline Editor | Commit Form', () => { const findCancelBtn = () => wrapper.find('[type="reset"]'); describe('when the form is displayed', () => { - beforeEach(async () => { + beforeEach(() => { createComponent(); }); @@ -57,7 +57,7 @@ describe('Pipeline Editor | Commit Form', () => { }); describe('when buttons are clicked', () => { - beforeEach(async () => { + beforeEach(() => { createComponent({}, mount); }); diff --git a/spec/frontend/ci/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js b/spec/frontend/ci/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js index 5399924b462..0296ab5a65c 100644 --- a/spec/frontend/ci/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js @@ -68,7 +68,7 @@ describe('Pipeline config reference card', () => { }); }; - it('tracks help page links', async () => { + it('tracks help page links', () => { const { CI_EXAMPLES_LINK, CI_HELP_LINK, diff --git a/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js b/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js index 560e8840d57..2861fc35342 100644 --- a/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js @@ -58,7 +58,7 @@ describe('CI Editor Header', () => { expect(findLinkBtn().props('icon')).toBe('external-link'); }); - it('tracks the click on the browse button', async () => { + it('tracks the click on the browse button', () => { const { browseTemplates } = pipelineEditorTrackingOptions.actions; testTracker(findLinkBtn(), browseTemplates); @@ -91,7 +91,7 @@ describe('CI Editor Header', () => { expect(wrapper.emitted('open-drawer')).toHaveLength(1); }); - it('tracks open help drawer action', async () => { + it('tracks open help drawer action', () => { const { actions } = pipelineEditorTrackingOptions; testTracker(findHelpBtn(), actions.openHelpDrawer); diff --git a/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js b/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js index bf14f4c4cd6..3a99949413b 100644 --- a/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js @@ -288,7 +288,7 @@ describe('Pipeline editor branch switcher', () => { }); describe('with a search term', () => { - beforeEach(async () => { + beforeEach(() => { mockAvailableBranchQuery.mockResolvedValue(mockSearchBranches); }); diff --git a/spec/frontend/ci/pipeline_editor/components/file-tree/container_spec.js b/spec/frontend/ci/pipeline_editor/components/file-tree/container_spec.js index 306dd78d395..f2effcb2966 100644 --- a/spec/frontend/ci/pipeline_editor/components/file-tree/container_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/file-tree/container_spec.js @@ -60,11 +60,11 @@ describe('Pipeline editor file nav', () => { expect(fileTreeItems().exists()).toBe(false); }); - it('renders alert tip', async () => { + it('renders alert tip', () => { expect(findTip().exists()).toBe(true); }); - it('renders learn more link', async () => { + it('renders learn more link', () => { expect(findTip().props('secondaryButtonLink')).toBe(mockIncludesHelpPagePath); }); @@ -87,7 +87,7 @@ describe('Pipeline editor file nav', () => { }); }); - it('does not render alert tip', async () => { + it('does not render alert tip', () => { expect(findTip().exists()).toBe(false); }); }); diff --git a/spec/frontend/ci/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js b/spec/frontend/ci/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js index 7bf955012c7..b8526e569ec 100644 --- a/spec/frontend/ci/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js @@ -96,7 +96,7 @@ describe('Pipeline Status', () => { await waitForPromises(); }); - it('should emit an error event when query fails', async () => { + it('should emit an error event when query fails', () => { expect(wrapper.emitted('showError')).toHaveLength(1); expect(wrapper.emitted('showError')[0]).toEqual([ { diff --git a/spec/frontend/ci/pipeline_editor/components/header/pipeline_status_spec.js b/spec/frontend/ci/pipeline_editor/components/header/pipeline_status_spec.js index 3faa2890254..8ca88472bf1 100644 --- a/spec/frontend/ci/pipeline_editor/components/header/pipeline_status_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/header/pipeline_status_spec.js @@ -77,7 +77,7 @@ describe('Pipeline Status', () => { await waitForPromises(); }); - it('query is called with correct variables', async () => { + it('query is called with correct variables', () => { expect(mockPipelineQuery).toHaveBeenCalledTimes(1); expect(mockPipelineQuery).toHaveBeenCalledWith({ fullPath: mockProjectFullPath, diff --git a/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js index 52a543c7686..cbdf01105c7 100644 --- a/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js @@ -314,13 +314,13 @@ describe('Pipeline editor tabs component', () => { createComponent(); }); - it('shows walkthrough popover', async () => { + it('shows walkthrough popover', () => { expect(findWalkthroughPopover().exists()).toBe(true); }); }); describe('when isNewCiConfigFile prop is false', () => { - it('does not show walkthrough popover', async () => { + it('does not show walkthrough popover', () => { createComponent({ props: { isNewCiConfigFile: false } }); expect(findWalkthroughPopover().exists()).toBe(false); }); diff --git a/spec/frontend/ci/pipeline_editor/components/popovers/file_tree_popover_spec.js b/spec/frontend/ci/pipeline_editor/components/popovers/file_tree_popover_spec.js index a9aabb103f2..3d84f06967a 100644 --- a/spec/frontend/ci/pipeline_editor/components/popovers/file_tree_popover_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/popovers/file_tree_popover_spec.js @@ -25,7 +25,7 @@ describe('FileTreePopover component', () => { }); describe('default', () => { - beforeEach(async () => { + beforeEach(() => { createComponent({ stubs: { GlSprintf } }); }); @@ -45,7 +45,7 @@ describe('FileTreePopover component', () => { }); describe('when popover has already been dismissed before', () => { - it('does not render popover', async () => { + it('does not render popover', () => { localStorage.setItem(FILE_TREE_POPOVER_DISMISSED_KEY, 'true'); createComponent(); diff --git a/spec/frontend/ci/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js b/spec/frontend/ci/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js index 23f9c7a87ee..18eec48ad83 100644 --- a/spec/frontend/ci/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js @@ -20,7 +20,7 @@ describe('ValidatePopover component', () => { const findFeedbackLink = () => wrapper.findByTestId('feedback-link'); describe('template', () => { - beforeEach(async () => { + beforeEach(() => { createComponent({ stubs: { GlLink, GlSprintf }, }); diff --git a/spec/frontend/ci/pipeline_editor/components/popovers/walkthrough_popover_spec.js b/spec/frontend/ci/pipeline_editor/components/popovers/walkthrough_popover_spec.js index 186fd803d47..37339b1c422 100644 --- a/spec/frontend/ci/pipeline_editor/components/popovers/walkthrough_popover_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/popovers/walkthrough_popover_spec.js @@ -18,7 +18,7 @@ describe('WalkthroughPopover component', () => { await wrapper.findByTestId('ctaBtn').trigger('click'); }); - it('emits "walkthrough-popover-cta-clicked" event', async () => { + it('emits "walkthrough-popover-cta-clicked" event', () => { expect(wrapper.emitted()['walkthrough-popover-cta-clicked']).toHaveLength(1); }); }); diff --git a/spec/frontend/ci/pipeline_editor/components/ui/editor_tab_spec.js b/spec/frontend/ci/pipeline_editor/components/ui/editor_tab_spec.js index a4e7abba7b0..f02b1f5efbc 100644 --- a/spec/frontend/ci/pipeline_editor/components/ui/editor_tab_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/ui/editor_tab_spec.js @@ -64,7 +64,7 @@ describe('~/ci/pipeline_editor/components/ui/editor_tab.vue', () => { mockChildMounted = jest.fn(); }); - it('tabs are mounted lazily', async () => { + it('tabs are mounted lazily', () => { createMockedWrapper(); expect(mockChildMounted).toHaveBeenCalledTimes(0); @@ -192,7 +192,7 @@ describe('~/ci/pipeline_editor/components/ui/editor_tab.vue', () => { createMockedWrapper(); }); - it('renders correct number of badges', async () => { + it('renders correct number of badges', () => { expect(findBadges()).toHaveLength(1); expect(findBadges().at(0).text()).toBe('NEW'); }); diff --git a/spec/frontend/ci/pipeline_editor/graphql/resolvers_spec.js b/spec/frontend/ci/pipeline_editor/graphql/resolvers_spec.js index 6a6cc3a14de..893f6775ac5 100644 --- a/spec/frontend/ci/pipeline_editor/graphql/resolvers_spec.js +++ b/spec/frontend/ci/pipeline_editor/graphql/resolvers_spec.js @@ -34,7 +34,7 @@ describe('~/ci/pipeline_editor/graphql/resolvers', () => { }); /* eslint-disable no-underscore-dangle */ - it('lint data has correct type names', async () => { + it('lint data has correct type names', () => { expect(result.__typename).toBe('CiLintContent'); expect(result.jobs[0].__typename).toBe('CiLintJob'); diff --git a/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js index d22a1832343..8bac46a3e9c 100644 --- a/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js +++ b/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js @@ -96,7 +96,7 @@ describe('Pipeline editor app component', () => { }); }; - const createComponentWithApollo = async ({ + const createComponentWithApollo = ({ provide = {}, stubs = {}, withUndefinedBranch = false, @@ -260,7 +260,7 @@ describe('Pipeline editor app component', () => { expect(findAlert().exists()).toBe(false); }); - it('ci config query is called with correct variables', async () => { + it('ci config query is called with correct variables', () => { expect(mockCiConfigData).toHaveBeenCalledWith({ content: mockCiYml, projectPath: mockProjectFullPath, @@ -287,7 +287,7 @@ describe('Pipeline editor app component', () => { .mockImplementation(jest.fn()); }); - it('shows an empty state and does not show editor home component', async () => { + it('shows an empty state and does not show editor home component', () => { expect(findEmptyState().exists()).toBe(true); expect(findAlert().exists()).toBe(false); expect(findEditorHome().exists()).toBe(false); diff --git a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js index 1349461d8bc..9015031b6c8 100644 --- a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js +++ b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js @@ -142,7 +142,7 @@ describe('Pipeline New Form', () => { await waitForPromises(); }); - it('displays the correct values for the provided query params', async () => { + it('displays the correct values for the provided query params', () => { expect(findVariableTypes().at(0).props('text')).toBe('Variable'); expect(findVariableTypes().at(1).props('text')).toBe('File'); expect(findRefsDropdown().props('value')).toEqual({ shortName: 'tag-1' }); @@ -154,7 +154,7 @@ describe('Pipeline New Form', () => { expect(findValueInputs().at(0).element.value).toBe('test_var_val'); }); - it('displays an empty variable for the user to fill out', async () => { + it('displays an empty variable for the user to fill out', () => { expect(findKeyInputs().at(2).element.value).toBe(''); expect(findValueInputs().at(2).element.value).toBe(''); expect(findVariableTypes().at(2).props('text')).toBe('Variable'); @@ -186,12 +186,12 @@ describe('Pipeline New Form', () => { }); describe('Pipeline creation', () => { - beforeEach(async () => { + beforeEach(() => { mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse); mock.onPost(pipelinesPath).reply(HTTP_STATUS_OK, newPipelinePostResponse); }); - it('does not submit the native HTML form', async () => { + it('does not submit the native HTML form', () => { createComponentWithApollo(); findForm().vm.$emit('submit', dummySubmitEvent); @@ -328,7 +328,7 @@ describe('Pipeline New Form', () => { }); const testBehaviorWhenCacheIsPopulated = (queryResponse) => { - beforeEach(async () => { + beforeEach(() => { mockCiConfigVariables.mockResolvedValue(queryResponse); createComponentWithApollo({ method: mountExtended }); }); @@ -406,7 +406,7 @@ describe('Pipeline New Form', () => { await waitForPromises(); }); - it('displays all the variables', async () => { + it('displays all the variables', () => { expect(findVariableRows()).toHaveLength(6); }); @@ -445,7 +445,7 @@ describe('Pipeline New Form', () => { await waitForPromises(); }); - it('displays variables with description only', async () => { + it('displays variables with description only', () => { expect(findVariableRows()).toHaveLength(2); // extra empty variable is added at the end }); }); diff --git a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js index 60ace483712..82dac1358c5 100644 --- a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js +++ b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js @@ -54,7 +54,7 @@ describe('Pipeline New Form', () => { expect(findRefsDropdownItems()).toHaveLength(0); }); - it('does not make requests immediately', async () => { + it('does not make requests immediately', () => { expect(mock.history.get).toHaveLength(0); }); @@ -117,14 +117,14 @@ describe('Pipeline New Form', () => { await waitForPromises(); }); - it('requests filtered tags and branches', async () => { + it('requests filtered tags and branches', () => { expect(mock.history.get).toHaveLength(2); expect(mock.history.get[1].params).toEqual({ search: mockSearchTerm, }); }); - it('displays dropdown with branches and tags', async () => { + it('displays dropdown with branches and tags', () => { const filteredRefLength = mockFilteredRefs.Tags.length + mockFilteredRefs.Branches.length; expect(findRefsDropdownItems()).toHaveLength(filteredRefLength); diff --git a/spec/frontend/ci/pipeline_schedules/components/delete_pipeline_schedule_modal_spec.js b/spec/frontend/ci/pipeline_schedules/components/delete_pipeline_schedule_modal_spec.js index c45267e5a47..e48f556c246 100644 --- a/spec/frontend/ci/pipeline_schedules/components/delete_pipeline_schedule_modal_spec.js +++ b/spec/frontend/ci/pipeline_schedules/components/delete_pipeline_schedule_modal_spec.js @@ -20,13 +20,13 @@ describe('Delete pipeline schedule modal', () => { createComponent(); }); - it('emits the deleteSchedule event', async () => { + it('emits the deleteSchedule event', () => { findModal().vm.$emit('primary'); expect(wrapper.emitted()).toEqual({ deleteSchedule: [[]] }); }); - it('emits the hideModal event', async () => { + it('emits the hideModal event', () => { findModal().vm.$emit('hide'); expect(wrapper.emitted()).toEqual({ hideModal: [[]] }); diff --git a/spec/frontend/ci/pipeline_schedules/components/take_ownership_modal_spec.js b/spec/frontend/ci/pipeline_schedules/components/take_ownership_modal_spec.js index e3965d13c19..7cc254b7653 100644 --- a/spec/frontend/ci/pipeline_schedules/components/take_ownership_modal_spec.js +++ b/spec/frontend/ci/pipeline_schedules/components/take_ownership_modal_spec.js @@ -26,13 +26,13 @@ describe('Take ownership modal', () => { ); }); - it('emits the takeOwnership event', async () => { + it('emits the takeOwnership event', () => { findModal().vm.$emit('primary'); expect(wrapper.emitted()).toEqual({ takeOwnership: [[]] }); }); - it('emits the hideModal event', async () => { + it('emits the hideModal event', () => { findModal().vm.$emit('hide'); expect(wrapper.emitted()).toEqual({ hideModal: [[]] }); diff --git a/spec/frontend/editor/utils_spec.js b/spec/frontend/editor/utils_spec.js index 13b8a9804b0..c9d6cbcaaa6 100644 --- a/spec/frontend/editor/utils_spec.js +++ b/spec/frontend/editor/utils_spec.js @@ -1,6 +1,8 @@ import { editor as monacoEditor } from 'monaco-editor'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import * as utils from '~/editor/utils'; +import languages from '~/ide/lib/languages'; +import { registerLanguages } from '~/ide/utils'; import { DEFAULT_THEME } from '~/ide/lib/themes'; describe('Source Editor utils', () => { @@ -53,13 +55,19 @@ describe('Source Editor utils', () => { }); describe('getBlobLanguage', () => { + beforeEach(() => { + registerLanguages(...languages); + }); + it.each` - path | expectedLanguage - ${'foo.js'} | ${'javascript'} - ${'foo.js.rb'} | ${'ruby'} - ${'foo.bar'} | ${'plaintext'} - ${undefined} | ${'plaintext'} - ${'foo/bar/foo.js'} | ${'javascript'} + path | expectedLanguage + ${'foo.js'} | ${'javascript'} + ${'foo.js.rb'} | ${'ruby'} + ${'foo.bar'} | ${'plaintext'} + ${undefined} | ${'plaintext'} + ${'foo/bar/foo.js'} | ${'javascript'} + ${'CODEOWNERS'} | ${'codeowners'} + ${'.gitlab/CODEOWNERS'} | ${'codeowners'} `(`returns '$expectedLanguage' for '$path' path`, ({ path, expectedLanguage }) => { const language = utils.getBlobLanguage(path); diff --git a/spec/frontend/ide/lib/languages/codeowners_spec.js b/spec/frontend/ide/lib/languages/codeowners_spec.js new file mode 100644 index 00000000000..9b204190fdf --- /dev/null +++ b/spec/frontend/ide/lib/languages/codeowners_spec.js @@ -0,0 +1,26 @@ +import { editor } from 'monaco-editor'; +import codeowners from '~/ide/lib/languages/codeowners'; +import { registerLanguages } from '~/ide/utils'; + +describe('tokenization for CODEOWNERS files', () => { + beforeEach(() => { + registerLanguages(codeowners); + }); + + it.each([ + ['## Foo bar comment', [[{ language: 'codeowners', offset: 0, type: 'comment.codeowners' }]]], + [ + '/foo/bar @gsamsa', + [ + [ + { language: 'codeowners', offset: 0, type: 'regexp.codeowners' }, + { language: 'codeowners', offset: 8, type: 'source.codeowners' }, + { language: 'codeowners', offset: 9, type: 'variable.value.codeowners' }, + ], + ], + ], + ['^[Section name]', [[{ language: 'codeowners', offset: 0, type: 'namespace.codeowners' }]]], + ])('%s', (string, tokens) => { + expect(editor.tokenize(string, 'codeowners')).toEqual(tokens); + }); +}); diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index 1a171517b35..f1189361df8 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -366,32 +366,6 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh end end end - - describe 'options' do - describe 'schemes' do - context 'with :none' do - let(:schemes) { :none } - - context 'when URL has no scheme' do - let(:import_url) { '1.1.1.1' } - - it_behaves_like 'validates URI and hostname' do - let(:expected_uri) { import_url } - let(:expected_hostname) { nil } - end - end - - context 'when URL has a scheme' do - let(:import_url) { 'http://1.1.1.1' } - - it 'raises an error' do - expect { subject } - .to raise_error(described_class::BlockedUrlError, "No scheme allowed but got `http://`") - end - end - end - end - end end describe '#blocked_url?' do @@ -420,11 +394,6 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only, feature_category: :sh expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', schemes: ['http'])).to be true end - it 'return true when schemes is :none' do - expect(described_class.blocked_url?('gitlab.com', schemes: :none)).to be false - expect(described_class.blocked_url?('smtp://gitlab.com', schemes: :none)).to be true - end - it 'returns true for bad protocol on configured web/SSH host and ports' do web_url = "javascript://#{Gitlab.host_with_port}/t.git%0aalert(1)" expect(described_class.blocked_url?(web_url, schemes: schemes)).to be true diff --git a/spec/migrations/ensure_mr_user_mentions_note_id_bigint_backfill_is_finished_for_gitlab_dot_com_spec.rb b/spec/migrations/ensure_mr_user_mentions_note_id_bigint_backfill_is_finished_for_gitlab_dot_com_spec.rb new file mode 100644 index 00000000000..af9fc3f3b07 --- /dev/null +++ b/spec/migrations/ensure_mr_user_mentions_note_id_bigint_backfill_is_finished_for_gitlab_dot_com_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe EnsureMrUserMentionsNoteIdBigintBackfillIsFinishedForGitlabDotCom, feature_category: :database do + describe '#up' do + let(:migration_arguments) do + { + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: 'merge_request_user_mentions', + column_name: 'id', + job_arguments: [['note_id'], ['note_id_convert_to_bigint']] + } + end + + it 'ensures the migration is completed for GitLab.com, dev, or test' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + expect(instance).to receive(:ensure_batched_background_migration_is_finished).with(migration_arguments) + end + + migrate! + end + + it 'skips the check for other instances' do + expect_next_instance_of(described_class) do |instance| + expect(instance).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + expect(instance).not_to receive(:ensure_batched_background_migration_is_finished) + end + + migrate! + end + end +end diff --git a/spec/migrations/ensure_timelogs_note_id_bigint_backfill_is_finished_for_gitlab_dot_com_spec.rb b/spec/migrations/ensure_timelogs_note_id_bigint_backfill_is_finished_for_gitlab_dot_com_spec.rb index 9066413ce68..9f733f1e1f4 100644 --- a/spec/migrations/ensure_timelogs_note_id_bigint_backfill_is_finished_for_gitlab_dot_com_spec.rb +++ b/spec/migrations/ensure_timelogs_note_id_bigint_backfill_is_finished_for_gitlab_dot_com_spec.rb @@ -5,8 +5,6 @@ require_migration! RSpec.describe EnsureTimelogsNoteIdBigintBackfillIsFinishedForGitlabDotCom, feature_category: :database do describe '#up' do - using RSpec::Parameterized::TableSyntax - let(:migration_arguments) do { job_class_name: 'CopyColumnUsingBackgroundMigrationJob', diff --git a/spec/migrations/swap_merge_request_user_mentions_note_id_to_bigint_spec.rb b/spec/migrations/swap_merge_request_user_mentions_note_id_to_bigint_spec.rb new file mode 100644 index 00000000000..15b21d34714 --- /dev/null +++ b/spec/migrations/swap_merge_request_user_mentions_note_id_to_bigint_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe SwapMergeRequestUserMentionsNoteIdToBigint, feature_category: :database do + describe '#up' do + before do + # A we call `schema_migrate_down!` before each example, and for this migration + # `#down` is same as `#up`, we need to ensure we start from the expected state. + connection = described_class.new.connection + connection.execute('ALTER TABLE merge_request_user_mentions ALTER COLUMN note_id TYPE integer') + connection.execute('ALTER TABLE merge_request_user_mentions ALTER COLUMN note_id_convert_to_bigint TYPE bigint') + end + + # rubocop: disable RSpec/AnyInstanceOf + it 'swaps the integer and bigint columns for GitLab.com, dev, or test' do + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(true) + + user_mentions = table(:merge_request_user_mentions) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + user_mentions.reset_column_information + + expect(user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to eq('bigint') + } + + migration.after -> { + user_mentions.reset_column_information + + expect(user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('bigint') + expect(user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to eq('integer') + } + end + end + end + + it 'is a no-op for other instances' do + allow_any_instance_of(described_class).to receive(:com_or_dev_or_test_but_not_jh?).and_return(false) + + user_mentions = table(:merge_request_user_mentions) + + disable_migrations_output do + reversible_migration do |migration| + migration.before -> { + user_mentions.reset_column_information + + expect(user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to eq('bigint') + } + + migration.after -> { + user_mentions.reset_column_information + + expect(user_mentions.columns.find { |c| c.name == 'note_id' }.sql_type).to eq('integer') + expect(user_mentions.columns.find { |c| c.name == 'note_id_convert_to_bigint' }.sql_type).to eq('bigint') + } + end + end + end + # rubocop: enable RSpec/AnyInstanceOf + end +end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index d50672da8e5..e532d9c7b7b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2016,6 +2016,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: context 'with non-empty project' do let(:pipeline) do create(:ci_pipeline, + project: project, ref: project.default_branch, sha: project.commit.sha) end @@ -2023,27 +2024,46 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: describe '#lazy_ref_commit' do let(:another) do create(:ci_pipeline, + project: project, ref: 'feature', sha: project.commit('feature').sha) end let(:unicode) do create(:ci_pipeline, + project: project, ref: 'ü/unicode/multi-byte') end - it 'returns the latest commit for a ref lazily' do + let(:in_another_project) do + other_project = create(:project, :repository) + create(:ci_pipeline, + project: other_project, + ref: other_project.default_branch, + sha: other_project.commit.sha) + end + + it 'returns the latest commit for a ref lazily', :aggregate_failures do expect(project.repository) .to receive(:list_commits_by_ref_name).once .and_call_original + requests_before = Gitlab::GitalyClient.get_request_count pipeline.lazy_ref_commit another.lazy_ref_commit unicode.lazy_ref_commit + in_another_project.lazy_ref_commit + requests_after = Gitlab::GitalyClient.get_request_count + + expect(requests_after - requests_before).to eq(0) expect(pipeline.lazy_ref_commit.id).to eq pipeline.sha expect(another.lazy_ref_commit.id).to eq another.sha - expect(unicode.lazy_ref_commit).to be_nil + expect(unicode.lazy_ref_commit.itself).to be_nil + expect(in_another_project.lazy_ref_commit.id).to eq in_another_project.sha + + expect(pipeline.lazy_ref_commit.repository.container).to eq project + expect(in_another_project.lazy_ref_commit.repository.container).to eq in_another_project.project end end diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index 2bc4c177bc9..086c7d0ceb3 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Snippets, factory_default: :keep, feature_category: :source_code_management do +RSpec.describe API::Snippets, :aggregate_failures, factory_default: :keep, feature_category: :source_code_management do include SnippetHelpers let_it_be(:admin) { create(:user, :admin) } @@ -448,7 +448,7 @@ RSpec.describe API::Snippets, factory_default: :keep, feature_category: :source_ end context "when admin" do - let_it_be(:token) { create(:personal_access_token, user: admin, scopes: [:sudo]) } + let_it_be(:token) { create(:personal_access_token, :admin_mode, user: admin, scopes: [:sudo]) } subject do put api("/snippets/#{snippet.id}", personal_access_token: token), params: { visibility: 'private', sudo: user.id } @@ -504,7 +504,7 @@ RSpec.describe API::Snippets, factory_default: :keep, feature_category: :source_ it 'exposes known attributes' do user_agent_detail = create(:user_agent_detail, subject: snippet) - get api("/snippets/#{snippet.id}/user_agent_detail", admin) + get api("/snippets/#{snippet.id}/user_agent_detail", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) diff --git a/spec/requests/api/statistics_spec.rb b/spec/requests/api/statistics_spec.rb index 85fed48a077..8a674872d45 100644 --- a/spec/requests/api/statistics_spec.rb +++ b/spec/requests/api/statistics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Statistics, 'Statistics', feature_category: :devops_reports do +RSpec.describe API::Statistics, 'Statistics', :aggregate_failures, feature_category: :devops_reports do include ProjectForksHelper tables_to_analyze = %w[ projects @@ -43,7 +43,7 @@ RSpec.describe API::Statistics, 'Statistics', feature_category: :devops_reports let(:admin) { create(:admin) } it 'matches the response schema' do - get api(path, admin) + get api(path, admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('statistics') @@ -66,7 +66,7 @@ RSpec.describe API::Statistics, 'Statistics', feature_category: :devops_reports ApplicationRecord.connection.execute("ANALYZE #{table}") end - get api(path, admin) + get api(path, admin, admin_mode: true) expected_statistics = { issues: 2, diff --git a/spec/requests/api/topics_spec.rb b/spec/requests/api/topics_spec.rb index 14719292557..083d741c48c 100644 --- a/spec/requests/api/topics_spec.rb +++ b/spec/requests/api/topics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Topics, feature_category: :projects do +RSpec.describe API::Topics, :aggregate_failures, feature_category: :projects do include WorkhorseHelpers let_it_be(:file) { fixture_file_upload('spec/fixtures/dk.png') } @@ -14,7 +14,7 @@ RSpec.describe API::Topics, feature_category: :projects do let_it_be(:admin) { create(:user, :admin) } let_it_be(:user) { create(:user) } - describe 'GET /topics', :aggregate_failures do + describe 'GET /topics' do it 'returns topics ordered by total_projects_count' do get api('/topics') @@ -105,7 +105,7 @@ RSpec.describe API::Topics, feature_category: :projects do end end - describe 'GET /topic/:id', :aggregate_failures do + describe 'GET /topic/:id' do it 'returns topic' do get api("/topics/#{topic_2.id}") @@ -130,10 +130,10 @@ RSpec.describe API::Topics, feature_category: :projects do end end - describe 'POST /topics', :aggregate_failures do + describe 'POST /topics' do context 'as administrator' do it 'creates a topic' do - post api('/topics/', admin), params: { name: 'my-topic', title: 'My Topic' } + post api('/topics/', admin, admin_mode: true), params: { name: 'my-topic', title: 'My Topic' } expect(response).to have_gitlab_http_status(:created) expect(json_response['name']).to eq('my-topic') @@ -142,7 +142,7 @@ RSpec.describe API::Topics, feature_category: :projects do it 'creates a topic with avatar and description' do workhorse_form_with_file( - api('/topics/', admin), + api('/topics/', admin, admin_mode: true), file_key: :avatar, params: { name: 'my-topic', title: 'My Topic', description: 'my description...', avatar: file } ) @@ -160,14 +160,14 @@ RSpec.describe API::Topics, feature_category: :projects do end it 'returns 400 if name is not unique (case insensitive)' do - post api('/topics/', admin), params: { name: topic_1.name.downcase, title: 'My Topic' } + post api('/topics/', admin, admin_mode: true), params: { name: topic_1.name.downcase, title: 'My Topic' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']['name']).to eq(['has already been taken']) end it 'returns 400 if title is missing' do - post api('/topics/', admin), params: { name: 'my-topic' } + post api('/topics/', admin, admin_mode: true), params: { name: 'my-topic' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eql('title is missing') @@ -191,10 +191,10 @@ RSpec.describe API::Topics, feature_category: :projects do end end - describe 'PUT /topics', :aggregate_failures do + describe 'PUT /topics' do context 'as administrator' do it 'updates a topic' do - put api("/topics/#{topic_3.id}", admin), params: { name: 'my-topic' } + put api("/topics/#{topic_3.id}", admin, admin_mode: true), params: { name: 'my-topic' } expect(response).to have_gitlab_http_status(:ok) expect(json_response['name']).to eq('my-topic') @@ -203,7 +203,7 @@ RSpec.describe API::Topics, feature_category: :projects do it 'updates a topic with avatar and description' do workhorse_form_with_file( - api("/topics/#{topic_3.id}", admin), + api("/topics/#{topic_3.id}", admin, admin_mode: true), method: :put, file_key: :avatar, params: { description: 'my description...', avatar: file } @@ -215,7 +215,7 @@ RSpec.describe API::Topics, feature_category: :projects do end it 'keeps avatar when updating other fields' do - put api("/topics/#{topic_1.id}", admin), params: { name: 'my-topic' } + put api("/topics/#{topic_1.id}", admin, admin_mode: true), params: { name: 'my-topic' } expect(response).to have_gitlab_http_status(:ok) expect(json_response['name']).to eq('my-topic') @@ -223,13 +223,13 @@ RSpec.describe API::Topics, feature_category: :projects do end it 'returns 404 for non existing id' do - put api("/topics/#{non_existing_record_id}", admin), params: { name: 'my-topic' } + put api("/topics/#{non_existing_record_id}", admin, admin_mode: true), params: { name: 'my-topic' } expect(response).to have_gitlab_http_status(:not_found) end it 'returns 400 for invalid `id` parameter' do - put api('/topics/invalid', admin), params: { name: 'my-topic' } + put api('/topics/invalid', admin, admin_mode: true), params: { name: 'my-topic' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eql('id is invalid') @@ -237,7 +237,7 @@ RSpec.describe API::Topics, feature_category: :projects do context 'with blank avatar' do it 'removes avatar' do - put api("/topics/#{topic_1.id}", admin), params: { avatar: '' } + put api("/topics/#{topic_1.id}", admin, admin_mode: true), params: { avatar: '' } expect(response).to have_gitlab_http_status(:ok) expect(json_response['avatar_url']).to be_nil @@ -245,7 +245,7 @@ RSpec.describe API::Topics, feature_category: :projects do end it 'removes avatar besides other changes' do - put api("/topics/#{topic_1.id}", admin), params: { name: 'new-topic-name', avatar: '' } + put api("/topics/#{topic_1.id}", admin, admin_mode: true), params: { name: 'new-topic-name', avatar: '' } expect(response).to have_gitlab_http_status(:ok) expect(json_response['name']).to eq('new-topic-name') @@ -254,7 +254,7 @@ RSpec.describe API::Topics, feature_category: :projects do end it 'does not remove avatar in case of other errors' do - put api("/topics/#{topic_1.id}", admin), params: { name: topic_2.name, avatar: '' } + put api("/topics/#{topic_1.id}", admin, admin_mode: true), params: { name: topic_2.name, avatar: '' } expect(response).to have_gitlab_http_status(:bad_request) expect(topic_1.reload.avatar_url).not_to be_nil @@ -279,22 +279,22 @@ RSpec.describe API::Topics, feature_category: :projects do end end - describe 'DELETE /topics', :aggregate_failures do + describe 'DELETE /topics' do context 'as administrator' do it 'deletes a topic' do - delete api("/topics/#{topic_3.id}", admin), params: { name: 'my-topic' } + delete api("/topics/#{topic_3.id}", admin, admin_mode: true), params: { name: 'my-topic' } expect(response).to have_gitlab_http_status(:no_content) end it 'returns 404 for non existing id' do - delete api("/topics/#{non_existing_record_id}", admin), params: { name: 'my-topic' } + delete api("/topics/#{non_existing_record_id}", admin, admin_mode: true), params: { name: 'my-topic' } expect(response).to have_gitlab_http_status(:not_found) end it 'returns 400 for invalid `id` parameter' do - delete api('/topics/invalid', admin), params: { name: 'my-topic' } + delete api('/topics/invalid', admin, admin_mode: true), params: { name: 'my-topic' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eql('id is invalid') @@ -318,9 +318,9 @@ RSpec.describe API::Topics, feature_category: :projects do end end - describe 'POST /topics/merge', :aggregate_failures do + describe 'POST /topics/merge' do context 'as administrator' do - let_it_be(:api_url) { api('/topics/merge', admin) } + let_it_be(:api_url) { api('/topics/merge', admin, admin_mode: true) } it 'merge topics' do post api_url, params: { source_topic_id: topic_3.id, target_topic_id: topic_2.id } diff --git a/spec/requests/api/usage_data_non_sql_metrics_spec.rb b/spec/requests/api/usage_data_non_sql_metrics_spec.rb index 0a6f248af2c..a648f63974c 100644 --- a/spec/requests/api/usage_data_non_sql_metrics_spec.rb +++ b/spec/requests/api/usage_data_non_sql_metrics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::UsageDataNonSqlMetrics, feature_category: :service_ping do +RSpec.describe API::UsageDataNonSqlMetrics, :aggregate_failures, feature_category: :service_ping do include UsageDataHelpers let_it_be(:admin) { create(:user, admin: true) } @@ -22,7 +22,7 @@ RSpec.describe API::UsageDataNonSqlMetrics, feature_category: :service_ping do end it 'returns non sql metrics if user is admin' do - get api(endpoint, admin) + get api(endpoint, admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response['counts']).to be_a(Hash) @@ -53,7 +53,7 @@ RSpec.describe API::UsageDataNonSqlMetrics, feature_category: :service_ping do end it 'returns not_found for admin' do - get api(endpoint, admin) + get api(endpoint, admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end diff --git a/spec/requests/api/usage_data_queries_spec.rb b/spec/requests/api/usage_data_queries_spec.rb index e556064025c..a85b2612af1 100644 --- a/spec/requests/api/usage_data_queries_spec.rb +++ b/spec/requests/api/usage_data_queries_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require 'rake_helper' -RSpec.describe API::UsageDataQueries, feature_category: :service_ping do +RSpec.describe API::UsageDataQueries, :aggregate_failures, feature_category: :service_ping do include UsageDataHelpers let_it_be(:admin) { create(:user, admin: true) } @@ -23,7 +23,7 @@ RSpec.describe API::UsageDataQueries, feature_category: :service_ping do end it 'returns queries if user is admin' do - get api(endpoint, admin) + get api(endpoint, admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response['active_user_count']).to start_with('SELECT COUNT("users"."id") FROM "users"') @@ -54,7 +54,7 @@ RSpec.describe API::UsageDataQueries, feature_category: :service_ping do end it 'returns not_found for admin' do - get api(endpoint, admin) + get api(endpoint, admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -81,7 +81,7 @@ RSpec.describe API::UsageDataQueries, feature_category: :service_ping do it 'matches the generated query' do travel_to(Time.utc(2021, 1, 1)) do - get api(endpoint, admin) + get api(endpoint, admin, admin_mode: true) end data = Gitlab::Json.parse(File.read(file)) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c924f529e11..17847d4af03 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Users, feature_category: :user_profile do +RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile do include WorkhorseHelpers let_it_be(:admin) { create(:admin) } @@ -41,7 +41,7 @@ RSpec.describe API::Users, feature_category: :user_profile do optional_attributes = { note: 'Awesome Note' } attributes = attributes_for(:user).merge(optional_attributes) - post api('/users', admin), params: attributes + post api('/users', admin, admin_mode: true), params: attributes expect(response).to have_gitlab_http_status(:created) expect(json_response['note']).to eq(optional_attributes[:note]) @@ -64,7 +64,7 @@ RSpec.describe API::Users, feature_category: :user_profile do new_note = '2019-07-07 | Email changed | user requested | www.gitlab.com' expect do - put api("/users/#{user.id}", admin), params: { note: new_note } + put api("/users/#{user.id}", admin, admin_mode: true), params: { note: new_note } end.to change { user.reload.note } .from('2018-11-05 | 2FA removed | user requested | www.gitlab.com') .to(new_note) @@ -89,7 +89,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context "when current user is an admin" do it "returns a 204 when 2FA is disabled for the target user" do expect do - patch api("/users/#{user_with_2fa.id}/disable_two_factor", admin) + patch api("/users/#{user_with_2fa.id}/disable_two_factor", admin, admin_mode: true) end.to change { user_with_2fa.reload.two_factor_enabled? } .from(true) .to(false) @@ -103,14 +103,14 @@ RSpec.describe API::Users, feature_category: :user_profile do .and_return(destroy_service) expect(destroy_service).to receive(:execute) - patch api("/users/#{user_with_2fa.id}/disable_two_factor", admin) + patch api("/users/#{user_with_2fa.id}/disable_two_factor", admin, admin_mode: true) end it "returns a 400 if 2FA is not enabled for the target user" do expect(TwoFactor::DestroyService).to receive(:new).and_call_original expect do - patch api("/users/#{user.id}/disable_two_factor", admin) + patch api("/users/#{user.id}/disable_two_factor", admin, admin_mode: true) end.not_to change { user.reload.two_factor_enabled? } expect(response).to have_gitlab_http_status(:bad_request) @@ -121,7 +121,7 @@ RSpec.describe API::Users, feature_category: :user_profile do expect(TwoFactor::DestroyService).not_to receive(:new) expect do - patch api("/users/#{admin_with_2fa.id}/disable_two_factor", admin) + patch api("/users/#{admin_with_2fa.id}/disable_two_factor", admin, admin_mode: true) end.not_to change { admin_with_2fa.reload.two_factor_enabled? } expect(response).to have_gitlab_http_status(:forbidden) @@ -131,7 +131,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "returns a 404 if the target user cannot be found" do expect(TwoFactor::DestroyService).not_to receive(:new) - patch api("/users/#{non_existing_record_id}/disable_two_factor", admin) + patch api("/users/#{non_existing_record_id}/disable_two_factor", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq("404 User Not Found") @@ -182,7 +182,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'as an admin' do it 'contains the note of users' do - get api("/users", admin), params: { username: user.username } + get api("/users", admin, admin_mode: true), params: { username: user.username } expect(response).to have_gitlab_http_status(:success) expect(json_response.first).to have_key('note') @@ -191,7 +191,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'with `created_by` details' do it 'has created_by as nil with a self-registered account' do - get api("/users", admin), params: { username: user.username } + get api("/users", admin, admin_mode: true), params: { username: user.username } expect(response).to have_gitlab_http_status(:success) expect(json_response.first).to have_key('created_by') @@ -201,7 +201,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'is created_by a user and has those details' do created = create(:user, created_by_id: user.id) - get api("/users", admin), params: { username: created.username } + get api("/users", admin, admin_mode: true), params: { username: created.username } expect(response).to have_gitlab_http_status(:success) expect(json_response.first['created_by'].symbolize_keys) @@ -259,7 +259,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end context 'sudo' do - let(:admin_personal_access_token) { create(:personal_access_token, user: admin, scopes: %w[api sudo]).token } + let(:admin_personal_access_token) { create(:personal_access_token, :admin_mode, user: admin, scopes: %w[api sudo]).token } context 'accesses the profile of another regular user' do it 'does not contain the note of the user' do @@ -477,6 +477,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'exclude_internal param' do let_it_be(:internal_user) { User.alert_bot } + # why is this working without admin_mode? it 'returns all users when it is not set' do get api("/users?exclude_internal=false", admin) @@ -528,7 +529,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context "when admin" do context 'when sudo is defined' do it 'does not return 500' do - admin_personal_access_token = create(:personal_access_token, user: admin, scopes: [:sudo]) + admin_personal_access_token = create(:personal_access_token, :admin_mode, user: admin, scopes: [:sudo]) get api("/users?sudo=#{user.id}", admin, personal_access_token: admin_personal_access_token) expect(response).to have_gitlab_http_status(:success) @@ -536,14 +537,14 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns an array of users" do - get api("/users", admin) + get api("/users", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admins') expect(response).to include_pagination_headers end it "users contain the `namespace_id` field" do - get api("/users", admin) + get api("/users", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:success) expect(response).to match_response_schema('public_api/v4/user/admins') @@ -554,7 +555,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "returns an array of external users" do create(:user, external: true) - get api("/users?external=true", admin) + get api("/users?external=true", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admins') expect(response).to include_pagination_headers @@ -562,7 +563,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns one user by external UID" do - get api("/users?extern_uid=#{omniauth_user.identities.first.extern_uid}&provider=#{omniauth_user.identities.first.provider}", admin) + get api("/users?extern_uid=#{omniauth_user.identities.first.extern_uid}&provider=#{omniauth_user.identities.first.provider}", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admins') expect(json_response.size).to eq(1) @@ -570,13 +571,13 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns 400 error if provider with no extern_uid" do - get api("/users?extern_uid=#{omniauth_user.identities.first.extern_uid}", admin) + get api("/users?extern_uid=#{omniauth_user.identities.first.extern_uid}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end it "returns 400 error if provider with no extern_uid" do - get api("/users?provider=#{omniauth_user.identities.first.provider}", admin) + get api("/users?provider=#{omniauth_user.identities.first.provider}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -584,7 +585,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "returns a user created before a specific date" do user = create(:user, created_at: Date.new(2000, 1, 1)) - get api("/users?created_before=2000-01-02T00:00:00.060Z", admin) + get api("/users?created_before=2000-01-02T00:00:00.060Z", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admins') expect(json_response.size).to eq(1) @@ -594,7 +595,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "returns no users created before a specific date" do create(:user, created_at: Date.new(2001, 1, 1)) - get api("/users?created_before=2000-01-02T00:00:00.060Z", admin) + get api("/users?created_before=2000-01-02T00:00:00.060Z", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admins') expect(json_response.size).to eq(0) @@ -603,7 +604,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "returns users created before and after a specific date" do user = create(:user, created_at: Date.new(2001, 1, 1)) - get api("/users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060", admin) + get api("/users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admins') expect(json_response.size).to eq(1) @@ -615,7 +616,7 @@ RSpec.describe API::Users, feature_category: :user_profile do # - admin # - user - get api('/users', admin), params: { order_by: 'id', sort: 'asc' } + get api('/users', admin, admin_mode: true), params: { order_by: 'id', sort: 'asc' } expect(response).to match_response_schema('public_api/v4/user/admins') expect(json_response.size).to eq(2) @@ -626,7 +627,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns users with 2fa enabled' do user_with_2fa = create(:user, :two_factor_via_otp) - get api('/users', admin), params: { two_factor: 'enabled' } + get api('/users', admin, admin_mode: true), params: { two_factor: 'enabled' } expect(response).to match_response_schema('public_api/v4/user/admins') expect(json_response.size).to eq(1) @@ -638,7 +639,7 @@ RSpec.describe API::Users, feature_category: :user_profile do create(:project, namespace: user.namespace) create(:project, namespace: admin.namespace) - get api('/users', admin), params: { without_projects: true } + get api('/users', admin, admin_mode: true), params: { without_projects: true } expect(response).to match_response_schema('public_api/v4/user/admins') expect(json_response.size).to eq(1) @@ -646,7 +647,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'returns 400 when provided incorrect sort params' do - get api('/users', admin), params: { order_by: 'magic', sort: 'asc' } + get api('/users', admin, admin_mode: true), params: { order_by: 'magic', sort: 'asc' } expect(response).to have_gitlab_http_status(:bad_request) end @@ -654,7 +655,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'admins param' do it 'returns only admins' do - get api("/users?admins=true", admin) + get api("/users?admins=true", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/basics') expect(json_response.size).to eq(1) @@ -794,7 +795,7 @@ RSpec.describe API::Users, feature_category: :user_profile do expect(Gitlab::ApplicationRateLimiter) .not_to receive(:throttled?) - get api("/users/#{user.id}", admin) + get api("/users/#{user.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) end @@ -836,7 +837,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'when authenticated as admin' do it 'contains the note of the user' do - get api("/users/#{user.id}", admin) + get api("/users/#{user.id}", admin, admin_mode: true) expect(json_response).to have_key('note') expect(json_response['note']).to eq(user.note) @@ -844,28 +845,28 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'includes the `is_admin` field' do - get api("/users/#{user.id}", admin) + get api("/users/#{user.id}", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['is_admin']).to be(false) end it "includes the `created_at` field for private users" do - get api("/users/#{private_user.id}", admin) + get api("/users/#{private_user.id}", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response.keys).to include 'created_at' end it 'includes the `highest_role` field' do - get api("/users/#{user.id}", admin) + get api("/users/#{user.id}", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['highest_role']).to be(0) end it 'includes the `namespace_id` field' do - get api("/users/#{user.id}", admin) + get api("/users/#{user.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:success) expect(response).to match_response_schema('public_api/v4/user/admin') @@ -874,13 +875,13 @@ RSpec.describe API::Users, feature_category: :user_profile do if Gitlab.ee? it 'does not include values for plan or trial' do - get api("/users/#{user.id}", admin) + get api("/users/#{user.id}", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/basic') end else it 'does not include plan or trial data' do - get api("/users/#{user.id}", admin) + get api("/users/#{user.id}", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/basic') expect(json_response.keys).not_to include 'plan' @@ -890,7 +891,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'when user has not logged in' do it 'does not include the sign in IPs' do - get api("/users/#{user.id}", admin) + get api("/users/#{user.id}", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response).to include('current_sign_in_ip' => nil, 'last_sign_in_ip' => nil) @@ -901,7 +902,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let_it_be(:signed_in_user) { create(:user, :with_sign_ins) } it 'includes the sign in IPs' do - get api("/users/#{signed_in_user.id}", admin) + get api("/users/#{signed_in_user.id}", admin, admin_mode: true) expect(response).to match_response_schema('public_api/v4/user/admin') expect(json_response['current_sign_in_ip']).to eq('127.0.0.1') @@ -1104,12 +1105,12 @@ RSpec.describe API::Users, feature_category: :user_profile do describe "POST /users" do it "creates user" do expect do - post api("/users", admin), params: attributes_for(:user, projects_limit: 3) + post api("/users", admin, admin_mode: true), params: attributes_for(:user, projects_limit: 3) end.to change { User.count }.by(1) end it "creates user with correct attributes" do - post api('/users', admin), params: attributes_for(:user, admin: true, can_create_group: true) + post api('/users', admin, admin_mode: true), params: attributes_for(:user, admin: true, can_create_group: true) expect(response).to have_gitlab_http_status(:created) user_id = json_response['id'] new_user = User.find(user_id) @@ -1121,13 +1122,13 @@ RSpec.describe API::Users, feature_category: :user_profile do optional_attributes = { confirm: true, theme_id: 2, color_scheme_id: 4 } attributes = attributes_for(:user).merge(optional_attributes) - post api('/users', admin), params: attributes + post api('/users', admin, admin_mode: true), params: attributes expect(response).to have_gitlab_http_status(:created) end it "creates non-admin user" do - post api('/users', admin), params: attributes_for(:user, admin: false, can_create_group: false) + post api('/users', admin, admin_mode: true), params: attributes_for(:user, admin: false, can_create_group: false) expect(response).to have_gitlab_http_status(:created) user_id = json_response['id'] new_user = User.find(user_id) @@ -1136,7 +1137,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "creates non-admin users by default" do - post api('/users', admin), params: attributes_for(:user) + post api('/users', admin, admin_mode: true), params: attributes_for(:user) expect(response).to have_gitlab_http_status(:created) user_id = json_response['id'] new_user = User.find(user_id) @@ -1144,13 +1145,13 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns 201 Created on success" do - post api("/users", admin), params: attributes_for(:user, projects_limit: 3) + post api("/users", admin, admin_mode: true), params: attributes_for(:user, projects_limit: 3) expect(response).to match_response_schema('public_api/v4/user/admin') expect(response).to have_gitlab_http_status(:created) end it 'creates non-external users by default' do - post api("/users", admin), params: attributes_for(:user) + post api("/users", admin, admin_mode: true), params: attributes_for(:user) expect(response).to have_gitlab_http_status(:created) user_id = json_response['id'] @@ -1159,7 +1160,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'allows an external user to be created' do - post api("/users", admin), params: attributes_for(:user, external: true) + post api("/users", admin, admin_mode: true), params: attributes_for(:user, external: true) expect(response).to have_gitlab_http_status(:created) user_id = json_response['id'] @@ -1168,7 +1169,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "creates user with reset password" do - post api('/users', admin), params: attributes_for(:user, reset_password: true).except(:password) + post api('/users', admin, admin_mode: true), params: attributes_for(:user, reset_password: true).except(:password) expect(response).to have_gitlab_http_status(:created) @@ -1181,7 +1182,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "creates user with random password" do params = attributes_for(:user, force_random_password: true) params.delete(:password) - post api('/users', admin), params: params + post api('/users', admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:created) @@ -1192,7 +1193,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "creates user with private profile" do - post api('/users', admin), params: attributes_for(:user, private_profile: true) + post api('/users', admin, admin_mode: true), params: attributes_for(:user, private_profile: true) expect(response).to have_gitlab_http_status(:created) @@ -1204,7 +1205,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "creates user with view_diffs_file_by_file" do - post api('/users', admin), params: attributes_for(:user, view_diffs_file_by_file: true) + post api('/users', admin, admin_mode: true), params: attributes_for(:user, view_diffs_file_by_file: true) expect(response).to have_gitlab_http_status(:created) @@ -1217,7 +1218,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "creates user with avatar" do workhorse_form_with_file( - api('/users', admin), + api('/users', admin, admin_mode: true), method: :post, file_key: :avatar, params: attributes_for(:user, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif')) @@ -1232,7 +1233,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "does not create user with invalid email" do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { email: 'invalid email', password: User.random_password, @@ -1242,22 +1243,22 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'returns 400 error if name not given' do - post api('/users', admin), params: attributes_for(:user).except(:name) + post api('/users', admin, admin_mode: true), params: attributes_for(:user).except(:name) expect(response).to have_gitlab_http_status(:bad_request) end it 'returns 400 error if password not given' do - post api('/users', admin), params: attributes_for(:user).except(:password) + post api('/users', admin, admin_mode: true), params: attributes_for(:user).except(:password) expect(response).to have_gitlab_http_status(:bad_request) end it 'returns 400 error if email not given' do - post api('/users', admin), params: attributes_for(:user).except(:email) + post api('/users', admin, admin_mode: true), params: attributes_for(:user).except(:email) expect(response).to have_gitlab_http_status(:bad_request) end it 'returns 400 error if username not given' do - post api('/users', admin), params: attributes_for(:user).except(:username) + post api('/users', admin, admin_mode: true), params: attributes_for(:user).except(:username) expect(response).to have_gitlab_http_status(:bad_request) end @@ -1265,13 +1266,13 @@ RSpec.describe API::Users, feature_category: :user_profile do optional_attributes = { theme_id: 50, color_scheme_id: 50 } attributes = attributes_for(:user).merge(optional_attributes) - post api('/users', admin), params: attributes + post api('/users', admin, admin_mode: true), params: attributes expect(response).to have_gitlab_http_status(:bad_request) end it 'returns 400 error if user does not validate' do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { password: 'pass', email: 'test@example.com', @@ -1293,7 +1294,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'tracks weak password errors' do attributes = attributes_for(:user).merge({ password: "password" }) - post api('/users', admin), params: attributes + post api('/users', admin, admin_mode: true), params: attributes expect(json_response['message']['password']) .to eq(['must not contain commonly used combinations of words and letters']) @@ -1312,7 +1313,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'with existing user' do before do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { email: 'test@example.com', password: User.random_password, @@ -1323,7 +1324,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 409 conflict error if user with same email exists' do expect do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { name: 'foo', email: 'test@example.com', @@ -1337,7 +1338,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 409 conflict error if same username exists' do expect do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { name: 'foo', email: 'foo@example.com', @@ -1351,7 +1352,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 409 conflict error if same username exists (case insensitive)' do expect do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { name: 'foo', email: 'foo@example.com', @@ -1364,7 +1365,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'creates user with new identity' do - post api("/users", admin), params: attributes_for(:user, provider: 'github', extern_uid: '67890') + post api("/users", admin, admin_mode: true), params: attributes_for(:user, provider: 'github', extern_uid: '67890') expect(response).to have_gitlab_http_status(:created) expect(json_response['identities'].first['extern_uid']).to eq('67890') @@ -1378,7 +1379,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 409 conflict error' do expect do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { name: 'foo', email: confirmed_user.email, @@ -1396,7 +1397,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 409 conflict error' do expect do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { name: 'foo', email: unconfirmed_user.email, @@ -1416,7 +1417,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 409 conflict error' do expect do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { name: 'foo', email: email.email, @@ -1434,7 +1435,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'does not create user' do expect do - post api('/users', admin), + post api('/users', admin, admin_mode: true), params: { name: 'foo', email: email.email, @@ -1465,7 +1466,7 @@ RSpec.describe API::Users, feature_category: :user_profile do shared_examples_for 'creates the user with the value of `private_profile` based on the application setting' do specify do - post api("/users", admin), params: params + post api("/users", admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:created) user = User.find_by(id: json_response['id'], private_profile: true) @@ -1479,7 +1480,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'when the attribute is overridden in params' do it 'creates the user with the value of `private_profile` same as the value of the overridden param' do - post api("/users", admin), params: params.merge(private_profile: false) + post api("/users", admin, admin_mode: true), params: params.merge(private_profile: false) expect(response).to have_gitlab_http_status(:created) user = User.find_by(id: json_response['id'], private_profile: false) @@ -1498,7 +1499,7 @@ RSpec.describe API::Users, feature_category: :user_profile do describe "PUT /users/:id" do it "returns 200 OK on success" do - put api("/users/#{user.id}", admin), params: { bio: 'new test bio' } + put api("/users/#{user.id}", admin, admin_mode: true), params: { bio: 'new test bio' } expect(response).to match_response_schema('public_api/v4/user/admin') expect(response).to have_gitlab_http_status(:ok) @@ -1506,7 +1507,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'updating password' do def update_password(user, admin, password = User.random_password) - put api("/users/#{user.id}", admin), params: { password: password } + put api("/users/#{user.id}", admin, admin_mode: true), params: { password: password } end context 'admin updates their own password' do @@ -1564,7 +1565,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "updates user with new bio" do - put api("/users/#{user.id}", admin), params: { bio: 'new test bio' } + put api("/users/#{user.id}", admin, admin_mode: true), params: { bio: 'new test bio' } expect(response).to have_gitlab_http_status(:ok) expect(json_response['bio']).to eq('new test bio') @@ -1574,7 +1575,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "updates user with empty bio" do user.update!(bio: 'previous bio') - put api("/users/#{user.id}", admin), params: { bio: '' } + put api("/users/#{user.id}", admin, admin_mode: true), params: { bio: '' } expect(response).to have_gitlab_http_status(:ok) expect(json_response['bio']).to eq('') @@ -1582,7 +1583,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'updates user with nil bio' do - put api("/users/#{user.id}", admin), params: { bio: nil } + put api("/users/#{user.id}", admin, admin_mode: true), params: { bio: nil } expect(response).to have_gitlab_http_status(:ok) expect(json_response['bio']).to eq('') @@ -1590,7 +1591,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "updates user with organization" do - put api("/users/#{user.id}", admin), params: { organization: 'GitLab' } + put api("/users/#{user.id}", admin, admin_mode: true), params: { organization: 'GitLab' } expect(response).to have_gitlab_http_status(:ok) expect(json_response['organization']).to eq('GitLab') @@ -1599,7 +1600,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'updates user with avatar' do workhorse_form_with_file( - api("/users/#{user.id}", admin), + api("/users/#{user.id}", admin, admin_mode: true), method: :put, file_key: :avatar, params: { avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') } @@ -1615,7 +1616,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'updates user with a new email' do old_email = user.email old_notification_email = user.notification_email_or_default - put api("/users/#{user.id}", admin), params: { email: 'new@email.com' } + put api("/users/#{user.id}", admin, admin_mode: true), params: { email: 'new@email.com' } user.reload @@ -1627,7 +1628,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'skips reconfirmation when requested' do - put api("/users/#{user.id}", admin), params: { email: 'new@email.com', skip_reconfirmation: true } + put api("/users/#{user.id}", admin, admin_mode: true), params: { email: 'new@email.com', skip_reconfirmation: true } user.reload @@ -1637,7 +1638,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'updates user with their own username' do - put api("/users/#{user.id}", admin), params: { username: user.username } + put api("/users/#{user.id}", admin, admin_mode: true), params: { username: user.username } expect(response).to have_gitlab_http_status(:ok) expect(json_response['username']).to eq(user.username) @@ -1645,14 +1646,14 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "updates user's existing identity" do - put api("/users/#{ldap_user.id}", admin), params: { provider: 'ldapmain', extern_uid: '654321' } + put api("/users/#{ldap_user.id}", admin, admin_mode: true), params: { provider: 'ldapmain', extern_uid: '654321' } expect(response).to have_gitlab_http_status(:ok) expect(ldap_user.reload.identities.first.extern_uid).to eq('654321') end it 'updates user with new identity' do - put api("/users/#{user.id}", admin), params: { provider: 'github', extern_uid: 'john' } + put api("/users/#{user.id}", admin, admin_mode: true), params: { provider: 'github', extern_uid: 'john' } expect(response).to have_gitlab_http_status(:ok) expect(user.reload.identities.first.extern_uid).to eq('john') @@ -1660,14 +1661,14 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "updates admin status" do - put api("/users/#{user.id}", admin), params: { admin: true } + put api("/users/#{user.id}", admin, admin_mode: true), params: { admin: true } expect(response).to have_gitlab_http_status(:ok) expect(user.reload.admin).to eq(true) end it "updates external status" do - put api("/users/#{user.id}", admin), params: { external: true } + put api("/users/#{user.id}", admin, admin_mode: true), params: { external: true } expect(response).to have_gitlab_http_status(:ok) expect(json_response['external']).to eq(true) @@ -1675,14 +1676,14 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "does have default values for theme and color-scheme ID" do - put api("/users/#{user.id}", admin), params: {} + put api("/users/#{user.id}", admin, admin_mode: true), params: {} expect(user.reload.theme_id).to eq(Gitlab::Themes.default.id) expect(user.reload.color_scheme_id).to eq(Gitlab::ColorSchemes.default.id) end it "updates viewing diffs file by file" do - put api("/users/#{user.id}", admin), params: { view_diffs_file_by_file: true } + put api("/users/#{user.id}", admin, admin_mode: true), params: { view_diffs_file_by_file: true } expect(response).to have_gitlab_http_status(:ok) expect(user.reload.user_preference.view_diffs_file_by_file?).to eq(true) @@ -1693,7 +1694,7 @@ RSpec.describe API::Users, feature_category: :user_profile do current_value = user.private_profile new_value = !current_value - put api("/users/#{user.id}", admin), params: { private_profile: new_value } + put api("/users/#{user.id}", admin, admin_mode: true), params: { private_profile: new_value } expect(response).to have_gitlab_http_status(:ok) expect(user.reload.private_profile).to eq(new_value) @@ -1707,7 +1708,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "updates private_profile to value of the application setting" do user.update!(private_profile: false) - put api("/users/#{user.id}", admin), params: { private_profile: nil } + put api("/users/#{user.id}", admin, admin_mode: true), params: { private_profile: nil } expect(response).to have_gitlab_http_status(:ok) expect(user.reload.private_profile).to eq(true) @@ -1717,7 +1718,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "does not modify private profile when field is not provided" do user.update!(private_profile: true) - put api("/users/#{user.id}", admin), params: {} + put api("/users/#{user.id}", admin, admin_mode: true), params: {} expect(response).to have_gitlab_http_status(:ok) expect(user.reload.private_profile).to eq(true) @@ -1730,7 +1731,7 @@ RSpec.describe API::Users, feature_category: :user_profile do user.update!(theme_id: theme.id, color_scheme_id: scheme.id) - put api("/users/#{user.id}", admin), params: {} + put api("/users/#{user.id}", admin, admin_mode: true), params: {} expect(response).to have_gitlab_http_status(:ok) expect(user.reload.theme_id).to eq(theme.id) @@ -1740,7 +1741,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it "does not update admin status" do admin_user = create(:admin) - put api("/users/#{admin_user.id}", admin), params: { can_create_group: false } + put api("/users/#{admin_user.id}", admin, admin_mode: true), params: { can_create_group: false } expect(response).to have_gitlab_http_status(:ok) expect(admin_user.reload.admin).to eq(true) @@ -1748,35 +1749,35 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "does not allow invalid update" do - put api("/users/#{user.id}", admin), params: { email: 'invalid email' } + put api("/users/#{user.id}", admin, admin_mode: true), params: { email: 'invalid email' } expect(response).to have_gitlab_http_status(:bad_request) expect(user.reload.email).not_to eq('invalid email') end it "updates theme id" do - put api("/users/#{user.id}", admin), params: { theme_id: 5 } + put api("/users/#{user.id}", admin, admin_mode: true), params: { theme_id: 5 } expect(response).to have_gitlab_http_status(:ok) expect(user.reload.theme_id).to eq(5) end it "does not update invalid theme id" do - put api("/users/#{user.id}", admin), params: { theme_id: 50 } + put api("/users/#{user.id}", admin, admin_mode: true), params: { theme_id: 50 } expect(response).to have_gitlab_http_status(:bad_request) expect(user.reload.theme_id).not_to eq(50) end it "updates color scheme id" do - put api("/users/#{user.id}", admin), params: { color_scheme_id: 5 } + put api("/users/#{user.id}", admin, admin_mode: true), params: { color_scheme_id: 5 } expect(response).to have_gitlab_http_status(:ok) expect(user.reload.color_scheme_id).to eq(5) end it "does not update invalid color scheme id" do - put api("/users/#{user.id}", admin), params: { color_scheme_id: 50 } + put api("/users/#{user.id}", admin, admin_mode: true), params: { color_scheme_id: 50 } expect(response).to have_gitlab_http_status(:bad_request) expect(user.reload.color_scheme_id).not_to eq(50) @@ -1793,20 +1794,20 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns 404 for non-existing user" do - put api("/users/0", admin), params: { bio: 'update should fail' } + put api("/users/0", admin, admin_mode: true), params: { bio: 'update should fail' } expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 if invalid ID" do - put api("/users/ASDF", admin) + put api("/users/ASDF", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end it 'returns 400 error if user does not validate' do - put api("/users/#{user.id}", admin), + put api("/users/#{user.id}", admin, admin_mode: true), params: { password: 'pass', email: 'test@example.com', @@ -1827,26 +1828,26 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'returns 400 if provider is missing for identity update' do - put api("/users/#{omniauth_user.id}", admin), params: { extern_uid: '654321' } + put api("/users/#{omniauth_user.id}", admin, admin_mode: true), params: { extern_uid: '654321' } expect(response).to have_gitlab_http_status(:bad_request) end it 'returns 400 if external UID is missing for identity update' do - put api("/users/#{omniauth_user.id}", admin), params: { provider: 'ldap' } + put api("/users/#{omniauth_user.id}", admin, admin_mode: true), params: { provider: 'ldap' } expect(response).to have_gitlab_http_status(:bad_request) end context "with existing user" do before do - post api("/users", admin), params: { email: 'test@example.com', password: User.random_password, username: 'test', name: 'test' } - post api("/users", admin), params: { email: 'foo@bar.com', password: User.random_password, username: 'john', name: 'john' } + post api("/users", admin, admin_mode: true), params: { email: 'test@example.com', password: User.random_password, username: 'test', name: 'test' } + post api("/users", admin, admin_mode: true), params: { email: 'foo@bar.com', password: User.random_password, username: 'john', name: 'john' } @user = User.all.last end it 'returns 409 conflict error if email address exists' do - put api("/users/#{@user.id}", admin), params: { email: 'test@example.com' } + put api("/users/#{@user.id}", admin, admin_mode: true), params: { email: 'test@example.com' } expect(response).to have_gitlab_http_status(:conflict) expect(@user.reload.email).to eq(@user.email) @@ -1854,7 +1855,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 409 conflict error if username taken' do @user_id = User.all.last.id - put api("/users/#{@user.id}", admin), params: { username: 'test' } + put api("/users/#{@user.id}", admin, admin_mode: true), params: { username: 'test' } expect(response).to have_gitlab_http_status(:conflict) expect(@user.reload.username).to eq(@user.username) @@ -1862,7 +1863,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 409 conflict error if username taken (case insensitive)' do @user_id = User.all.last.id - put api("/users/#{@user.id}", admin), params: { username: 'TEST' } + put api("/users/#{@user.id}", admin, admin_mode: true), params: { username: 'TEST' } expect(response).to have_gitlab_http_status(:conflict) expect(@user.reload.username).to eq(@user.username) @@ -1874,7 +1875,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let!(:confirmed_user) { create(:user, email: 'foo@example.com') } it 'returns 409 conflict error' do - put api("/users/#{user.id}", admin), params: { email: confirmed_user.email } + put api("/users/#{user.id}", admin, admin_mode: true), params: { email: confirmed_user.email } expect(response).to have_gitlab_http_status(:conflict) expect(user.reload.email).not_to eq(confirmed_user.email) @@ -1885,7 +1886,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let!(:unconfirmed_user) { create(:user, :unconfirmed, email: 'foo@example.com') } it 'returns 409 conflict error' do - put api("/users/#{user.id}", admin), params: { email: unconfirmed_user.email } + put api("/users/#{user.id}", admin, admin_mode: true), params: { email: unconfirmed_user.email } expect(response).to have_gitlab_http_status(:conflict) expect(user.reload.email).not_to eq(unconfirmed_user.email) @@ -1898,7 +1899,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let!(:email) { create(:email, :confirmed, email: 'foo@example.com') } it 'returns 409 conflict error' do - put api("/users/#{user.id}", admin), params: { email: email.email } + put api("/users/#{user.id}", admin, admin_mode: true), params: { email: email.email } expect(response).to have_gitlab_http_status(:conflict) expect(user.reload.email).not_to eq(email.email) @@ -1909,7 +1910,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let!(:email) { create(:email, email: 'foo@example.com') } it 'does not update email' do - put api("/users/#{user.id}", admin), params: { email: email.email } + put api("/users/#{user.id}", admin, admin_mode: true), params: { email: email.email } expect(response).to have_gitlab_http_status(:bad_request) expect(user.reload.email).not_to eq(email.email) @@ -1941,7 +1942,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end context 'when authenticated as non-admin' do - it "does not allow updating user's credit card validation", :aggregate_failures do + it "does not allow updating user's credit card validation" do put api("/user/#{user.id}/credit_card_validation", user), params: params expect(response).to have_gitlab_http_status(:forbidden) @@ -1949,8 +1950,8 @@ RSpec.describe API::Users, feature_category: :user_profile do end context 'when authenticated as admin' do - it "updates user's credit card validation", :aggregate_failures do - put api("/user/#{user.id}/credit_card_validation", admin), params: params + it "updates user's credit card validation" do + put api("/user/#{user.id}/credit_card_validation", admin, admin_mode: true), params: params user.reload @@ -1965,13 +1966,13 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns 400 error if credit_card_validated_at is missing" do - put api("/user/#{user.id}/credit_card_validation", admin), params: {} + put api("/user/#{user.id}/credit_card_validation", admin, admin_mode: true), params: {} expect(response).to have_gitlab_http_status(:bad_request) end it 'returns 404 error if user not found' do - put api("/user/#{non_existing_record_id}/credit_card_validation", admin), params: params + put api("/user/#{non_existing_record_id}/credit_card_validation", admin, admin_mode: true), params: params expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') @@ -1993,24 +1994,24 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'when authenticated' do it 'deletes identity of given provider' do expect do - delete api("/users/#{test_user.id}/identities/ldapmain", admin) + delete api("/users/#{test_user.id}/identities/ldapmain", admin, admin_mode: true) end.to change { test_user.identities.count }.by(-1) expect(response).to have_gitlab_http_status(:no_content) end it_behaves_like '412 response' do - let(:request) { api("/users/#{test_user.id}/identities/ldapmain", admin) } + let(:request) { api("/users/#{test_user.id}/identities/ldapmain", admin, admin_mode: true) } end it 'returns 404 error if user not found' do - delete api("/users/0/identities/ldapmain", admin) + delete api("/users/0/identities/ldapmain", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if identity not found' do - delete api("/users/#{test_user.id}/identities/saml", admin) + delete api("/users/#{test_user.id}/identities/saml", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Identity Not Found') @@ -2020,24 +2021,24 @@ RSpec.describe API::Users, feature_category: :user_profile do describe "POST /users/:id/keys" do it "does not create invalid ssh key" do - post api("/users/#{user.id}/keys", admin), params: { title: "invalid key" } + post api("/users/#{user.id}/keys", admin, admin_mode: true), params: { title: "invalid key" } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq('key is missing') end it 'does not create key without title' do - post api("/users/#{user.id}/keys", admin), params: { key: 'some key' } + post api("/users/#{user.id}/keys", admin, admin_mode: true), params: { key: 'some key' } expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq('title is missing') end - it "creates ssh key", :aggregate_failures do + it "creates ssh key" do key_attrs = attributes_for(:key, usage_type: :signing) expect do - post api("/users/#{user.id}/keys", admin), params: key_attrs + post api("/users/#{user.id}/keys", admin, admin_mode: true), params: key_attrs end.to change { user.keys.count }.by(1) expect(response).to have_gitlab_http_status(:created) @@ -2052,14 +2053,14 @@ RSpec.describe API::Users, feature_category: :user_profile do optional_attributes = { expires_at: 3.weeks.from_now } attributes = attributes_for(:key).merge(optional_attributes) - post api("/users/#{user.id}/keys", admin), params: attributes + post api("/users/#{user.id}/keys", admin, admin_mode: true), params: attributes expect(response).to have_gitlab_http_status(:created) expect(json_response['expires_at'].to_date).to eq(optional_attributes[:expires_at].to_date) end it "returns 400 for invalid ID" do - post api("/users/0/keys", admin) + post api("/users/0/keys", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end end @@ -2240,7 +2241,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end describe 'GET /user/:id/keys/:key_id' do - it 'gets existing key', :aggregate_failures do + it 'gets existing key' do user.keys << key get api("/users/#{user.id}/keys/#{key.id}") @@ -2249,7 +2250,7 @@ RSpec.describe API::Users, feature_category: :user_profile do expect(json_response['title']).to eq(key.title) end - it 'returns 404 error if user not found', :aggregate_failures do + it 'returns 404 error if user not found' do user.keys << key get api("/users/0/keys/#{key.id}") @@ -2258,7 +2259,7 @@ RSpec.describe API::Users, feature_category: :user_profile do expect(json_response['message']).to eq('404 User Not Found') end - it 'returns 404 error if key not found', :aggregate_failures do + it 'returns 404 error if key not found' do get api("/users/#{user.id}/keys/#{non_existing_record_id}") expect(response).to have_gitlab_http_status(:not_found) @@ -2279,26 +2280,26 @@ RSpec.describe API::Users, feature_category: :user_profile do user.keys << key expect do - delete api("/users/#{user.id}/keys/#{key.id}", admin) + delete api("/users/#{user.id}/keys/#{key.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:no_content) end.to change { user.keys.count }.by(-1) end it_behaves_like '412 response' do - let(:request) { api("/users/#{user.id}/keys/#{key.id}", admin) } + let(:request) { api("/users/#{user.id}/keys/#{key.id}", admin, admin_mode: true) } end it 'returns 404 error if user not found' do user.keys << key - delete api("/users/0/keys/#{key.id}", admin) + delete api("/users/0/keys/#{key.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if key not foud' do - delete api("/users/#{user.id}/keys/#{non_existing_record_id}", admin) + delete api("/users/#{user.id}/keys/#{non_existing_record_id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Key Not Found') end @@ -2307,7 +2308,7 @@ RSpec.describe API::Users, feature_category: :user_profile do describe 'POST /users/:id/gpg_keys' do it 'does not create invalid GPG key' do - post api("/users/#{user.id}/gpg_keys", admin) + post api("/users/#{user.id}/gpg_keys", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq('key is missing') @@ -2317,14 +2318,14 @@ RSpec.describe API::Users, feature_category: :user_profile do key_attrs = attributes_for :gpg_key, key: GpgHelpers::User2.public_key expect do - post api("/users/#{user.id}/gpg_keys", admin), params: key_attrs + post api("/users/#{user.id}/gpg_keys", admin, admin_mode: true), params: key_attrs expect(response).to have_gitlab_http_status(:created) end.to change { user.gpg_keys.count }.by(1) end it 'returns 400 for invalid ID' do - post api('/users/0/gpg_keys', admin) + post api('/users/0/gpg_keys', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -2389,7 +2390,7 @@ RSpec.describe API::Users, feature_category: :user_profile do user.gpg_keys << gpg_key expect do - delete api("/users/#{user.id}/gpg_keys/#{gpg_key.id}", admin) + delete api("/users/#{user.id}/gpg_keys/#{gpg_key.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:no_content) end.to change { user.gpg_keys.count }.by(-1) @@ -2398,14 +2399,14 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 404 error if user not found' do user.keys << key - delete api("/users/0/gpg_keys/#{gpg_key.id}", admin) + delete api("/users/0/gpg_keys/#{gpg_key.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if key not foud' do - delete api("/users/#{user.id}/gpg_keys/#{non_existing_record_id}", admin) + delete api("/users/#{user.id}/gpg_keys/#{non_existing_record_id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 GPG Key Not Found') @@ -2427,7 +2428,7 @@ RSpec.describe API::Users, feature_category: :user_profile do user.gpg_keys << gpg_key expect do - post api("/users/#{user.id}/gpg_keys/#{gpg_key.id}/revoke", admin) + post api("/users/#{user.id}/gpg_keys/#{gpg_key.id}/revoke", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:accepted) end.to change { user.gpg_keys.count }.by(-1) @@ -2436,14 +2437,14 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns 404 error if user not found' do user.gpg_keys << gpg_key - post api("/users/0/gpg_keys/#{gpg_key.id}/revoke", admin) + post api("/users/0/gpg_keys/#{gpg_key.id}/revoke", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if key not foud' do - post api("/users/#{user.id}/gpg_keys/#{non_existing_record_id}/revoke", admin) + post api("/users/#{user.id}/gpg_keys/#{non_existing_record_id}/revoke", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 GPG Key Not Found') @@ -2453,7 +2454,7 @@ RSpec.describe API::Users, feature_category: :user_profile do describe "POST /users/:id/emails", :mailer do it "does not create invalid email" do - post api("/users/#{user.id}/emails", admin), params: {} + post api("/users/#{user.id}/emails", admin, admin_mode: true), params: {} expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq('email is missing') @@ -2464,7 +2465,7 @@ RSpec.describe API::Users, feature_category: :user_profile do perform_enqueued_jobs do expect do - post api("/users/#{user.id}/emails", admin), params: email_attrs + post api("/users/#{user.id}/emails", admin, admin_mode: true), params: email_attrs end.to change { user.emails.count }.by(1) end @@ -2473,7 +2474,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns a 400 for invalid ID" do - post api("/users/0/emails", admin) + post api("/users/0/emails", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -2482,7 +2483,7 @@ RSpec.describe API::Users, feature_category: :user_profile do email_attrs = attributes_for :email email_attrs[:skip_confirmation] = true - post api("/users/#{user.id}/emails", admin), params: email_attrs + post api("/users/#{user.id}/emails", admin, admin_mode: true), params: email_attrs expect(response).to have_gitlab_http_status(:created) @@ -2494,7 +2495,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let!(:confirmed_user) { create(:user, email: 'foo@example.com') } it 'returns 400 error' do - post api("/users/#{user.id}/emails", admin), params: { email: confirmed_user.email } + post api("/users/#{user.id}/emails", admin, admin_mode: true), params: { email: confirmed_user.email } expect(response).to have_gitlab_http_status(:bad_request) end @@ -2504,7 +2505,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let!(:unconfirmed_user) { create(:user, :unconfirmed, email: 'foo@example.com') } it 'returns 400 error' do - post api("/users/#{user.id}/emails", admin), params: { email: unconfirmed_user.email } + post api("/users/#{user.id}/emails", admin, admin_mode: true), params: { email: unconfirmed_user.email } expect(response).to have_gitlab_http_status(:bad_request) end @@ -2516,7 +2517,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let!(:email) { create(:email, :confirmed, email: 'foo@example.com') } it 'returns 400 error' do - post api("/users/#{user.id}/emails", admin), params: { email: email.email } + post api("/users/#{user.id}/emails", admin, admin_mode: true), params: { email: email.email } expect(response).to have_gitlab_http_status(:bad_request) end @@ -2526,7 +2527,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let!(:email) { create(:email, email: 'foo@example.com') } it 'returns 400 error' do - post api("/users/#{user.id}/emails", admin), params: { email: email.email } + post api("/users/#{user.id}/emails", admin, admin_mode: true), params: { email: email.email } expect(response).to have_gitlab_http_status(:bad_request) end @@ -2544,7 +2545,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'when authenticated' do it 'returns 404 for non-existing user' do - get api('/users/0/emails', admin) + get api('/users/0/emails', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end @@ -2552,7 +2553,7 @@ RSpec.describe API::Users, feature_category: :user_profile do it 'returns array of emails' do user.emails << email - get api("/users/#{user.id}/emails", admin) + get api("/users/#{user.id}/emails", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -2562,7 +2563,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns a 404 for invalid ID" do - get api("/users/ASDF/emails", admin) + get api("/users/ASDF/emails", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -2582,26 +2583,26 @@ RSpec.describe API::Users, feature_category: :user_profile do user.emails << email expect do - delete api("/users/#{user.id}/emails/#{email.id}", admin) + delete api("/users/#{user.id}/emails/#{email.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:no_content) end.to change { user.emails.count }.by(-1) end it_behaves_like '412 response' do - let(:request) { api("/users/#{user.id}/emails/#{email.id}", admin) } + let(:request) { api("/users/#{user.id}/emails/#{email.id}", admin, admin_mode: true) } end it 'returns 404 error if user not found' do user.emails << email - delete api("/users/0/emails/#{email.id}", admin) + delete api("/users/0/emails/#{email.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end it 'returns 404 error if email not foud' do - delete api("/users/#{user.id}/emails/#{non_existing_record_id}", admin) + delete api("/users/#{user.id}/emails/#{non_existing_record_id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Email Not Found') end @@ -2618,7 +2619,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let_it_be(:issue) { create(:issue, author: user) } it "deletes user", :sidekiq_inline do - perform_enqueued_jobs { delete api("/users/#{user.id}", admin) } + perform_enqueued_jobs { delete api("/users/#{user.id}", admin, admin_mode: true) } expect(response).to have_gitlab_http_status(:no_content) expect(Users::GhostUserMigration.where(user: user, @@ -2630,14 +2631,14 @@ RSpec.describe API::Users, feature_category: :user_profile do context "hard delete disabled" do it "does not delete user" do - perform_enqueued_jobs { delete api("/users/#{user.id}", admin) } + perform_enqueued_jobs { delete api("/users/#{user.id}", admin, admin_mode: true) } expect(response).to have_gitlab_http_status(:conflict) end end context "hard delete enabled" do it "delete user and group", :sidekiq_inline do - perform_enqueued_jobs { delete api("/users/#{user.id}?hard_delete=true", admin) } + perform_enqueued_jobs { delete api("/users/#{user.id}?hard_delete=true", admin, admin_mode: true) } expect(response).to have_gitlab_http_status(:no_content) expect(Group.exists?(group.id)).to be_falsy end @@ -2652,7 +2653,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "delete only user", :sidekiq_inline do - perform_enqueued_jobs { delete api("/users/#{user.id}?hard_delete=true", admin) } + perform_enqueued_jobs { delete api("/users/#{user.id}?hard_delete=true", admin, admin_mode: true) } expect(response).to have_gitlab_http_status(:no_content) expect(Group.exists?(subgroup.id)).to be_truthy end @@ -2661,7 +2662,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it_behaves_like '412 response' do - let(:request) { api("/users/#{user.id}", admin) } + let(:request) { api("/users/#{user.id}", admin, admin_mode: true) } end it "does not delete for unauthenticated user" do @@ -2675,20 +2676,20 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns 404 for non-existing user" do - perform_enqueued_jobs { delete api("/users/0", admin) } + perform_enqueued_jobs { delete api("/users/0", admin, admin_mode: true) } expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end it "returns a 404 for invalid ID" do - perform_enqueued_jobs { delete api("/users/ASDF", admin) } + perform_enqueued_jobs { delete api("/users/ASDF", admin, admin_mode: true) } expect(response).to have_gitlab_http_status(:not_found) end context "hard delete disabled" do it "moves contributions to the ghost user", :sidekiq_might_not_need_inline do - perform_enqueued_jobs { delete api("/users/#{user.id}", admin) } + perform_enqueued_jobs { delete api("/users/#{user.id}", admin, admin_mode: true) } expect(response).to have_gitlab_http_status(:no_content) expect(issue.reload).to be_persisted @@ -2700,7 +2701,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context "hard delete enabled" do it "removes contributions", :sidekiq_might_not_need_inline do - perform_enqueued_jobs { delete api("/users/#{user.id}?hard_delete=true", admin) } + perform_enqueued_jobs { delete api("/users/#{user.id}?hard_delete=true", admin, admin_mode: true) } expect(response).to have_gitlab_http_status(:no_content) expect(Users::GhostUserMigration.where(user: user, @@ -2749,6 +2750,7 @@ RSpec.describe API::Users, feature_category: :user_profile do expect(response).to have_gitlab_http_status(:forbidden) end + # why does this work without admin_mode? it 'returns initial current user without private token but with is_admin when sudo not defined' do get api("/user?private_token=#{admin_personal_access_token}", version: version) @@ -2881,13 +2883,13 @@ RSpec.describe API::Users, feature_category: :user_profile do user.keys << key admin - get api("/user/keys/#{key.id}", admin) + get api("/user/keys/#{key.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Key Not Found') end it "returns 404 for invalid ID" do - get api("/users/keys/ASDF", admin) + get api("/users/keys/ASDF", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -2901,7 +2903,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end describe "POST /user/keys" do - it "creates ssh key", :aggregate_failures do + it "creates ssh key" do key_attrs = attributes_for(:key, usage_type: :signing) expect do @@ -2981,7 +2983,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns a 404 for invalid ID" do - delete api("/users/keys/ASDF", admin) + delete api("/users/keys/ASDF", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -3037,14 +3039,14 @@ RSpec.describe API::Users, feature_category: :user_profile do it "returns 404 error if admin accesses user's GPG key" do user.gpg_keys << gpg_key - get api("/user/gpg_keys/#{gpg_key.id}", admin) + get api("/user/gpg_keys/#{gpg_key.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 GPG Key Not Found') end it 'returns 404 for invalid ID' do - get api('/users/gpg_keys/ASDF', admin) + get api('/users/gpg_keys/ASDF', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -3109,7 +3111,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'returns a 404 for invalid ID' do - post api('/users/gpg_keys/ASDF/revoke', admin) + post api('/users/gpg_keys/ASDF/revoke', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -3142,7 +3144,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'returns a 404 for invalid ID' do - delete api('/users/gpg_keys/ASDF', admin) + delete api('/users/gpg_keys/ASDF', admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -3197,13 +3199,13 @@ RSpec.describe API::Users, feature_category: :user_profile do user.emails << email admin - get api("/user/emails/#{email.id}", admin) + get api("/user/emails/#{email.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Email Not Found') end it "returns 404 for invalid ID" do - get api("/users/emails/ASDF", admin) + get api("/users/emails/ASDF", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -3268,7 +3270,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "returns 400 for invalid ID" do - delete api("/user/emails/ASDF", admin) + delete api("/user/emails/ASDF", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -3283,7 +3285,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end describe 'POST /users/:id/activate' do - subject(:activate) { post api("/users/#{user_id}/activate", api_user) } + subject(:activate) { post api("/users/#{user_id}/activate", api_user, admin_mode: true) } let(:user_id) { user.id } @@ -3363,7 +3365,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end describe 'POST /users/:id/deactivate' do - subject(:deactivate) { post api("/users/#{user_id}/deactivate", api_user) } + subject(:deactivate) { post api("/users/#{user_id}/deactivate", api_user, admin_mode: true) } let(:user_id) { user.id } @@ -3480,7 +3482,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end describe 'POST /users/:id/approve' do - subject(:approve) { post api("/users/#{user_id}/approve", api_user) } + subject(:approve) { post api("/users/#{user_id}/approve", api_user, admin_mode: true) } context 'performed by a non-admin user' do let(:api_user) { user } @@ -3558,8 +3560,8 @@ RSpec.describe API::Users, feature_category: :user_profile do end end - describe 'POST /users/:id/reject', :aggregate_failures do - subject(:reject) { post api("/users/#{user_id}/reject", api_user) } + describe 'POST /users/:id/reject' do + subject(:reject) { post api("/users/#{user_id}/reject", api_user, admin_mode: true) } shared_examples 'returns 409' do it 'returns 409' do @@ -3648,9 +3650,9 @@ RSpec.describe API::Users, feature_category: :user_profile do end end - describe 'POST /users/:id/block', :aggregate_failures do + describe 'POST /users/:id/block' do context 'when admin' do - subject(:block_user) { post api("/users/#{user_id}/block", admin) } + subject(:block_user) { post api("/users/#{user_id}/block", admin, admin_mode: true) } context 'with an existing user' do let(:user_id) { user.id } @@ -3738,9 +3740,9 @@ RSpec.describe API::Users, feature_category: :user_profile do end end - describe 'POST /users/:id/unblock', :aggregate_failures do + describe 'POST /users/:id/unblock' do context 'when admin' do - subject(:unblock_user) { post api("/users/#{user_id}/unblock", admin) } + subject(:unblock_user) { post api("/users/#{user_id}/unblock", admin, admin_mode: true) } context 'with an existing user' do let(:user_id) { user.id } @@ -3824,9 +3826,9 @@ RSpec.describe API::Users, feature_category: :user_profile do end end - describe 'POST /users/:id/ban', :aggregate_failures do + describe 'POST /users/:id/ban' do context 'when admin' do - subject(:ban_user) { post api("/users/#{user_id}/ban", admin) } + subject(:ban_user) { post api("/users/#{user_id}/ban", admin, admin_mode: true) } context 'with an active user' do let(:user_id) { user.id } @@ -3906,9 +3908,9 @@ RSpec.describe API::Users, feature_category: :user_profile do end end - describe 'POST /users/:id/unban', :aggregate_failures do + describe 'POST /users/:id/unban' do context 'when admin' do - subject(:unban_user) { post api("/users/#{user_id}/unban", admin) } + subject(:unban_user) { post api("/users/#{user_id}/unban", admin, admin_mode: true) } context 'with a banned user' do let(:user_id) { banned_user.id } @@ -4009,7 +4011,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let(:requesting_user) { create(:user, :admin) } it "responses successfully" do - get api("/users/#{user.id}/memberships", requesting_user) + get api("/users/#{user.id}/memberships", requesting_user, admin_mode: true) aggregate_failures 'expect successful response including groups and projects' do expect(response).to have_gitlab_http_status(:ok) @@ -4022,6 +4024,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end end + # why does this work without admin_mode? it 'does not submit N+1 DB queries' do # Avoid setup queries get api("/users/#{user.id}/memberships", requesting_user) @@ -4039,7 +4042,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'with type filter' do it "only returns project memberships" do - get api("/users/#{user.id}/memberships?type=Project", requesting_user) + get api("/users/#{user.id}/memberships?type=Project", requesting_user, admin_mode: true) aggregate_failures do expect(json_response).to contain_exactly(a_hash_including('source_type' => 'Project')) @@ -4048,7 +4051,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "only returns group memberships" do - get api("/users/#{user.id}/memberships?type=Namespace", requesting_user) + get api("/users/#{user.id}/memberships?type=Namespace", requesting_user, admin_mode: true) aggregate_failures do expect(json_response).to contain_exactly(a_hash_including('source_type' => 'Namespace')) @@ -4057,7 +4060,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it "recognizes unsupported types" do - get api("/users/#{user.id}/memberships?type=foo", requesting_user) + get api("/users/#{user.id}/memberships?type=foo", requesting_user, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) end @@ -4079,7 +4082,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'as admin' do it 'returns the activities from the last 6 months' do - get api("/user/activities", admin) + get api("/user/activities", admin, admin_mode: true) expect(response).to include_pagination_headers expect(json_response.size).to eq(1) @@ -4093,7 +4096,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'passing a :from parameter' do it 'returns the activities from the given date' do - get api("/user/activities?from=2000-1-1", admin) + get api("/user/activities?from=2000-1-1", admin, admin_mode: true) expect(response).to include_pagination_headers expect(json_response.size).to eq(2) @@ -4276,14 +4279,14 @@ RSpec.describe API::Users, feature_category: :user_profile do let(:scopes) { %w(api read_user) } it 'returns error if required attributes are missing' do - post api("/users/#{user.id}/personal_access_tokens", admin) + post api("/users/#{user.id}/personal_access_tokens", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq('name is missing, scopes is missing, scopes does not have a valid value') end it 'returns a 404 error if user not found' do - post api("/users/#{non_existing_record_id}/personal_access_tokens", admin), + post api("/users/#{non_existing_record_id}/personal_access_tokens", admin, admin_mode: true), params: { name: name, scopes: scopes, @@ -4319,7 +4322,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'creates a personal access token when authenticated as admin' do - post api("/users/#{user.id}/personal_access_tokens", admin), + post api("/users/#{user.id}/personal_access_tokens", admin, admin_mode: true), params: { name: name, expires_at: expires_at, @@ -4338,7 +4341,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end context 'when an error is thrown by the model' do - let!(:admin_personal_access_token) { create(:personal_access_token, user: admin) } + let!(:admin_personal_access_token) { create(:personal_access_token, :admin_mode, user: admin) } let(:error_message) { 'error message' } before do @@ -4372,7 +4375,7 @@ RSpec.describe API::Users, feature_category: :user_profile do let_it_be(:revoked_impersonation_token) { create(:personal_access_token, :impersonation, :revoked, user: user) } it 'returns a 404 error if user not found' do - get api("/users/#{non_existing_record_id}/impersonation_tokens", admin) + get api("/users/#{non_existing_record_id}/impersonation_tokens", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') @@ -4386,7 +4389,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'returns an array of all impersonated tokens' do - get api("/users/#{user.id}/impersonation_tokens", admin) + get api("/users/#{user.id}/impersonation_tokens", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -4395,7 +4398,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'returns an array of active impersonation tokens if state active' do - get api("/users/#{user.id}/impersonation_tokens?state=active", admin) + get api("/users/#{user.id}/impersonation_tokens?state=active", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -4405,7 +4408,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'returns an array of inactive personal access tokens if active is set to false' do - get api("/users/#{user.id}/impersonation_tokens?state=inactive", admin) + get api("/users/#{user.id}/impersonation_tokens?state=inactive", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_an Array @@ -4421,14 +4424,14 @@ RSpec.describe API::Users, feature_category: :user_profile do let(:impersonation) { true } it 'returns validation error if impersonation token misses some attributes' do - post api("/users/#{user.id}/impersonation_tokens", admin) + post api("/users/#{user.id}/impersonation_tokens", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq('name is missing') end it 'returns a 404 error if user not found' do - post api("/users/#{non_existing_record_id}/impersonation_tokens", admin), + post api("/users/#{non_existing_record_id}/impersonation_tokens", admin, admin_mode: true), params: { name: name, expires_at: expires_at @@ -4450,7 +4453,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'creates a impersonation token' do - post api("/users/#{user.id}/impersonation_tokens", admin), + post api("/users/#{user.id}/impersonation_tokens", admin, admin_mode: true), params: { name: name, expires_at: expires_at, @@ -4476,21 +4479,21 @@ RSpec.describe API::Users, feature_category: :user_profile do let_it_be(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) } it 'returns 404 error if user not found' do - get api("/users/#{non_existing_record_id}/impersonation_tokens/1", admin) + get api("/users/#{non_existing_record_id}/impersonation_tokens/1", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end it 'returns a 404 error if impersonation token not found' do - get api("/users/#{user.id}/impersonation_tokens/#{non_existing_record_id}", admin) + get api("/users/#{user.id}/impersonation_tokens/#{non_existing_record_id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Impersonation Token Not Found') end it 'returns a 404 error if token is not impersonation token' do - get api("/users/#{user.id}/impersonation_tokens/#{personal_access_token.id}", admin) + get api("/users/#{user.id}/impersonation_tokens/#{personal_access_token.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Impersonation Token Not Found') @@ -4504,7 +4507,7 @@ RSpec.describe API::Users, feature_category: :user_profile do end it 'returns an impersonation token' do - get api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin) + get api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response['token']).not_to be_present @@ -4517,21 +4520,21 @@ RSpec.describe API::Users, feature_category: :user_profile do let_it_be(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) } it 'returns a 404 error if user not found' do - delete api("/users/#{non_existing_record_id}/impersonation_tokens/1", admin) + delete api("/users/#{non_existing_record_id}/impersonation_tokens/1", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 User Not Found') end it 'returns a 404 error if impersonation token not found' do - delete api("/users/#{user.id}/impersonation_tokens/#{non_existing_record_id}", admin) + delete api("/users/#{user.id}/impersonation_tokens/#{non_existing_record_id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Impersonation Token Not Found') end it 'returns a 404 error if token is not impersonation token' do - delete api("/users/#{user.id}/impersonation_tokens/#{personal_access_token.id}", admin) + delete api("/users/#{user.id}/impersonation_tokens/#{personal_access_token.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) expect(json_response['message']).to eq('404 Impersonation Token Not Found') @@ -4545,11 +4548,11 @@ RSpec.describe API::Users, feature_category: :user_profile do end it_behaves_like '412 response' do - let(:request) { api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin) } + let(:request) { api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin, admin_mode: true) } end it 'revokes a impersonation token' do - delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin) + delete api("/users/#{user.id}/impersonation_tokens/#{impersonation_token.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:no_content) expect(impersonation_token.revoked).to be_falsey @@ -4607,7 +4610,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'as an admin user' do context 'with invalid user id' do it 'returns 404 User Not Found' do - get api("/users/#{non_existing_record_id}/associations_count", admin) + get api("/users/#{non_existing_record_id}/associations_count", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:not_found) end @@ -4615,7 +4618,7 @@ RSpec.describe API::Users, feature_category: :user_profile do context 'with valid user id' do it 'returns valid JSON response' do - get api("/users/#{user.id}/associations_count", admin) + get api("/users/#{user.id}/associations_count", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_a Hash diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb index 0b8fac5c55c..4a7b552293c 100644 --- a/spec/requests/api/v3/github_spec.rb +++ b/spec/requests/api/v3/github_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::V3::Github, feature_category: :integrations do +RSpec.describe API::V3::Github, :aggregate_failures, feature_category: :integrations do let_it_be(:user) { create(:user) } let_it_be(:unauthorized_user) { create(:user) } let_it_be(:admin) { create(:user, :admin) } @@ -300,7 +300,7 @@ RSpec.describe API::V3::Github, feature_category: :integrations do context 'when instance admin' do it 'returns the requested merge request in github format' do - jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/pulls/#{merge_request.id}", admin) + jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/pulls/#{merge_request.id}", admin, admin_mode: true) expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('entities/github/pull_request') @@ -312,8 +312,8 @@ RSpec.describe API::V3::Github, feature_category: :integrations do describe 'GET /users/:namespace/repos' do let(:group) { create(:group, name: 'foo') } - def expect_project_under_namespace(projects, namespace, user) - jira_get v3_api("/users/#{namespace.path}/repos", user) + def expect_project_under_namespace(projects, namespace, user, admin_mode = false) + jira_get v3_api("/users/#{namespace.path}/repos", user, admin_mode: admin_mode) expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers @@ -343,7 +343,7 @@ RSpec.describe API::V3::Github, feature_category: :integrations do let(:user) { create(:user, :admin) } it 'returns an array of projects belonging to group' do - expect_project_under_namespace([project, project2], group, user) + expect_project_under_namespace([project, project2], group, user, true) end context 'with a private group' do @@ -351,7 +351,7 @@ RSpec.describe API::V3::Github, feature_category: :integrations do let!(:project2) { create(:project, :private, group: group) } it 'returns an array of projects belonging to group' do - expect_project_under_namespace([project, project2], group, user) + expect_project_under_namespace([project, project2], group, user, true) end end end @@ -473,7 +473,7 @@ RSpec.describe API::V3::Github, feature_category: :integrations do expect(response).to have_gitlab_http_status(:ok) end - context 'when the project has no repository', :aggregate_failures do + context 'when the project has no repository' do let_it_be(:project) { create(:project, creator: user) } it 'returns an empty collection response' do @@ -516,7 +516,7 @@ RSpec.describe API::V3::Github, feature_category: :integrations do end context 'authenticated' do - it 'returns commit with github format', :aggregate_failures do + it 'returns commit with github format' do call_api expect(response).to have_gitlab_http_status(:ok) @@ -552,7 +552,7 @@ RSpec.describe API::V3::Github, feature_category: :integrations do .and_call_original end - it 'handles the error, logs it, and returns empty diff files', :aggregate_failures do + it 'handles the error, logs it, and returns empty diff files' do allow(Gitlab::GitalyClient).to receive(:call) .with(*commit_diff_args) .and_raise(GRPC::DeadlineExceeded) @@ -567,7 +567,7 @@ RSpec.describe API::V3::Github, feature_category: :integrations do expect(response_diff_files(response)).to be_blank end - it 'only calls Gitaly once for all attempts within a period of time', :aggregate_failures do + it 'only calls Gitaly once for all attempts within a period of time' do expect(Gitlab::GitalyClient).to receive(:call) .with(*commit_diff_args) .once # <- once @@ -581,7 +581,7 @@ RSpec.describe API::V3::Github, feature_category: :integrations do end end - it 'calls Gitaly again after a period of time', :aggregate_failures do + it 'calls Gitaly again after a period of time' do expect(Gitlab::GitalyClient).to receive(:call) .with(*commit_diff_args) .twice # <- twice @@ -648,13 +648,14 @@ RSpec.describe API::V3::Github, feature_category: :integrations do get path, headers: { 'User-Agent' => user_agent } end - def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil) + def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil, admin_mode: false) api( path, user, version: 'v3', personal_access_token: personal_access_token, - oauth_access_token: oauth_access_token + oauth_access_token: oauth_access_token, + admin_mode: admin_mode ) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f2a16ae3452..b48ad59f7d3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -367,14 +367,6 @@ RSpec.configure do |config| ./spec/requests/api/project_snapshots_spec.rb ./spec/requests/api/project_snippets_spec.rb ./spec/requests/api/projects_spec.rb - ./spec/requests/api/snippets_spec.rb - ./spec/requests/api/statistics_spec.rb - ./spec/requests/api/system_hooks_spec.rb - ./spec/requests/api/topics_spec.rb - ./spec/requests/api/usage_data_non_sql_metrics_spec.rb - ./spec/requests/api/usage_data_queries_spec.rb - ./spec/requests/api/users_spec.rb - ./spec/requests/api/v3/github_spec.rb ./spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb ./spec/support/shared_examples/requests/api/hooks_shared_examples.rb ./spec/support/shared_examples/requests/api/notes_shared_examples.rb diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb index 59f566f97d7..219943ede5f 100644 --- a/spec/support/shared_examples/features/runners_shared_examples.rb +++ b/spec/support/shared_examples/features/runners_shared_examples.rb @@ -63,16 +63,15 @@ RSpec.shared_examples 'shows and resets runner registration token' do end RSpec.shared_examples 'shows no runners registered' do - it 'shows total count with 0' do + it 'shows 0 count and the empty state' do expect(find('[data-testid="runner-type-tabs"]')).to have_text "#{s_('Runners|All')} 0" # No stats are shown expect(page).not_to have_text s_('Runners|Online') expect(page).not_to have_text s_('Runners|Offline') expect(page).not_to have_text s_('Runners|Stale') - end - it 'shows "no runners" message' do + # "no runners" message expect(page).to have_text s_('Runners|Get started with runners') end end @@ -84,16 +83,14 @@ RSpec.shared_examples 'shows no runners found' do end RSpec.shared_examples 'shows runner in list' do - it 'does not show empty state' do - expect(page).not_to have_content s_('Runners|Get started with runners') - end - - it 'shows runner row' do + it 'shows runner row and no empty state' do within_runner_row(runner.id) do expect(page).to have_text "##{runner.id}" expect(page).to have_text runner.short_sha expect(page).to have_text runner.description end + + expect(page).not_to have_content s_('Runners|Get started with runners') end end diff --git a/yarn.lock b/yarn.lock index d51d8a7a545..885eaeeffd5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -338,7 +338,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.4", "@babel/parser@^7.16.8", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.4", "@babel/parser@^7.16.8", "@babel/parser@^7.18.4", "@babel/parser@^7.18.10", "@babel/parser@^7.19.0": version "7.21.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3" integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== @@ -2477,6 +2477,15 @@ postcss "^8.1.10" source-map "^0.6.1" +"@vue/compiler-sfc@2.7.14": + version "2.7.14" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz#3446fd2fbb670d709277fc3ffa88efc5e10284fd" + integrity sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA== + dependencies: + "@babel/parser" "^7.18.4" + postcss "^8.4.14" + source-map "^0.6.1" + "@vue/compiler-ssr@3.2.47": version "3.2.47" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz#35872c01a273aac4d6070ab9d8da918ab13057ee" @@ -4314,6 +4323,11 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" +csstype@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== + custom-jquery-matchers@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/custom-jquery-matchers/-/custom-jquery-matchers-2.1.0.tgz#e5988fa9715c416b0986b372563f872d9e91e024" @@ -4879,7 +4893,7 @@ dayjs@^1.10.4: de-indent@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" - integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= + integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" @@ -6684,7 +6698,7 @@ hastscript@^7.0.0: property-information "^6.0.0" space-separated-tokens "^2.0.0" -he@^1.1.0: +he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -12428,10 +12442,10 @@ vue-hot-reload-api@^2.3.0: hash-sum "^2.0.0" loader-utils "^2.0.0" -vue-loader@15.9.6: - version "15.9.6" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.6.tgz#f4bb9ae20c3a8370af3ecf09b8126d38ffdb6b8b" - integrity sha512-j0cqiLzwbeImIC6nVIby2o/ABAWhlppyL/m5oJ67R5MloP0hj/DtFgb0Zmq3J9CG7AJ+AXIvHVnJAPBvrLyuDg== +vue-loader@15.10.1: + version "15.10.1" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.10.1.tgz#c451c4cd05a911aae7b5dbbbc09fb913fb3cca18" + integrity sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA== dependencies: "@vue/component-compiler-utils" "^3.1.0" hash-sum "^1.0.2" @@ -12469,13 +12483,13 @@ vue-style-loader@^4.1.0: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@2.6.14: - version "2.6.14" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763" - integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g== +vue-template-compiler@2.7.14: + version "2.7.14" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz#4545b7dfb88090744c1577ae5ac3f964e61634b1" + integrity sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ== dependencies: de-indent "^1.0.2" - he "^1.1.0" + he "^1.2.0" vue-template-es2015-compiler@^1.9.0: version "1.9.1" @@ -12492,10 +12506,13 @@ vue-virtual-scroll-list@^1.4.7: resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.4.7.tgz#12ee26833885f5bb4d37dc058085ccf3ce5b5a74" integrity sha512-R8bk+k7WMGGoFQ9xF0krGCAlZhQjbJOkDUX+YZD2J+sHQWTzDtmTLS6kiIJToOHK1d/8QPGiD8fd9w0lDP4arg== -vue@2.6.14: - version "2.6.14" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235" - integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ== +vue@2.7.14: + version "2.7.14" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.14.tgz#3743dcd248fd3a34d421ae456b864a0246bafb17" + integrity sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ== + dependencies: + "@vue/compiler-sfc" "2.7.14" + csstype "^3.1.0" vuedraggable@^2.23.0: version "2.23.0" |