diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-27 12:10:54 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-27 12:10:54 +0000 |
commit | 7a20b3758e651fe79032a5165db2208183877317 (patch) | |
tree | ca4964f3e851cd4b77879652aec225ea5daa1ca4 | |
parent | 2458ea514066142e3ca8e5131e44925398902a77 (diff) | |
download | gitlab-ce-7a20b3758e651fe79032a5165db2208183877317.tar.gz |
Add latest changes from gitlab-org/gitlab@master
57 files changed, 977 insertions, 563 deletions
diff --git a/app/assets/images/gitorious-logo-black.png b/app/assets/images/gitorious-logo-black.png Binary files differdeleted file mode 100644 index 4a55fdc225a..00000000000 --- a/app/assets/images/gitorious-logo-black.png +++ /dev/null diff --git a/app/assets/images/gitorious-logo-blue.png b/app/assets/images/gitorious-logo-blue.png Binary files differdeleted file mode 100644 index 5eaa327d3df..00000000000 --- a/app/assets/images/gitorious-logo-blue.png +++ /dev/null diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index dde5ea81e9a..be5f4b09c3e 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -83,6 +83,7 @@ export default { idState() { return { isLoadingCollapsedDiff: false, + hasLoadedCollapsedDiff: false, forkMessageVisible: false, hasToggled: false, }; @@ -181,7 +182,13 @@ export default { }, 'file.file_hash': { handler: function hashChangeWatch(newHash, oldHash) { - if (newHash && oldHash && !this.hasDiff && !this.preRender) { + if ( + newHash && + oldHash && + !this.hasDiff && + !this.preRender && + !this.idState.hasLoadedCollapsedDiff + ) { this.requestDiff(); } }, @@ -265,14 +272,22 @@ export default { } }, requestDiff() { - this.idState.isLoadingCollapsedDiff = true; + const { idState, file } = this; - this.loadCollapsedDiff(this.file) + idState.isLoadingCollapsedDiff = true; + + this.loadCollapsedDiff(file) .then(() => { - this.idState.isLoadingCollapsedDiff = false; - this.setRenderIt(this.file); + idState.isLoadingCollapsedDiff = false; + idState.hasLoadedCollapsedDiff = true; + + if (this.file.file_hash === file.file_hash) { + this.setRenderIt(this.file); + } }) .then(() => { + if (this.file.file_hash !== file.file_hash) return; + requestIdleCallback( () => { this.postRender(); @@ -282,7 +297,7 @@ export default { ); }) .catch(() => { - this.idState.isLoadingCollapsedDiff = false; + idState.isLoadingCollapsedDiff = false; createFlash({ message: this.$options.i18n.genericError, }); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 7f5e22c397b..f63bf8f49cb 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -131,138 +131,136 @@ function deferredInitialisation() { setTimeout(() => $body.addClass('page-initialised'), 1000); } -document.addEventListener('DOMContentLoaded', () => { - const $body = $('body'); - const $document = $(document); - const bootstrapBreakpoint = bp.getBreakpointSize(); - - initUserTracking(); - initLayoutNav(); - initAlertHandler(); - - // Set the default path for all cookies to GitLab's root directory - Cookies.defaults.path = gon.relative_url_root || '/'; - - // `hashchange` is not triggered when link target is already in window.location - $body.on('click', 'a[href^="#"]', function clickHashLinkCallback() { - const href = this.getAttribute('href'); - if (href.substr(1) === getLocationHash()) { - setTimeout(handleLocationHash, 1); - } - }); +const $body = $('body'); +const $document = $(document); +const bootstrapBreakpoint = bp.getBreakpointSize(); + +initUserTracking(); +initLayoutNav(); +initAlertHandler(); + +// Set the default path for all cookies to GitLab's root directory +Cookies.defaults.path = gon.relative_url_root || '/'; + +// `hashchange` is not triggered when link target is already in window.location +$body.on('click', 'a[href^="#"]', function clickHashLinkCallback() { + const href = this.getAttribute('href'); + if (href.substr(1) === getLocationHash()) { + setTimeout(handleLocationHash, 1); + } +}); - /** - * TODO: Apparently we are collapsing the right sidebar on certain screensizes per default - * except on issue board pages. Why can't we do it with CSS? - * - * Proposal: Expose a global sidebar API, which we could import wherever we are manipulating - * the visibility of the sidebar. - * - * Quick fix: Get rid of jQuery for this implementation - */ - const isBoardsPage = /(projects|groups):boards:show/.test(document.body.dataset.page); - if (!isBoardsPage && (bootstrapBreakpoint === 'sm' || bootstrapBreakpoint === 'xs')) { - const $rightSidebar = $('aside.right-sidebar'); - const $layoutPage = $('.layout-page'); - - if ($rightSidebar.length > 0) { - $rightSidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); - $layoutPage.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); - } else { - $layoutPage.removeClass('right-sidebar-expanded right-sidebar-collapsed'); - } +/** + * TODO: Apparently we are collapsing the right sidebar on certain screensizes per default + * except on issue board pages. Why can't we do it with CSS? + * + * Proposal: Expose a global sidebar API, which we could import wherever we are manipulating + * the visibility of the sidebar. + * + * Quick fix: Get rid of jQuery for this implementation + */ +const isBoardsPage = /(projects|groups):boards:show/.test(document.body.dataset.page); +if (!isBoardsPage && (bootstrapBreakpoint === 'sm' || bootstrapBreakpoint === 'xs')) { + const $rightSidebar = $('aside.right-sidebar'); + const $layoutPage = $('.layout-page'); + + if ($rightSidebar.length > 0) { + $rightSidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); + $layoutPage.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); + } else { + $layoutPage.removeClass('right-sidebar-expanded right-sidebar-collapsed'); } +} - // prevent default action for disabled buttons - $('.btn').click(function clickDisabledButtonCallback(e) { - if ($(this).hasClass('disabled')) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } +// prevent default action for disabled buttons +$('.btn').click(function clickDisabledButtonCallback(e) { + if ($(this).hasClass('disabled')) { + e.preventDefault(); + e.stopImmediatePropagation(); + return false; + } - return true; - }); + return true; +}); - localTimeAgo(document.querySelectorAll('abbr.timeago, .js-timeago'), true); - - /** - * This disables form buttons while a form is submitting - * We do not difinitively know all of the places where this is used - * - * TODO: Defer execution, migrate to behaviors, and add sentry logging - */ - $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function ajaxCompleteCallback(e) { - const $buttons = $('[type="submit"], .js-disable-on-submit', this).not('.js-no-auto-disable'); - switch (e.type) { - case 'ajax:beforeSend': - case 'submit': - return $buttons.disable(); - default: - return $buttons.enable(); - } - }); +localTimeAgo(document.querySelectorAll('abbr.timeago, .js-timeago'), true); + +/** + * This disables form buttons while a form is submitting + * We do not difinitively know all of the places where this is used + * + * TODO: Defer execution, migrate to behaviors, and add sentry logging + */ +$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function ajaxCompleteCallback(e) { + const $buttons = $('[type="submit"], .js-disable-on-submit', this).not('.js-no-auto-disable'); + switch (e.type) { + case 'ajax:beforeSend': + case 'submit': + return $buttons.disable(); + default: + return $buttons.enable(); + } +}); - $('.navbar-toggler').on('click', () => { - // The order is important. The `menu-expanded` is used as a source of truth for now. - // This can be simplified when the :combined_menu feature flag is removed. - // https://gitlab.com/gitlab-org/gitlab/-/issues/333180 - $('.header-content').toggleClass('menu-expanded'); - navEventHub.$emit(EVENT_RESPONSIVE_TOGGLE); - }); +$('.navbar-toggler').on('click', () => { + // The order is important. The `menu-expanded` is used as a source of truth for now. + // This can be simplified when the :combined_menu feature flag is removed. + // https://gitlab.com/gitlab-org/gitlab/-/issues/333180 + $('.header-content').toggleClass('menu-expanded'); + navEventHub.$emit(EVENT_RESPONSIVE_TOGGLE); +}); - /** - * Show suppressed commit diff - * - * TODO: Move to commit diff pages - */ - $document.on('click', '.diff-content .js-show-suppressed-diff', function showDiffCallback() { - const $container = $(this).parent(); - $container.next('table').show(); - $container.remove(); - }); +/** + * Show suppressed commit diff + * + * TODO: Move to commit diff pages + */ +$document.on('click', '.diff-content .js-show-suppressed-diff', function showDiffCallback() { + const $container = $(this).parent(); + $container.next('table').show(); + $container.remove(); +}); - // Show/hide comments on diff - $body.on('click', '.js-toggle-diff-comments', function toggleDiffCommentsCallback(e) { - const $this = $(this); - const notesHolders = $this.closest('.diff-file').find('.notes_holder'); +// Show/hide comments on diff +$body.on('click', '.js-toggle-diff-comments', function toggleDiffCommentsCallback(e) { + const $this = $(this); + const notesHolders = $this.closest('.diff-file').find('.notes_holder'); - e.preventDefault(); + e.preventDefault(); - $this.toggleClass('selected'); + $this.toggleClass('selected'); - if ($this.hasClass('active')) { - notesHolders.show().find('.hide, .content').show(); - } else { - notesHolders.hide().find('.content').hide(); - } + if ($this.hasClass('active')) { + notesHolders.show().find('.hide, .content').show(); + } else { + notesHolders.hide().find('.content').hide(); + } - $(document).trigger('toggle.comments'); - }); + $(document).trigger('toggle.comments'); +}); - $('form.filter-form').on('submit', function filterFormSubmitCallback(event) { - const link = document.createElement('a'); - link.href = this.action; +$('form.filter-form').on('submit', function filterFormSubmitCallback(event) { + const link = document.createElement('a'); + link.href = this.action; - const action = `${this.action}${link.search === '' ? '?' : '&'}`; + const action = `${this.action}${link.search === '' ? '?' : '&'}`; - event.preventDefault(); - // eslint-disable-next-line no-jquery/no-serialize - visitUrl(`${action}${$(this).serialize()}`); - }); + event.preventDefault(); + // eslint-disable-next-line no-jquery/no-serialize + visitUrl(`${action}${$(this).serialize()}`); +}); - const flashContainer = document.querySelector('.flash-container'); +const flashContainer = document.querySelector('.flash-container'); - if (flashContainer && flashContainer.children.length) { - flashContainer - .querySelectorAll('.flash-alert, .flash-notice, .flash-success') - .forEach((flashEl) => { - removeFlashClickListener(flashEl); - }); - } +if (flashContainer && flashContainer.children.length) { + flashContainer + .querySelectorAll('.flash-alert, .flash-notice, .flash-success') + .forEach((flashEl) => { + removeFlashClickListener(flashEl); + }); +} - // initialize field errors - $('.gl-show-field-errors').each((i, form) => new GlFieldErrors(form)); +// initialize field errors +$('.gl-show-field-errors').each((i, form) => new GlFieldErrors(form)); - requestIdleCallback(deferredInitialisation); -}); +requestIdleCallback(deferredInitialisation); diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue index 60edbb69666..7c157fe2775 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue @@ -1,6 +1,5 @@ <script> -/* eslint-disable vue/no-v-html */ -import { GlButton } from '@gitlab/ui'; +import { GlButton, GlSafeHtmlDirective } from '@gitlab/ui'; import { joinPaths } from '~/lib/utils/url_utility'; import { sprintf, s__ } from '../../../locale'; @@ -9,6 +8,9 @@ export default { components: { GlButton, }, + directives: { + SafeHtml: GlSafeHtmlDirective, + }, computed: { href() { return joinPaths(gon.relative_url_root || '', '/help/user/project/time_tracking.md'); @@ -40,8 +42,8 @@ export default { <div class="time-tracking-info"> <h4>{{ __('Track time with quick actions') }}</h4> <p>{{ __('Quick actions can be used in description and comment boxes.') }}</p> - <p v-html="estimateText"></p> - <p v-html="spendText"></p> + <p v-safe-html="estimateText"></p> + <p v-safe-html="spendText"></p> <gl-button :href="href">{{ __('Learn more') }}</gl-button> </div> </div> diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 85ee2204324..010b85e81bf 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -19,7 +19,7 @@ class JwtController < ApplicationController service = SERVICES[params[:service]] return head :not_found unless service - result = service.new(@authentication_result.project, @authentication_result.actor, auth_params) + result = service.new(@authentication_result.project, auth_user, auth_params) .execute(authentication_abilities: @authentication_result.authentication_abilities) render json: result, status: result[:http_status] @@ -67,7 +67,7 @@ class JwtController < ApplicationController end def additional_params - { scopes: scopes_param }.compact + { scopes: scopes_param, deploy_token: @authentication_result.deploy_token }.compact end # We have to parse scope here, because Docker Client does not send an array of scopes, @@ -83,8 +83,7 @@ class JwtController < ApplicationController def auth_user strong_memoize(:auth_user) do - actor = @authentication_result&.actor - actor.is_a?(User) ? actor : nil + @authentication_result.auth_user end end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index a3df566e4b3..2447a731167 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -447,6 +447,12 @@ module ApplicationSettingsHelper def signup_enabled? !!Gitlab::CurrentSettings.signup_enabled end + + def pending_user_count + return 0 if Gitlab::CurrentSettings.new_user_signups_cap.blank? + + User.blocked_pending_approval.count + end end ApplicationSettingsHelper.prepend_mod_with('ApplicationSettingsHelper') diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb index 745654a87be..ec6adc87bf4 100644 --- a/app/models/integrations/jira.rb +++ b/app/models/integrations/jira.rb @@ -539,8 +539,7 @@ module Integrations end def update_deployment_type? - (api_url_changed? || url_changed? || username_changed? || password_changed?) && - testable? + api_url_changed? || url_changed? || username_changed? || password_changed? end def update_deployment_type diff --git a/app/models/packages/debian.rb b/app/models/packages/debian.rb index e20f1b8244a..2daafe0ebcf 100644 --- a/app/models/packages/debian.rb +++ b/app/models/packages/debian.rb @@ -6,6 +6,8 @@ module Packages COMPONENT_REGEX = DISTRIBUTION_REGEX.freeze ARCHITECTURE_REGEX = %r{[a-z0-9][-a-z0-9]*}.freeze + LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze + def self.table_name_prefix 'packages_debian_' end diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index d42dcb2fd00..18515536ad7 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -21,7 +21,7 @@ module Auth return error('DENIED', status: 403, message: 'access forbidden') unless has_registry_ability? - unless scopes.any? || current_user || project + unless scopes.any? || current_user || deploy_token || project return error('DENIED', status: 403, message: 'access forbidden') end @@ -178,8 +178,7 @@ module Auth end def can_user?(ability, project) - user = current_user.is_a?(User) ? current_user : nil - can?(user, ability, project) + can?(current_user, ability, project) end def build_can_pull?(requested_project) @@ -202,16 +201,16 @@ module Auth def deploy_token_can_pull?(requested_project) has_authentication_ability?(:read_container_image) && - current_user.is_a?(DeployToken) && - current_user.has_access_to?(requested_project) && - current_user.read_registry? + deploy_token.present? && + deploy_token.has_access_to?(requested_project) && + deploy_token.read_registry? end def deploy_token_can_push?(requested_project) has_authentication_ability?(:create_container_image) && - current_user.is_a?(DeployToken) && - current_user.has_access_to?(requested_project) && - current_user.write_registry? + deploy_token.present? && + deploy_token.has_access_to?(requested_project) && + deploy_token.write_registry? end ## @@ -250,6 +249,10 @@ module Auth {} end + def deploy_token + params[:deploy_token] + end + def log_if_actions_denied(type, requested_project, requested_actions, authorized_actions) return if requested_actions == authorized_actions diff --git a/app/services/auth/dependency_proxy_authentication_service.rb b/app/services/auth/dependency_proxy_authentication_service.rb index fab42e0ebb6..4335fb0bd06 100644 --- a/app/services/auth/dependency_proxy_authentication_service.rb +++ b/app/services/auth/dependency_proxy_authentication_service.rb @@ -11,7 +11,7 @@ module Auth # Because app/controllers/concerns/dependency_proxy/auth.rb consumes this # JWT only as `User.find`, we currently only allow User (not DeployToken, etc) - return error('access forbidden', 403) unless current_user.is_a?(User) + return error('access forbidden', 403) unless current_user { token: authorized_token.encoded } end diff --git a/app/services/packages/debian/generate_distribution_service.rb b/app/services/packages/debian/generate_distribution_service.rb index 651325c49a0..caf1673e0a0 100644 --- a/app/services/packages/debian/generate_distribution_service.rb +++ b/app/services/packages/debian/generate_distribution_service.rb @@ -120,7 +120,7 @@ module Packages def package_filename(package_file) letter = package_file.package.name.start_with?('lib') ? package_file.package.name[0..3] : package_file.package.name[0] - "#{pool_prefix(package_file)}/#{letter}/#{package_file.package.name}/#{package_file.file_name}" + "#{pool_prefix(package_file)}/#{letter}/#{package_file.package.name}/#{package_file.package.version}/#{package_file.file_name}" end def pool_prefix(package_file) @@ -128,7 +128,7 @@ module Packages when ::Packages::Debian::GroupDistribution "pool/#{@distribution.codename}/#{package_file.package.project_id}" else - "pool/#{@distribution.codename}/#{@distribution.container_id}" + "pool/#{@distribution.codename}" end end diff --git a/app/views/admin/application_settings/_signup.html.haml b/app/views/admin/application_settings/_signup.html.haml index a5b47159239..a658ba63939 100644 --- a/app/views/admin/application_settings/_signup.html.haml +++ b/app/views/admin/application_settings/_signup.html.haml @@ -17,4 +17,5 @@ email_restrictions_enabled: @application_setting[:email_restrictions_enabled].to_s, supported_syntax_link_url: 'https://github.com/google/re2/wiki/Syntax', email_restrictions: @application_setting.email_restrictions, - after_sign_up_text: @application_setting[:after_sign_up_text] } } + after_sign_up_text: @application_setting[:after_sign_up_text], + pending_user_count: pending_user_count } } diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 4757f50739b..e515f1e7320 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -19,6 +19,7 @@ = render "archived_notice", project: @project = render_if_exists "projects/marked_for_deletion_notice", project: @project = render_if_exists "projects/ancestor_group_marked_for_deletion_notice", project: @project += render_if_exists 'projects/sast_entry_points', project: @project - view_path = @project.default_view diff --git a/config/application.rb b/config/application.rb index ffdb4d1b4d7..4c9c4711c66 100644 --- a/config/application.rb +++ b/config/application.rb @@ -214,6 +214,7 @@ module Gitlab config.assets.precompile << "page_bundles/jira_connect.css" config.assets.precompile << "page_bundles/jira_connect_users.css" config.assets.precompile << "page_bundles/learn_gitlab.css" + config.assets.precompile << "page_bundles/marketing_popover.css" config.assets.precompile << "page_bundles/members.css" config.assets.precompile << "page_bundles/merge_conflicts.css" config.assets.precompile << "page_bundles/merge_requests.css" diff --git a/config/feature_flags/development/gitaly_backup.yml b/config/feature_flags/development/gitaly_backup.yml index 4f7a0a4baf9..67552d39d92 100644 --- a/config/feature_flags/development/gitaly_backup.yml +++ b/config/feature_flags/development/gitaly_backup.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/333034 milestone: '14.0' type: development group: group::gitaly -default_enabled: false +default_enabled: true diff --git a/db/migrate/20210719145532_add_foreign_keys_view.rb b/db/migrate/20210719145532_add_foreign_keys_view.rb new file mode 100644 index 00000000000..2d31371e782 --- /dev/null +++ b/db/migrate/20210719145532_add_foreign_keys_view.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class AddForeignKeysView < ActiveRecord::Migration[6.1] + def up + execute(<<~SQL) + CREATE OR REPLACE VIEW postgres_foreign_keys AS + SELECT + pg_constraint.oid AS oid, + pg_constraint.conname AS name, + constrained_namespace.nspname::text || '.'::text || constrained_table.relname::text AS constrained_table_identifier, + referenced_namespace.nspname::text || '.'::text || referenced_table.relname::text AS referenced_table_identifier + FROM pg_constraint + INNER JOIN pg_class constrained_table ON constrained_table.oid = pg_constraint.conrelid + INNER JOIN pg_class referenced_table ON referenced_table.oid = pg_constraint.confrelid + INNER JOIN pg_namespace constrained_namespace ON constrained_table.relnamespace = constrained_namespace.oid + INNER JOIN pg_namespace referenced_namespace ON referenced_table.relnamespace = referenced_namespace.oid + WHERE contype = 'f'; + SQL + end + + def down + execute(<<~SQL) + DROP VIEW IF EXISTS postgres_foreign_keys + SQL + end +end diff --git a/db/schema_migrations/20210719145532 b/db/schema_migrations/20210719145532 new file mode 100644 index 00000000000..a9afd7a18ed --- /dev/null +++ b/db/schema_migrations/20210719145532 @@ -0,0 +1 @@ +5e088e5109b50d8f4fadd37a0382d7dc4ce856a851ec2b97f8d5d868c3cb19fd
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 6075cd812f9..c5d2b0806d6 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -16519,6 +16519,18 @@ CREATE SEQUENCE pool_repositories_id_seq ALTER SEQUENCE pool_repositories_id_seq OWNED BY pool_repositories.id; +CREATE VIEW postgres_foreign_keys AS + SELECT pg_constraint.oid, + pg_constraint.conname AS name, + (((constrained_namespace.nspname)::text || '.'::text) || (constrained_table.relname)::text) AS constrained_table_identifier, + (((referenced_namespace.nspname)::text || '.'::text) || (referenced_table.relname)::text) AS referenced_table_identifier + FROM ((((pg_constraint + JOIN pg_class constrained_table ON ((constrained_table.oid = pg_constraint.conrelid))) + JOIN pg_class referenced_table ON ((referenced_table.oid = pg_constraint.confrelid))) + JOIN pg_namespace constrained_namespace ON ((constrained_table.relnamespace = constrained_namespace.oid))) + JOIN pg_namespace referenced_namespace ON ((referenced_table.relnamespace = referenced_namespace.oid))) + WHERE (pg_constraint.contype = 'f'::"char"); + CREATE VIEW postgres_index_bloat_estimates AS SELECT (((relation_stats.nspname)::text || '.'::text) || (relation_stats.idxname)::text) AS identifier, ( diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index 7d433923865..4b361da6070 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -207,6 +207,59 @@ Sample response: } ``` +### Get a pipeline's test report summary + +> Introduced in [GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65471) + +NOTE: +This API route is part of the [Unit test report](../ci/unit_test_reports.md) feature. + +```plaintext +GET /projects/:id/pipelines/:pipeline_id/test_report_summary +``` + +| Attribute | Type | Required | Description | +|------------|---------|----------|---------------------| +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user | +| `pipeline_id` | integer | yes | The ID of a pipeline | + +Sample request: + +```shell +curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/pipelines/46/test_report_summary" +``` + +Sample response: + +```json +{ + "total": { + "time": 1904, + "count": 3363, + "success": 3351, + "failed": 0, + "skipped": 12, + "error": 0, + "suite_error": null + }, + "test_suites": [ + { + "name": "test", + "total_time": 1904, + "total_count": 3363, + "success_count": 3351, + "failed_count": 0, + "skipped_count": 12, + "error_count": 0, + "build_ids": [ + 66004 + ], + "suite_error": null + } + ] +} +``` + ## Create a new pipeline ```plaintext diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index d8f3a18577f..5e142d5c278 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -423,6 +423,40 @@ it('does something', () => { }); ``` +### Mocking the current location in Jest + +If your tests require `window.location.href` to take a particular value, use +the `setWindowLocation` helper: + +```javascript +import setWindowLocation from 'helpers/set_window_location'; + +it('passes', () => { + setWindowLocation('https://gitlab.test/foo?bar=true'); + + expect(window.location).toMatchObject({ + hostname: 'gitlab.test', + pathname: '/foo', + search: '?bar=true', + }); +}); +``` + +If your tests need to assert that certain `window.location` methods were +called, use the `useMockLocationHelper` helper: + +```javascript +import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; + +useMockLocationHelper(); + +it('passes', () => { + window.location.reload(); + + expect(window.location.reload).toHaveBeenCalled(); +}); +``` + ### Waiting in tests Sometimes a test needs to wait for something to happen in the application before it continues. diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index b393be18910..02afe8da6d7 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -1472,3 +1472,47 @@ If this happens, examine the following: - Confirm there is sufficient disk space for the Gzip operation. - If NFS is being used, check if the mount option `timeout` is set. The default is `600`, and changing this to smaller values results in this error. + +### `gitaly-backup` for repository backup and restore **(FREE SELF)** + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/333034) in GitLab 14.2. +> - [Deployed behind a feature flag](../user/feature_flags.md), enabled by default. +> - Recommended for production use. +> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-or-enable-gitaly-backup). + +There can be +[risks when disabling released features](../user/feature_flags.md#risks-when-disabling-released-features). +Refer to this feature's version history for more details. + +`gitaly-backup` is used by the backup Rake task to create and restore repository backups from Gitaly. +`gitaly-backup` replaces the previous backup method that directly calls RPCs on Gitaly from GitLab. + +The backup Rake task must be able to find this executable. It can be configured in Omnibus GitLab packages: + +1. Add the following to `/etc/gitlab/gitlab.rb`: + + ```ruby + gitlab_rails['backup_gitaly_backup_path'] = '/path/to/gitaly-backup' + ``` + +1. [Reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) + for the changes to take effect + +#### Disable or enable `gitaly-backup` + +`gitaly-backup` is under development but ready for production use. +It is deployed behind a feature flag that is **enabled by default**. +[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md) +can opt to disable it. + +To disable it: + +```ruby +Feature.disable(:gitaly_backup) +``` + +To enable it: + +```ruby +Feature.enable(:gitaly_backup) +``` diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 19222ef200b..78264b9fedb 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -187,6 +187,19 @@ module API present pipeline.test_reports, with: TestReportEntity, details: true end + desc 'Gets the test report summary for a given pipeline' do + detail 'This feature was introduced in GitLab 14.2' + success TestReportSummaryEntity + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + end + get ':id/pipelines/:pipeline_id/test_report_summary' do + authorize! :read_build, pipeline + + present pipeline.test_report_summary, with: TestReportSummaryEntity + end + desc 'Deletes a pipeline' do detail 'This feature was introduced in GitLab 11.6' http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']] diff --git a/lib/api/concerns/packages/debian_package_endpoints.rb b/lib/api/concerns/packages/debian_package_endpoints.rb index 7740ba6bfa6..e1a14c37d8c 100644 --- a/lib/api/concerns/packages/debian_package_endpoints.rb +++ b/lib/api/concerns/packages/debian_package_endpoints.rb @@ -6,8 +6,6 @@ module API module DebianPackageEndpoints extend ActiveSupport::Concern - LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze - PACKAGE_REGEX = API::NO_SLASH_URL_PART_REGEX DISTRIBUTION_REQUIREMENTS = { distribution: ::Packages::Debian::DISTRIBUTION_REGEX }.freeze @@ -15,14 +13,6 @@ module API component: ::Packages::Debian::COMPONENT_REGEX, architecture: ::Packages::Debian::ARCHITECTURE_REGEX }.freeze - COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS = { - component: ::Packages::Debian::COMPONENT_REGEX, - letter: LETTER_REGEX, - source_package: PACKAGE_REGEX - }.freeze - FILE_NAME_REQUIREMENTS = { - file_name: API::NO_SLASH_URL_PART_REGEX - }.freeze included do feature_category :package_registry @@ -31,109 +21,107 @@ module API helpers ::API::Helpers::Packages::BasicAuthHelpers include ::API::Helpers::Authentication - namespace 'packages/debian' do - authenticate_with do |accept| - accept.token_types(:personal_access_token, :deploy_token, :job_token) - .sent_through(:http_basic_auth) + helpers do + params :shared_package_file_params do + requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex + requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)' + requires :package_name, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex + requires :package_version, type: String, desc: 'The Debian Source Package Version', regexp: Gitlab::Regex.debian_version_regex + requires :file_name, type: String, desc: 'The Debian File Name' end - helpers do - def present_release_file - distribution = ::Packages::Debian::DistributionsFinder.new(project_or_group, codename_or_suite: params[:distribution]).execute.last! - - present_carrierwave_file!(distribution.file) - end + def distribution_from!(container) + ::Packages::Debian::DistributionsFinder.new(container, codename_or_suite: params[:distribution]).execute.last! end - format :txt - content_type :txt, 'text/plain' + def present_package_file! + not_found! unless params[:package_name].start_with?(params[:letter]) - params do - requires :distribution, type: String, desc: 'The Debian Codename', regexp: Gitlab::Regex.debian_distribution_regex + package_file = distribution_from!(user_project).package_files.with_file_name(params[:file_name]).last! + + present_carrierwave_file!(package_file.file) end + end - namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do - # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release.gpg - desc 'The Release file signature' do - detail 'This feature was introduced in GitLab 13.5' - end + authenticate_with do |accept| + accept.token_types(:personal_access_token, :deploy_token, :job_token) + .sent_through(:http_basic_auth) + end - route_setting :authentication, authenticate_non_public: true - get 'Release.gpg' do - not_found! - end + rescue_from ArgumentError do |e| + render_api_error!(e.message, 400) + end - # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release - desc 'The unsigned Release file' do - detail 'This feature was introduced in GitLab 13.5' - end + rescue_from ActiveRecord::RecordInvalid do |e| + render_api_error!(e.message, 400) + end - route_setting :authentication, authenticate_non_public: true - get 'Release' do - present_release_file - end + format :txt + content_type :txt, 'text/plain' - # GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease - desc 'The signed Release file' do - detail 'This feature was introduced in GitLab 13.5' - end + params do + requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex + end - route_setting :authentication, authenticate_non_public: true - get 'InRelease' do - # Signature to be added in 7.3 of https://gitlab.com/groups/gitlab-org/-/epics/6057#note_582697034 - present_release_file - end + namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do + # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release.gpg + desc 'The Release file signature' do + detail 'This feature was introduced in GitLab 13.5' + end - params do - requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex - requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex - end + route_setting :authentication, authenticate_non_public: true + get 'Release.gpg' do + not_found! + end - namespace ':component/binary-:architecture', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do - # GET {projects|groups}/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages - desc 'The binary files index' do - detail 'This feature was introduced in GitLab 13.5' - end - - route_setting :authentication, authenticate_non_public: true - get 'Packages' do - relation = "::Packages::Debian::#{project_or_group.class.name}ComponentFile".constantize - - component_file = relation - .preload_distribution - .with_container(project_or_group) - .with_codename_or_suite(params[:distribution]) - .with_component_name(params[:component]) - .with_file_type(:packages) - .with_architecture_name(params[:architecture]) - .with_compression_type(nil) - .order_created_asc - .last! - - present_carrierwave_file!(component_file.file) - end - end + # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release + desc 'The unsigned Release file' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, authenticate_non_public: true + get 'Release' do + present_carrierwave_file!(distribution_from!(project_or_group).file) + end + + # GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease + desc 'The signed Release file' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, authenticate_non_public: true + get 'InRelease' do + # Signature to be added in 7.3 of https://gitlab.com/groups/gitlab-org/-/epics/6057#note_582697034 + present_carrierwave_file!(distribution_from!(project_or_group).file) end params do requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex - requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)' - requires :source_package, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex + requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex end - namespace 'pool/:component/:letter/:source_package', requirements: COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS do - # GET {projects|groups}/:id/packages/debian/pool/:component/:letter/:source_package/:file_name - params do - requires :file_name, type: String, desc: 'The Debian File Name' - end - desc 'The package' do + namespace ':component/binary-:architecture', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do + # GET {projects|groups}/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages + desc 'The binary files index' do detail 'This feature was introduced in GitLab 13.5' end route_setting :authentication, authenticate_non_public: true - get ':file_name', requirements: FILE_NAME_REQUIREMENTS do - # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 - 'TODO File' + get 'Packages' do + relation = "::Packages::Debian::#{project_or_group.class.name}ComponentFile".constantize + + component_file = relation + .preload_distribution + .with_container(project_or_group) + .with_codename_or_suite(params[:distribution]) + .with_component_name(params[:component]) + .with_file_type(:packages) + .with_architecture_name(params[:architecture]) + .with_compression_type(nil) + .order_created_asc + .last! + + present_carrierwave_file!(component_file.file) end end end diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb index d4bf440051a..29f5047230a 100644 --- a/lib/api/debian_group_packages.rb +++ b/lib/api/debian_group_packages.rb @@ -2,20 +2,22 @@ module API class DebianGroupPackages < ::API::Base - params do - requires :id, type: String, desc: 'The ID of a group' - end + PACKAGE_FILE_REQUIREMENTS = ::API::DebianProjectPackages::PACKAGE_FILE_REQUIREMENTS.merge( + project_id: %r{[0-9]+}.freeze + ).freeze resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - rescue_from ArgumentError do |e| - render_api_error!(e.message, 400) - end + helpers do + def user_project + @project ||= find_project!(params[:project_id]) + end - rescue_from ActiveRecord::RecordInvalid do |e| - render_api_error!(e.message, 400) + def project_or_group + user_group + end end - before do + after_validation do require_packages_enabled! not_found! unless ::Feature.enabled?(:debian_group_packages, user_group) @@ -23,14 +25,27 @@ module API authorize_read_package!(user_group) end - namespace ':id/-' do - helpers do - def project_or_group - user_group - end - end + params do + requires :id, type: String, desc: 'The ID of a group' + end + namespace ':id/-/packages/debian' do include ::API::Concerns::Packages::DebianPackageEndpoints + + # GET groups/:id/packages/debian/pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name + params do + requires :project_id, type: Integer, desc: 'The Project Id' + use :shared_package_file_params + end + + desc 'The package' do + detail 'This feature was introduced in GitLab 14.2' + end + + route_setting :authentication, authenticate_non_public: true + get 'pool/:distribution/:project_id/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do + present_package_file! + end end end end diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index 70ddf9dea37..497ce2f4356 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -2,17 +2,23 @@ module API class DebianProjectPackages < ::API::Base - params do - requires :id, type: String, desc: 'The ID of a project' - end + PACKAGE_FILE_REQUIREMENTS = { + id: API::NO_SLASH_URL_PART_REGEX, + distribution: ::Packages::Debian::DISTRIBUTION_REGEX, + letter: ::Packages::Debian::LETTER_REGEX, + package_name: API::NO_SLASH_URL_PART_REGEX, + package_version: API::NO_SLASH_URL_PART_REGEX, + file_name: API::NO_SLASH_URL_PART_REGEX + }.freeze + FILE_NAME_REQUIREMENTS = { + file_name: API::NO_SLASH_URL_PART_REGEX + }.freeze resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - rescue_from ArgumentError do |e| - render_api_error!(e.message, 400) - end - - rescue_from ActiveRecord::RecordInvalid do |e| - render_api_error!(e.message, 400) + helpers do + def project_or_group + user_project + end end after_validation do @@ -23,20 +29,32 @@ module API authorize_read_package! end - namespace ':id' do - helpers do - def project_or_group - user_project - end - end + params do + requires :id, type: String, desc: 'The ID of a project' + end + namespace ':id/packages/debian' do include ::API::Concerns::Packages::DebianPackageEndpoints + # GET projects/:id/packages/debian/pool/:distribution/:letter/:package_name/:package_version/:file_name + params do + use :shared_package_file_params + end + + desc 'The package' do + detail 'This feature was introduced in GitLab 14.2' + end + + route_setting :authentication, authenticate_non_public: true + get 'pool/:distribution/:letter/:package_name/:package_version/:file_name', requirements: PACKAGE_FILE_REQUIREMENTS do + present_package_file! + end + params do requires :file_name, type: String, desc: 'The file name' end - namespace 'packages/debian/:file_name', requirements: FILE_NAME_REQUIREMENTS do + namespace ':file_name', requirements: FILE_NAME_REQUIREMENTS do format :txt content_type :json, Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb index da874524826..443f7c08e18 100644 --- a/lib/gitlab/auth/result.rb +++ b/lib/gitlab/auth/result.rb @@ -21,6 +21,14 @@ module Gitlab def failed? !success? end + + def auth_user + actor.is_a?(User) ? actor : nil + end + + def deploy_token + actor.is_a?(DeployToken) ? actor : nil + end end end end diff --git a/lib/gitlab/database/postgres_foreign_key.rb b/lib/gitlab/database/postgres_foreign_key.rb new file mode 100644 index 00000000000..94f74724295 --- /dev/null +++ b/lib/gitlab/database/postgres_foreign_key.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Gitlab + module Database + class PostgresForeignKey < ApplicationRecord + self.primary_key = :oid + + scope :by_referenced_table_identifier, ->(identifier) do + raise ArgumentError, "Referenced table name is not fully qualified with a schema: #{identifier}" unless identifier =~ /^\w+\.\w+$/ + + where(referenced_table_identifier: identifier) + end + end + end +end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index ed74dd472ff..1a65be04d09 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -297,7 +297,7 @@ namespace :gitlab do end def repository_backup_strategy - if Feature.enabled?(:gitaly_backup) + if Feature.enabled?(:gitaly_backup, default_enabled: :yaml) max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence max_storage_concurrency = ENV['GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY'].presence Backup::GitalyBackup.new(progress, parallel: max_concurrency, parallel_storage: max_storage_concurrency) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1adb3cefe79..231800642aa 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5234,9 +5234,6 @@ msgstr "" msgid "Billing|Type %{username} to confirm" msgstr "" -msgid "Billing|Type to search" -msgstr "" - msgid "Billing|User was successfully removed" msgstr "" @@ -14074,6 +14071,9 @@ msgstr "" msgid "Filter results..." msgstr "" +msgid "Filter users" +msgstr "" + msgid "Filter your repositories by name" msgstr "" @@ -19110,6 +19110,9 @@ msgstr "" msgid "Last Accessed On" msgstr "" +msgid "Last Activity" +msgstr "" + msgid "Last Pipeline" msgstr "" @@ -28492,6 +28495,21 @@ msgstr "" msgid "SVG illustration" msgstr "" +msgid "SastEntryPoints|Add Security Testing" +msgstr "" + +msgid "SastEntryPoints|Catch your security vulnerabilities ahead of time!" +msgstr "" + +msgid "SastEntryPoints|GitLab can scan your code for security vulnerabilities. Static Application Security Testing (SAST) helps you worry less and build more." +msgstr "" + +msgid "SastEntryPoints|How do I set up SAST?" +msgstr "" + +msgid "SastEntryPoints|Learn more." +msgstr "" + msgid "Satisfied" msgstr "" diff --git a/spec/frontend/__helpers__/set_window_location_helper.js b/spec/frontend/__helpers__/set_window_location_helper.js index a94e73762c9..573a089f111 100644 --- a/spec/frontend/__helpers__/set_window_location_helper.js +++ b/spec/frontend/__helpers__/set_window_location_helper.js @@ -1,40 +1,53 @@ /** - * setWindowLocation allows for setting `window.location` - * (doing so directly is causing an error in jsdom) + * setWindowLocation allows for setting `window.location` within Jest. * - * Example usage: - * assert(window.location.hash === undefined); - * setWindowLocation('http://example.com#foo') - * assert(window.location.hash === '#foo'); + * The jsdom environment at the time of writing does not support changing the + * current location (see + * https://github.com/jsdom/jsdom/blob/16.4.0/lib/jsdom/living/window/navigation.js#L76), + * hence this helper. * - * More information: - * https://github.com/facebook/jest/issues/890 + * This helper mutates the current `window.location` very similarly to how + * a direct assignment to `window.location.href` would in a browser (but + * without the navigation/reload behaviour). For instance: * - * @param url + * - Set the full href by passing an absolute URL, e.g.: + * + * setWindowLocation('https://gdk.test'); + * // window.location.href is now 'https://gdk.test' + * + * - Set the path, search and/or hash components by passing a relative URL: + * + * setWindowLocation('/foo/bar'); + * // window.location.href is now 'http://test.host/foo/bar' + * + * setWindowLocation('?foo=bar'); + * // window.location.href is now 'http://test.host/?foo=bar' + * + * setWindowLocation('#foo'); + * // window.location.href is now 'http://test.host/#foo' + * + * setWindowLocation('/a/b/foo.html?bar=1#qux'); + * // window.location.href is now 'http://test.host/a/b/foo.html?bar=1#qux + * + * Both approaches also automatically update the rest of the properties on + * `window.locaton`. For instance: + * + * setWindowLocation('http://test.host/a/b/foo.html?bar=1#qux'); + * // window.location.origin is now 'http://test.host' + * // window.location.pathname is now '/a/b/foo.html' + * // window.location.search is now '?bar=1' + * // window.location.searchParams is now { bar: 1 } + * // window.location.hash is now '#qux' + * + * @param {string} url A string representing an absolute or relative URL. + * @returns {undefined} */ export default function setWindowLocation(url) { - const parsedUrl = new URL(url); + if (typeof url !== 'string') { + throw new TypeError(`Expected string; got ${url} (${typeof url})`); + } - const newLocationValue = [ - 'hash', - 'host', - 'hostname', - 'href', - 'origin', - 'pathname', - 'port', - 'protocol', - 'search', - ].reduce( - (location, prop) => ({ - ...location, - [prop]: parsedUrl[prop], - }), - {}, - ); + const newUrl = new URL(url, window.location.href); - Object.defineProperty(window, 'location', { - value: newLocationValue, - writable: true, - }); + global.jsdom.reconfigure({ url: newUrl.href }); } diff --git a/spec/frontend/__helpers__/set_window_location_helper_spec.js b/spec/frontend/__helpers__/set_window_location_helper_spec.js index 98f26854822..b9c1d6f5e99 100644 --- a/spec/frontend/__helpers__/set_window_location_helper_spec.js +++ b/spec/frontend/__helpers__/set_window_location_helper_spec.js @@ -1,40 +1,106 @@ import setWindowLocation from './set_window_location_helper'; -describe('setWindowLocation', () => { - const originalLocation = window.location; +describe('helpers/set_window_location_helper', () => { + const originalLocation = window.location.href; - afterEach(() => { - window.location = originalLocation; + beforeEach(() => { + setWindowLocation(originalLocation); }); - it.each` - url | property | value - ${'https://gitlab.com#foo'} | ${'hash'} | ${'#foo'} - ${'http://gitlab.com'} | ${'host'} | ${'gitlab.com'} - ${'http://gitlab.org'} | ${'hostname'} | ${'gitlab.org'} - ${'http://gitlab.org/foo#bar'} | ${'href'} | ${'http://gitlab.org/foo#bar'} - ${'http://gitlab.com'} | ${'origin'} | ${'http://gitlab.com'} - ${'http://gitlab.com/foo/bar/baz'} | ${'pathname'} | ${'/foo/bar/baz'} - ${'https://gitlab.com'} | ${'protocol'} | ${'https:'} - ${'http://gitlab.com#foo'} | ${'protocol'} | ${'http:'} - ${'http://gitlab.com:8080'} | ${'port'} | ${'8080'} - ${'http://gitlab.com?foo=bar&bar=foo'} | ${'search'} | ${'?foo=bar&bar=foo'} - `( - 'sets "window.location.$property" to be "$value" when called with: "$url"', - ({ url, property, value }) => { - expect(window.location).toBe(originalLocation); - - setWindowLocation(url); - - expect(window.location[property]).toBe(value); - }, - ); - - it.each([null, 1, undefined, false, '', 'gitlab.com'])( - 'throws an error when called with an invalid url: "%s"', - (invalidUrl) => { - expect(() => setWindowLocation(invalidUrl)).toThrow(/Invalid URL/); - expect(window.location).toBe(originalLocation); - }, - ); + describe('setWindowLocation', () => { + describe('given a complete URL', () => { + it.each` + url | property | value + ${'https://gitlab.com#foo'} | ${'hash'} | ${'#foo'} + ${'http://gitlab.com'} | ${'host'} | ${'gitlab.com'} + ${'http://gitlab.org'} | ${'hostname'} | ${'gitlab.org'} + ${'http://gitlab.org/foo#bar'} | ${'href'} | ${'http://gitlab.org/foo#bar'} + ${'http://gitlab.com'} | ${'origin'} | ${'http://gitlab.com'} + ${'http://gitlab.com/foo/bar/baz'} | ${'pathname'} | ${'/foo/bar/baz'} + ${'https://gitlab.com'} | ${'protocol'} | ${'https:'} + ${'ftp://gitlab.com#foo'} | ${'protocol'} | ${'ftp:'} + ${'http://gitlab.com:8080'} | ${'port'} | ${'8080'} + ${'http://gitlab.com?foo=bar&bar=foo'} | ${'search'} | ${'?foo=bar&bar=foo'} + `( + 'sets "window.location.$property" to be "$value" when called with: "$url"', + ({ url, property, value }) => { + expect(window.location.href).toBe(originalLocation); + + setWindowLocation(url); + + expect(window.location[property]).toBe(value); + }, + ); + }); + + describe('given a partial URL', () => { + it.each` + partialURL | href + ${'//foo.test:3000/'} | ${'http://foo.test:3000/'} + ${'/foo/bar'} | ${`${originalLocation}foo/bar`} + ${'foo/bar'} | ${`${originalLocation}foo/bar`} + ${'?foo=bar'} | ${`${originalLocation}?foo=bar`} + ${'#a-thing'} | ${`${originalLocation}#a-thing`} + `('$partialURL sets location.href to $href', ({ partialURL, href }) => { + expect(window.location.href).toBe(originalLocation); + + setWindowLocation(partialURL); + + expect(window.location.href).toBe(href); + }); + }); + + describe('relative path', () => { + describe.each` + initialHref | path | newHref + ${'https://gdk.test/foo/bar'} | ${'/qux'} | ${'https://gdk.test/qux'} + ${'https://gdk.test/foo/bar/'} | ${'/qux'} | ${'https://gdk.test/qux'} + ${'https://gdk.test/foo/bar'} | ${'qux'} | ${'https://gdk.test/foo/qux'} + ${'https://gdk.test/foo/bar/'} | ${'qux'} | ${'https://gdk.test/foo/bar/qux'} + ${'https://gdk.test/foo/bar'} | ${'../qux'} | ${'https://gdk.test/qux'} + ${'https://gdk.test/foo/bar/'} | ${'../qux'} | ${'https://gdk.test/foo/qux'} + `('when location is $initialHref', ({ initialHref, path, newHref }) => { + beforeEach(() => { + setWindowLocation(initialHref); + }); + + it(`${path} sets window.location.href to ${newHref}`, () => { + expect(window.location.href).toBe(initialHref); + + setWindowLocation(path); + + expect(window.location.href).toBe(newHref); + }); + }); + }); + + it.each([null, 1, undefined, false, 'https://', 'https:', { foo: 1 }, []])( + 'throws an error when called with an invalid url: "%s"', + (invalidUrl) => { + expect(() => setWindowLocation(invalidUrl)).toThrow(); + expect(window.location.href).toBe(originalLocation); + }, + ); + + describe('affects links', () => { + it.each` + url | hrefAttr | expectedHref + ${'http://gitlab.com/'} | ${'foo'} | ${'http://gitlab.com/foo'} + ${'http://gitlab.com/bar/'} | ${'foo'} | ${'http://gitlab.com/bar/foo'} + ${'http://gitlab.com/bar/'} | ${'/foo'} | ${'http://gitlab.com/foo'} + ${'http://gdk.test:3000/?foo=bar'} | ${'?qux=1'} | ${'http://gdk.test:3000/?qux=1'} + ${'https://gdk.test:3000/?foo=bar'} | ${'//other.test'} | ${'https://other.test/'} + `( + 'given $url, <a href="$hrefAttr"> points to $expectedHref', + ({ url, hrefAttr, expectedHref }) => { + setWindowLocation(url); + + const link = document.createElement('a'); + link.setAttribute('href', hrefAttr); + + expect(link.href).toBe(expectedHref); + }, + ); + }); + }); }); diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js index 80a51ee137a..1697ea3e952 100644 --- a/spec/frontend/diffs/components/compare_versions_spec.js +++ b/spec/frontend/diffs/components/compare_versions_spec.js @@ -1,5 +1,6 @@ import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; import { trimText } from 'helpers/text_helper'; import CompareVersionsComponent from '~/diffs/components/compare_versions.vue'; @@ -13,6 +14,10 @@ localVue.use(Vuex); const NEXT_COMMIT_URL = `${TEST_HOST}/?commit_id=next`; const PREV_COMMIT_URL = `${TEST_HOST}/?commit_id=prev`; +beforeEach(() => { + setWindowLocation(TEST_HOST); +}); + describe('CompareVersions', () => { let wrapper; let store; @@ -215,15 +220,7 @@ describe('CompareVersions', () => { describe('prev commit', () => { beforeAll(() => { - global.jsdom.reconfigure({ - url: `${TEST_HOST}?commit_id=${mrCommit.id}`, - }); - }); - - afterAll(() => { - global.jsdom.reconfigure({ - url: TEST_HOST, - }); + setWindowLocation(`${TEST_HOST}?commit_id=${mrCommit.id}`); }); beforeEach(() => { @@ -258,15 +255,7 @@ describe('CompareVersions', () => { describe('next commit', () => { beforeAll(() => { - global.jsdom.reconfigure({ - url: `${TEST_HOST}?commit_id=${mrCommit.id}`, - }); - }); - - afterAll(() => { - global.jsdom.reconfigure({ - url: TEST_HOST, - }); + setWindowLocation(`${TEST_HOST}?commit_id=${mrCommit.id}`); }); beforeEach(() => { diff --git a/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js b/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js index 99f13a1c84c..6ea8f691c3c 100644 --- a/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js +++ b/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js @@ -1,3 +1,4 @@ +import setWindowLocation from 'helpers/set_window_location_helper'; import { DIFF_COMPARE_BASE_VERSION_INDEX, DIFF_COMPARE_HEAD_VERSION_INDEX, @@ -47,15 +48,12 @@ describe('Compare diff version dropdowns', () => { let expectedFirstVersion; let expectedBaseVersion; let expectedHeadVersion; - const originalLocation = window.location; + const originalLocation = window.location.href; const setupTest = (includeDiffHeadParam) => { const diffHeadParam = includeDiffHeadParam ? '?diff_head=true' : ''; - Object.defineProperty(window, 'location', { - writable: true, - value: { search: diffHeadParam }, - }); + setWindowLocation(diffHeadParam); expectedFirstVersion = { ...diffsMockData[1], @@ -91,7 +89,7 @@ describe('Compare diff version dropdowns', () => { }; afterEach(() => { - window.location = originalLocation; + setWindowLocation(originalLocation); }); it('base version selected', () => { diff --git a/spec/frontend/issues_list/components/issuables_list_app_spec.js b/spec/frontend/issues_list/components/issuables_list_app_spec.js index 86112dad444..5ef2a2e0525 100644 --- a/spec/frontend/issues_list/components/issuables_list_app_spec.js +++ b/spec/frontend/issues_list/components/issuables_list_app_spec.js @@ -6,6 +6,7 @@ import { import { shallowMount } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; @@ -27,11 +28,6 @@ const TEST_ENDPOINT = '/issues'; const TEST_CREATE_ISSUES_PATH = '/createIssue'; const TEST_SVG_PATH = '/emptySvg'; -const setUrl = (query) => { - window.location.href = `${TEST_LOCATION}${query}`; - window.location.search = query; -}; - const MOCK_ISSUES = Array(PAGE_SIZE_MANUAL) .fill(0) .map((_, i) => ({ @@ -40,7 +36,6 @@ const MOCK_ISSUES = Array(PAGE_SIZE_MANUAL) })); describe('Issuables list component', () => { - let oldLocation; let mockAxios; let wrapper; let apiSpy; @@ -75,19 +70,13 @@ describe('Issuables list component', () => { beforeEach(() => { mockAxios = new MockAdapter(axios); - oldLocation = window.location; - Object.defineProperty(window, 'location', { - writable: true, - value: { href: '', search: '' }, - }); - window.location.href = TEST_LOCATION; + setWindowLocation(TEST_LOCATION); }); afterEach(() => { wrapper.destroy(); wrapper = null; mockAxios.restore(); - window.location = oldLocation; }); describe('with failed issues response', () => { @@ -314,7 +303,7 @@ describe('Issuables list component', () => { '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&weight=0¬[label_name][]=Afterpod¬[milestone_title][]=13'; beforeEach(() => { - setUrl(query); + setWindowLocation(query); setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); factory({ sortKey: 'milestone_due_desc' }); @@ -358,7 +347,7 @@ describe('Issuables list component', () => { '?assignee_username=root&author_username=root&confidential=yes&label_name%5B%5D=Aquapod&label_name%5B%5D=Astro&milestone_title=v3.0&my_reaction_emoji=airplane&scope=all&sort=priority&state=opened&weight=0&page=3'; beforeEach(() => { - setUrl(query); + setWindowLocation(query); setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); factory({ sortKey: 'milestone_due_desc' }); @@ -387,7 +376,7 @@ describe('Issuables list component', () => { describe('with hash in window.location', () => { beforeEach(() => { - window.location.href = `${TEST_LOCATION}#stuff`; + setWindowLocation(`${TEST_LOCATION}#stuff`); setupApiMock(() => [200, MOCK_ISSUES.slice(0)]); factory(); return waitForPromises(); @@ -422,7 +411,7 @@ describe('Issuables list component', () => { describe('with query in window location', () => { beforeEach(() => { - window.location.search = '?weight=Any'; + setWindowLocation('?weight=Any'); factory(); @@ -436,7 +425,7 @@ describe('Issuables list component', () => { describe('with closed state', () => { beforeEach(() => { - window.location.search = '?state=closed'; + setWindowLocation('?state=closed'); factory(); @@ -450,7 +439,7 @@ describe('Issuables list component', () => { describe('with all state', () => { beforeEach(() => { - window.location.search = '?state=all'; + setWindowLocation('?state=all'); factory(); @@ -565,7 +554,7 @@ describe('Issuables list component', () => { }); it('sets value according to query', () => { - setUrl(query); + setWindowLocation(query); factory({ type: 'jira' }); @@ -583,7 +572,7 @@ describe('Issuables list component', () => { it('sets value according to query', () => { const query = '?search=free+text'; - setUrl(query); + setWindowLocation(query); factory({ type: 'jira' }); diff --git a/spec/frontend/issues_list/components/issues_list_app_spec.js b/spec/frontend/issues_list/components/issues_list_app_spec.js index 846236e1fb5..2f4803eac8c 100644 --- a/spec/frontend/issues_list/components/issues_list_app_spec.js +++ b/spec/frontend/issues_list/components/issues_list_app_spec.js @@ -7,6 +7,7 @@ import VueApollo from 'vue-apollo'; import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql'; import getIssuesCountQuery from 'ee_else_ce/issues_list/queries/get_issues_count.query.graphql'; import createMockApollo from 'helpers/mock_apollo_helper'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; import { @@ -42,7 +43,6 @@ import eventHub from '~/issues_list/eventhub'; import { getSortOptions } from '~/issues_list/utils'; import axios from '~/lib/utils/axios_utils'; import { scrollUp } from '~/lib/utils/scroll_utils'; -import { setUrlParams } from '~/lib/utils/url_utility'; jest.mock('~/flash'); jest.mock('~/lib/utils/scroll_utils', () => ({ @@ -115,11 +115,11 @@ describe('IssuesListApp component', () => { }; beforeEach(() => { + setWindowLocation(TEST_HOST); axiosMock = new AxiosMockAdapter(axios); }); afterEach(() => { - global.jsdom.reconfigure({ url: TEST_HOST }); axiosMock.reset(); wrapper.destroy(); }); @@ -186,7 +186,7 @@ describe('IssuesListApp component', () => { const search = '?search=refactor&sort=created_date&state=opened'; beforeEach(() => { - global.jsdom.reconfigure({ url: `${TEST_HOST}${search}` }); + setWindowLocation(search); wrapper = mountComponent({ provide: { ...defaultProvide, isSignedIn: true }, @@ -258,7 +258,7 @@ describe('IssuesListApp component', () => { describe('initial url params', () => { describe('due_date', () => { it('is set from the url params', () => { - global.jsdom.reconfigure({ url: `${TEST_HOST}?${PARAM_DUE_DATE}=${DUE_DATE_OVERDUE}` }); + setWindowLocation(`?${PARAM_DUE_DATE}=${DUE_DATE_OVERDUE}`); wrapper = mountComponent(); @@ -268,7 +268,7 @@ describe('IssuesListApp component', () => { describe('search', () => { it('is set from the url params', () => { - global.jsdom.reconfigure({ url: `${TEST_HOST}${locationSearch}` }); + setWindowLocation(locationSearch); wrapper = mountComponent(); @@ -278,9 +278,7 @@ describe('IssuesListApp component', () => { describe('sort', () => { it.each(Object.keys(urlSortParams))('is set as %s from the url params', (sortKey) => { - global.jsdom.reconfigure({ - url: setUrlParams({ sort: urlSortParams[sortKey] }, TEST_HOST), - }); + setWindowLocation(`?sort=${urlSortParams[sortKey]}`); wrapper = mountComponent(); @@ -297,7 +295,7 @@ describe('IssuesListApp component', () => { it('is set from the url params', () => { const initialState = IssuableStates.All; - global.jsdom.reconfigure({ url: setUrlParams({ state: initialState }, TEST_HOST) }); + setWindowLocation(`?state=${initialState}`); wrapper = mountComponent(); @@ -307,7 +305,7 @@ describe('IssuesListApp component', () => { describe('filter tokens', () => { it('is set from the url params', () => { - global.jsdom.reconfigure({ url: `${TEST_HOST}${locationSearch}` }); + setWindowLocation(locationSearch); wrapper = mountComponent(); @@ -347,7 +345,7 @@ describe('IssuesListApp component', () => { describe('when there are issues', () => { describe('when search returns no results', () => { beforeEach(() => { - global.jsdom.reconfigure({ url: `${TEST_HOST}?search=no+results` }); + setWindowLocation(`?search=no+results`); wrapper = mountComponent({ provide: { hasProjectIssues: true }, mountFn: mount }); }); @@ -377,9 +375,7 @@ describe('IssuesListApp component', () => { describe('when "Closed" tab has no issues', () => { beforeEach(() => { - global.jsdom.reconfigure({ - url: setUrlParams({ state: IssuableStates.Closed }, TEST_HOST), - }); + setWindowLocation(`?state=${IssuableStates.Closed}`); wrapper = mountComponent({ provide: { hasProjectIssues: true }, mountFn: mount }); }); diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index 66d0faa95e7..3e02b9b3066 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -1,3 +1,4 @@ +import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; import * as urlUtils from '~/lib/utils/url_utility'; @@ -16,24 +17,11 @@ const shas = { ], }; -const setWindowLocation = (value) => { - Object.defineProperty(window, 'location', { - writable: true, - value, - }); -}; +beforeEach(() => { + setWindowLocation(TEST_HOST); +}); describe('URL utility', () => { - let originalLocation; - - beforeAll(() => { - originalLocation = window.location; - }); - - afterAll(() => { - window.location = originalLocation; - }); - describe('webIDEUrl', () => { afterEach(() => { gon.relative_url_root = ''; @@ -68,14 +56,7 @@ describe('URL utility', () => { describe('getParameterValues', () => { beforeEach(() => { - setWindowLocation({ - href: 'https://gitlab.com?test=passing&multiple=1&multiple=2', - // make our fake location act like real window.location.toString - // URL() (used in getParameterValues) does this if passed an object - toString() { - return this.href; - }, - }); + setWindowLocation('https://gitlab.com?test=passing&multiple=1&multiple=2'); }); it('returns empty array for no params', () => { @@ -330,9 +311,7 @@ describe('URL utility', () => { describe('doesHashExistInUrl', () => { beforeEach(() => { - setWindowLocation({ - hash: 'https://gitlab.com/gitlab-org/gitlab-test/issues/1#note_1', - }); + setWindowLocation('#note_1'); }); it('should return true when the given string exists in the URL hash', () => { @@ -442,10 +421,7 @@ describe('URL utility', () => { describe('getBaseURL', () => { beforeEach(() => { - setWindowLocation({ - protocol: 'https:', - host: 'gitlab.com', - }); + setWindowLocation('https://gitlab.com'); }); it('returns correct base URL', () => { @@ -637,10 +613,7 @@ describe('URL utility', () => { ${'http:'} | ${'ws:'} ${'https:'} | ${'wss:'} `('returns "$expectation" with "$protocol" protocol', ({ protocol, expectation }) => { - setWindowLocation({ - protocol, - host: 'example.com', - }); + setWindowLocation(`${protocol}//example.com`); expect(urlUtils.getWebSocketProtocol()).toEqual(expectation); }); @@ -648,10 +621,7 @@ describe('URL utility', () => { describe('getWebSocketUrl', () => { it('joins location host to path', () => { - setWindowLocation({ - protocol: 'http:', - host: 'example.com', - }); + setWindowLocation('http://example.com'); const path = '/lorem/ipsum?a=bc'; @@ -724,32 +694,32 @@ describe('URL utility', () => { const { getParameterByName } = urlUtils; it('should return valid parameter', () => { - setWindowLocation({ search: '?scope=all&p=2' }); + setWindowLocation('?scope=all&p=2'); expect(getParameterByName('p')).toEqual('2'); expect(getParameterByName('scope')).toBe('all'); }); it('should return invalid parameter', () => { - setWindowLocation({ search: '?scope=all&p=2' }); + setWindowLocation('?scope=all&p=2'); expect(getParameterByName('fakeParameter')).toBe(null); }); it('should return a parameter with spaces', () => { - setWindowLocation({ search: '?search=my terms' }); + setWindowLocation('?search=my terms'); expect(getParameterByName('search')).toBe('my terms'); }); it('should return a parameter with encoded spaces', () => { - setWindowLocation({ search: '?search=my%20terms' }); + setWindowLocation('?search=my%20terms'); expect(getParameterByName('search')).toBe('my terms'); }); it('should return a parameter with plus signs as spaces', () => { - setWindowLocation({ search: '?search=my+terms' }); + setWindowLocation('?search=my+terms'); expect(getParameterByName('search')).toBe('my terms'); }); @@ -842,18 +812,20 @@ describe('URL utility', () => { }); describe('urlIsDifferent', () => { + const current = 'http://current.test/'; + beforeEach(() => { - setWindowLocation('current'); + setWindowLocation(current); }); it('should compare against the window location if no compare value is provided', () => { expect(urlUtils.urlIsDifferent('different')).toBeTruthy(); - expect(urlUtils.urlIsDifferent('current')).toBeFalsy(); + expect(urlUtils.urlIsDifferent(current)).toBeFalsy(); }); it('should use the provided compare value', () => { - expect(urlUtils.urlIsDifferent('different', 'current')).toBeTruthy(); - expect(urlUtils.urlIsDifferent('current', 'current')).toBeFalsy(); + expect(urlUtils.urlIsDifferent('different', current)).toBeTruthy(); + expect(urlUtils.urlIsDifferent(current, current)).toBeFalsy(); }); }); @@ -944,9 +916,8 @@ describe('URL utility', () => { it.each([[httpProtocol], [httpsProtocol]])( 'when no url passed, returns correct protocol for %i from window location', (protocol) => { - setWindowLocation({ - protocol, - }); + setWindowLocation(`${protocol}//test.host`); + expect(urlUtils.getHTTPProtocol()).toBe(protocol.slice(0, -1)); }, ); @@ -979,10 +950,8 @@ describe('URL utility', () => { describe('getURLOrigin', () => { it('when no url passed, returns correct origin from window location', () => { - const origin = 'https://foo.bar'; - - setWindowLocation({ origin }); - expect(urlUtils.getURLOrigin()).toBe(origin); + setWindowLocation('https://user:pass@origin.test:1234/foo/bar?foo=1#bar'); + expect(urlUtils.getURLOrigin()).toBe('https://origin.test:1234'); }); it.each` @@ -1032,10 +1001,6 @@ describe('URL utility', () => { // eslint-disable-next-line no-script-url const javascriptUrl = 'javascript:alert(1)'; - beforeEach(() => { - setWindowLocation({ origin: TEST_HOST }); - }); - it.each` url | expected ${TEST_HOST} | ${true} diff --git a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js index 93ebbc648fe..9f910ed4f9c 100644 --- a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js +++ b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js @@ -1,5 +1,6 @@ import { GlAlert } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; import CodeSnippetAlert from '~/pipeline_editor/components/code_snippet_alert/code_snippet_alert.vue'; import { CODE_SNIPPET_SOURCES } from '~/pipeline_editor/components/code_snippet_alert/constants'; @@ -12,6 +13,10 @@ import { LOAD_FAILURE_UNKNOWN, } from '~/pipeline_editor/constants'; +beforeEach(() => { + setWindowLocation(TEST_HOST); +}); + describe('Pipeline Editor messages', () => { let wrapper; @@ -95,9 +100,7 @@ describe('Pipeline Editor messages', () => { describe('code snippet alert', () => { const setCodeSnippetUrlParam = (value) => { - global.jsdom.reconfigure({ - url: `${TEST_HOST}/?code_snippet_copied_from=${value}`, - }); + setWindowLocation(`${TEST_HOST}/?code_snippet_copied_from=${value}`); }; it('does not show by default', () => { diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js index 2166961cedd..dd2cb1e8643 100644 --- a/spec/frontend/pipelines/pipelines_spec.js +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -4,6 +4,8 @@ import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { chunk } from 'lodash'; import { nextTick } from 'vue'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import { TEST_HOST } from 'helpers/test_constants'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; @@ -40,7 +42,6 @@ const mockPipelineWithStages = mockPipelinesResponse.pipelines.find( describe('Pipelines', () => { let wrapper; let mock; - let origWindowLocation; const paths = { emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg', @@ -98,17 +99,8 @@ describe('Pipelines', () => { ); }; - beforeAll(() => { - origWindowLocation = window.location; - delete window.location; - window.location = { - search: '', - protocol: 'https:', - }; - }); - - afterAll(() => { - window.location = origWindowLocation; + beforeEach(() => { + setWindowLocation(TEST_HOST); }); beforeEach(() => { diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js index 748b48dacaa..1db6fa21d6b 100644 --- a/spec/frontend/releases/components/app_edit_new_spec.js +++ b/spec/frontend/releases/components/app_edit_new_spec.js @@ -4,6 +4,7 @@ import MockAdapter from 'axios-mock-adapter'; import { merge } from 'lodash'; import Vuex from 'vuex'; import { getJSONFixture } from 'helpers/fixtures'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; import * as commonUtils from '~/lib/utils/common_utils'; import ReleaseEditNewApp from '~/releases/components/app_edit_new.vue'; @@ -77,7 +78,7 @@ describe('Release edit/new component', () => { }; beforeEach(() => { - global.jsdom.reconfigure({ url: TEST_HOST }); + setWindowLocation(TEST_HOST); mock = new MockAdapter(axios); gon.api_version = 'v4'; @@ -164,9 +165,7 @@ describe('Release edit/new component', () => { `when the URL contains a "${BACK_URL_PARAM}=$backUrl" parameter`, ({ backUrl, expectedHref }) => { beforeEach(async () => { - global.jsdom.reconfigure({ - url: `${TEST_HOST}?${BACK_URL_PARAM}=${encodeURIComponent(backUrl)}`, - }); + setWindowLocation(`${TEST_HOST}?${BACK_URL_PARAM}=${encodeURIComponent(backUrl)}`); await factory(); }); diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js index 0f6657090e6..47fd6377fcf 100644 --- a/spec/frontend/releases/components/release_block_header_spec.js +++ b/spec/frontend/releases/components/release_block_header_spec.js @@ -2,6 +2,7 @@ import { GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { merge } from 'lodash'; import { getJSONFixture } from 'helpers/fixtures'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import ReleaseBlockHeader from '~/releases/components/release_block_header.vue'; import { BACK_URL_PARAM } from '~/releases/constants'; @@ -60,12 +61,7 @@ describe('Release block header', () => { const currentUrl = 'https://example.gitlab.com/path'; beforeEach(() => { - Object.defineProperty(window, 'location', { - writable: true, - value: { - href: currentUrl, - }, - }); + setWindowLocation(currentUrl); factory(); }); diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js index 2c28f10ca8e..c1596711be7 100644 --- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js +++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js @@ -1,7 +1,7 @@ import { createLocalVue, mount, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; -import { TEST_HOST } from 'helpers/test_constants'; +import setWindowLocation from 'helpers/set_window_location_helper'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; import { updateHistory } from '~/lib/utils/url_utility'; @@ -43,7 +43,6 @@ localVue.use(VueApollo); describe('AdminRunnersApp', () => { let wrapper; let mockRunnersQuery; - let originalLocation; const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp); const findRunnerManualSetupHelp = () => wrapper.findComponent(RunnerManualSetupHelp); @@ -65,22 +64,8 @@ describe('AdminRunnersApp', () => { }); }; - const setQuery = (query) => { - window.location.href = `${TEST_HOST}/admin/runners?${query}`; - window.location.search = query; - }; - - beforeAll(() => { - originalLocation = window.location; - Object.defineProperty(window, 'location', { writable: true, value: { href: '', search: '' } }); - }); - - afterAll(() => { - window.location = originalLocation; - }); - beforeEach(async () => { - setQuery(''); + setWindowLocation('/admin/runners'); mockRunnersQuery = jest.fn().mockResolvedValue(runnersData); createComponentWithApollo(); @@ -116,7 +101,7 @@ describe('AdminRunnersApp', () => { describe('when a filter is preselected', () => { beforeEach(async () => { - setQuery(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`); + setWindowLocation(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`); createComponentWithApollo(); await waitForPromises(); diff --git a/spec/frontend/search/index_spec.js b/spec/frontend/search/index_spec.js index 1992a7f4437..c07cd74b456 100644 --- a/spec/frontend/search/index_spec.js +++ b/spec/frontend/search/index_spec.js @@ -1,4 +1,5 @@ import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { initSearchApp } from '~/search'; import createStore from '~/search/store'; @@ -8,25 +9,6 @@ jest.mock('~/search/sidebar'); jest.mock('ee_else_ce/search/highlight_blob_search_result'); describe('initSearchApp', () => { - let defaultLocation; - - const setUrl = (query) => { - window.location.href = `https://localhost:3000/search${query}`; - window.location.search = query; - }; - - beforeEach(() => { - defaultLocation = window.location; - Object.defineProperty(window, 'location', { - writable: true, - value: { href: '', search: '' }, - }); - }); - - afterEach(() => { - window.location = defaultLocation; - }); - describe.each` search | decodedSearch ${'test'} | ${'test'} @@ -38,7 +20,7 @@ describe('initSearchApp', () => { ${'test+%2520+this+%2520+stuff'} | ${'test %20 this %20 stuff'} `('parameter decoding', ({ search, decodedSearch }) => { beforeEach(() => { - setUrl(`?search=${search}`); + setWindowLocation(`/search?search=${search}`); initSearchApp(); }); diff --git a/spec/frontend_integration/diffs/diffs_interopability_spec.js b/spec/frontend_integration/diffs/diffs_interopability_spec.js index 448641ed834..064e3d21180 100644 --- a/spec/frontend_integration/diffs/diffs_interopability_spec.js +++ b/spec/frontend_integration/diffs/diffs_interopability_spec.js @@ -1,4 +1,5 @@ import { waitFor } from '@testing-library/dom'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; import initDiffsApp from '~/diffs'; import { createStore } from '~/mr_notes/stores'; @@ -111,9 +112,7 @@ describe('diffs third party interoperability', () => { ${'parallel view right side'} | ${'parallel'} | ${'.diff-tr.line_holder'} | ${'.diff-td.line_content.right-side'} | ${EXPECT_PARALLEL_RIGHT_SIDE} `('$desc', ({ view, rowSelector, codeSelector, expectation }) => { beforeEach(async () => { - global.jsdom.reconfigure({ - url: `${TEST_HOST}/${TEST_BASE_URL}/diffs?view=${view}`, - }); + setWindowLocation(`${TEST_HOST}/${TEST_BASE_URL}/diffs?view=${view}`); vm = startDiffsApp(); diff --git a/spec/frontend_integration/ide/helpers/start.js b/spec/frontend_integration/ide/helpers/start.js index cc6abd9e01f..4451c1ee946 100644 --- a/spec/frontend_integration/ide/helpers/start.js +++ b/spec/frontend_integration/ide/helpers/start.js @@ -1,5 +1,6 @@ /* global monaco */ +import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; import { initIde } from '~/ide'; import extendStore from '~/ide/stores/extend'; @@ -9,9 +10,7 @@ export default (container, { isRepoEmpty = false, path = '', mrId = '' } = {}) = const projectName = isRepoEmpty ? 'lorem-ipsum-empty' : 'lorem-ipsum'; const pathSuffix = mrId ? `merge_requests/${mrId}` : `tree/master/-/${path}`; - global.jsdom.reconfigure({ - url: `${TEST_HOST}/-/ide/project/gitlab-test/${projectName}/${pathSuffix}`, - }); + setWindowLocation(`${TEST_HOST}/-/ide/project/gitlab-test/${projectName}/${pathSuffix}`); const el = document.createElement('div'); Object.assign(el.dataset, IDE_DATASET); diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index 654d4116519..6d51d85fd64 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -254,4 +254,34 @@ RSpec.describe ApplicationSettingsHelper do ]) end end + + describe '.pending_user_count' do + let(:user_cap) { 200 } + + before do + stub_application_setting(new_user_signups_cap: user_cap) + end + + subject(:pending_user_count) { helper.pending_user_count } + + context 'when new_user_signups_cap is present' do + it 'returns the number of blocked pending users' do + create(:user, state: :blocked_pending_approval) + + expect(pending_user_count).to eq 1 + end + end + + context 'when the new_user_signups_cap is not present' do + let(:user_cap) { nil } + + it { is_expected.to eq 0 } + + it 'does not query users unnecessarily' do + expect(User).not_to receive(:blocked_pending_approval) + + pending_user_count + end + end + end end diff --git a/spec/lib/gitlab/auth/result_spec.rb b/spec/lib/gitlab/auth/result_spec.rb new file mode 100644 index 00000000000..2953538c15e --- /dev/null +++ b/spec/lib/gitlab/auth/result_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Auth::Result do + subject { described_class.new(actor, nil, nil, []) } + + context 'when actor is User' do + let(:actor) { create(:user) } + + it 'returns auth_user' do + expect(subject.auth_user).to eq(actor) + end + + it 'does not return deploy token' do + expect(subject.deploy_token).to be_nil + end + end + + context 'when actor is Deploy token' do + let(:actor) { create(:deploy_token) } + + it 'returns deploy token' do + expect(subject.deploy_token).to eq(actor) + end + + it 'does not return auth_user' do + expect(subject.auth_user).to be_nil + end + end +end diff --git a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb new file mode 100644 index 00000000000..ec39e5bfee7 --- /dev/null +++ b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model do + # PostgresForeignKey does not `behaves_like 'a postgres model'` because it does not correspond 1-1 with a single entry + # in pg_class + + before do + ActiveRecord::Base.connection.execute(<<~SQL) + CREATE TABLE public.referenced_table ( + id bigserial primary key not null + ); + + CREATE TABLE public.other_referenced_table ( + id bigserial primary key not null + ); + + CREATE TABLE public.constrained_table ( + id bigserial primary key not null, + referenced_table_id bigint not null, + other_referenced_table_id bigint not null, + CONSTRAINT fk_constrained_to_referenced FOREIGN KEY(referenced_table_id) REFERENCES referenced_table(id), + CONSTRAINT fk_constrained_to_other_referenced FOREIGN KEY(other_referenced_table_id) + REFERENCES other_referenced_table(id) + ); + SQL + end + + describe '#by_referenced_table_identifier' do + it 'throws an error when the identifier name is not fully qualified' do + expect { described_class.by_referenced_table_identifier('referenced_table') }.to raise_error(ArgumentError, /not fully qualified/) + end + + it 'finds the foreign keys for the referenced table' do + expected = described_class.find_by!(name: 'fk_constrained_to_referenced') + + expect(described_class.by_referenced_table_identifier('public.referenced_table')).to contain_exactly(expected) + end + end +end diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb index d555ff379c6..0321b151633 100644 --- a/spec/models/integrations/jira_spec.rb +++ b/spec/models/integrations/jira_spec.rb @@ -334,16 +334,6 @@ RSpec.describe Integrations::Jira do end end - context 'when not allowed to test an instance or group' do - it 'does not update deployment type' do - allow(integration).to receive(:testable?).and_return(false) - - integration.update!(url: 'http://first.url') - - expect(WebMock).not_to have_requested(:get, /serverInfo/) - end - end - context 'stored password invalidation' do context 'when a password was previously set' do context 'when only web url present' do diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb index eb6c0861844..6c0a1b2502f 100644 --- a/spec/requests/api/ci/pipelines_spec.rb +++ b/spec/requests/api/ci/pipelines_spec.rb @@ -1150,4 +1150,43 @@ RSpec.describe API::Ci::Pipelines do end end end + + describe 'GET /projects/:id/pipelines/:pipeline_id/test_report_summary' do + subject { get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report_summary", current_user) } + + context 'authorized user' do + let(:current_user) { user } + + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'when pipeline does not have a test report summary' do + it 'returns an empty test report summary' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['total']['count']).to eq(0) + end + end + + context 'when pipeline has a test report summary' do + let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project) } + + it 'returns the test report summary' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['total']['count']).to eq(2) + end + end + end + + context 'unauthorized user' do + it 'does not return project pipelines' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/test_report_summary", non_member) + + expect(response).to have_gitlab_http_status(:not_found) + expect(json_response['message']).to eq '404 Project Not Found' + end + end + end end diff --git a/spec/requests/api/debian_group_packages_spec.rb b/spec/requests/api/debian_group_packages_spec.rb index 931eaf41891..ddfc94f25db 100644 --- a/spec/requests/api/debian_group_packages_spec.rb +++ b/spec/requests/api/debian_group_packages_spec.rb @@ -6,6 +6,12 @@ RSpec.describe API::DebianGroupPackages do include WorkhorseHelpers include_context 'Debian repository shared context', :group, false do + context 'with invalid parameter' do + let(:url) { "/groups/1/-/packages/debian/dists/with+space/InRelease" } + + it_behaves_like 'Debian repository GET request', :bad_request, /^distribution is invalid$/ + end + describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release.gpg' do let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release.gpg" } @@ -30,10 +36,25 @@ RSpec.describe API::DebianGroupPackages do it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/ end - describe 'GET groups/:id/-/packages/debian/pool/:component/:letter/:source_package/:file_name' do - let(:url) { "/groups/#{container.id}/-/packages/debian/pool/#{component.name}/#{letter}/#{source_package}/#{package_name}_#{package_version}_#{architecture.name}.deb" } + describe 'GET groups/:id/-/packages/debian/pool/:codename/:project_id/:letter/:package_name/:package_version/:file_name' do + let(:url) { "/groups/#{container.id}/-/packages/debian/pool/#{package.debian_distribution.codename}/#{project.id}/#{letter}/#{package.name}/#{package.version}/#{file_name}" } + + using RSpec::Parameterized::TableSyntax + + where(:file_name, :success_body) do + 'sample_1.2.3~alpha2.tar.xz' | /^.7zXZ/ + 'sample_1.2.3~alpha2.dsc' | /^Format: 3.0 \(native\)/ + 'libsample0_1.2.3~alpha2_amd64.deb' | /^!<arch>/ + 'sample-udeb_1.2.3~alpha2_amd64.udeb' | /^!<arch>/ + 'sample_1.2.3~alpha2_amd64.buildinfo' | /Build-Tainted-By/ + 'sample_1.2.3~alpha2_amd64.changes' | /urgency=medium/ + end + + with_them do + include_context 'with file_name', params[:file_name] - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO File$/ + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, params[:success_body] + end end end end diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb index fb7da467322..d6f22dfbaa3 100644 --- a/spec/requests/api/debian_project_packages_spec.rb +++ b/spec/requests/api/debian_project_packages_spec.rb @@ -6,6 +6,12 @@ RSpec.describe API::DebianProjectPackages do include WorkhorseHelpers include_context 'Debian repository shared context', :project, true do + context 'with invalid parameter' do + let(:url) { "/projects/1/packages/debian/dists/with+space/InRelease" } + + it_behaves_like 'Debian repository GET request', :bad_request, /^distribution is invalid$/ + end + describe 'GET projects/:id/packages/debian/dists/*distribution/Release.gpg' do let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release.gpg" } @@ -30,10 +36,25 @@ RSpec.describe API::DebianProjectPackages do it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/ end - describe 'GET projects/:id/packages/debian/pool/:component/:letter/:source_package/:file_name' do - let(:url) { "/projects/#{container.id}/packages/debian/pool/#{component.name}/#{letter}/#{source_package}/#{package_name}_#{package_version}_#{architecture.name}.deb" } + describe 'GET projects/:id/packages/debian/pool/:codename/:letter/:package_name/:package_version/:file_name' do + let(:url) { "/projects/#{container.id}/packages/debian/pool/#{package.debian_distribution.codename}/#{letter}/#{package.name}/#{package.version}/#{file_name}" } + + using RSpec::Parameterized::TableSyntax - it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^TODO File$/ + where(:file_name, :success_body) do + 'sample_1.2.3~alpha2.tar.xz' | /^.7zXZ/ + 'sample_1.2.3~alpha2.dsc' | /^Format: 3.0 \(native\)/ + 'libsample0_1.2.3~alpha2_amd64.deb' | /^!<arch>/ + 'sample-udeb_1.2.3~alpha2_amd64.udeb' | /^!<arch>/ + 'sample_1.2.3~alpha2_amd64.buildinfo' | /Build-Tainted-By/ + 'sample_1.2.3~alpha2_amd64.changes' | /urgency=medium/ + end + + with_them do + include_context 'with file_name', params[:file_name] + + it_behaves_like 'Debian repository read endpoint', 'GET request', :success, params[:success_body] + end end describe 'PUT projects/:id/packages/debian/:file_name' do diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index 55577a5dc65..4bb832a7172 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -79,7 +79,7 @@ RSpec.describe JwtController do it 'authenticates correctly' do expect(response).to have_gitlab_http_status(:ok) - expect(service_class).to have_received(:new).with(nil, deploy_token, ActionController::Parameters.new(parameters).permit!) + expect(service_class).to have_received(:new).with(nil, nil, ActionController::Parameters.new(parameters.merge(deploy_token: deploy_token)).permit!) end it 'does not log a user' do diff --git a/spec/services/auth/dependency_proxy_authentication_service_spec.rb b/spec/services/auth/dependency_proxy_authentication_service_spec.rb index 35e6d59b456..c54509a3536 100644 --- a/spec/services/auth/dependency_proxy_authentication_service_spec.rb +++ b/spec/services/auth/dependency_proxy_authentication_service_spec.rb @@ -35,12 +35,6 @@ RSpec.describe Auth::DependencyProxyAuthenticationService do it_behaves_like 'returning', status: 403, message: 'access forbidden' end - context 'with a deploy token as user' do - let_it_be(:user) { create(:deploy_token) } - - it_behaves_like 'returning', status: 403, message: 'access forbidden' - end - context 'with a user' do it 'returns a token' do expect(subject[:token]).not_to be_nil diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb index bb96d2a71d1..a3ed74085fb 100644 --- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb @@ -29,6 +29,8 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ let_it_be(:public_project) { create(:project, :public, group: public_container) } let_it_be(:private_project_distribution) { create(:debian_project_distribution, container: private_project, codename: 'existing-codename') } let_it_be(:public_project_distribution) { create(:debian_project_distribution, container: public_project, codename: 'existing-codename') } + + let(:project) { { private: private_project, public: public_project }[visibility_level] } else let_it_be(:private_project) { private_container } let_it_be(:public_project) { public_container } @@ -45,12 +47,8 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ let(:architecture) { { private: private_architecture, public: public_architecture }[visibility_level] } let(:component) { { private: private_component, public: public_component }[visibility_level] } let(:component_file) { { private: private_component_file, public: public_component_file }[visibility_level] } - - let(:source_package) { 'sample' } - let(:letter) { source_package[0..2] == 'lib' ? source_package[0..3] : source_package[0] } - let(:package_name) { 'libsample0' } - let(:package_version) { '1.2.3~alpha2' } - let(:file_name) { "#{package_name}_#{package_version}_#{architecture.name}.deb" } + let(:package) { { private: private_package, public: public_package }[visibility_level] } + let(:letter) { package.name[0..2] == 'lib' ? package.name[0..3] : package.name[0] } let(:method) { :get } @@ -94,6 +92,10 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ end end +RSpec.shared_context 'with file_name' do |file_name| + let(:file_name) { file_name } +end + RSpec.shared_context 'Debian repository auth headers' do |user_role, user_token, auth_method = :token| let(:token) { user_token ? personal_access_token.token : 'wrong' } diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb index eafcbd77040..e514afee04f 100644 --- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb +++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb @@ -830,15 +830,15 @@ RSpec.shared_examples 'a container registry auth service' do context 'for deploy tokens' do let(:current_params) do - { scopes: ["repository:#{project.full_path}:pull"] } + { scopes: ["repository:#{project.full_path}:pull"], deploy_token: deploy_token } end context 'when deploy token has read and write registry as scopes' do - let(:current_user) { create(:deploy_token, write_registry: true, projects: [project]) } + let(:deploy_token) { create(:deploy_token, write_registry: true, projects: [project]) } shared_examples 'able to login' do context 'registry provides read_container_image authentication_abilities' do - let(:current_params) { {} } + let(:current_params) { { deploy_token: deploy_token } } let(:authentication_abilities) { [:read_container_image] } it_behaves_like 'an authenticated' @@ -854,7 +854,7 @@ RSpec.shared_examples 'a container registry auth service' do context 'when pushing' do let(:current_params) do - { scopes: ["repository:#{project.full_path}:push"] } + { scopes: ["repository:#{project.full_path}:push"], deploy_token: deploy_token } end it_behaves_like 'a pushable' @@ -872,7 +872,7 @@ RSpec.shared_examples 'a container registry auth service' do context 'when pushing' do let(:current_params) do - { scopes: ["repository:#{project.full_path}:push"] } + { scopes: ["repository:#{project.full_path}:push"], deploy_token: deploy_token } end it_behaves_like 'a pushable' @@ -890,7 +890,7 @@ RSpec.shared_examples 'a container registry auth service' do context 'when pushing' do let(:current_params) do - { scopes: ["repository:#{project.full_path}:push"] } + { scopes: ["repository:#{project.full_path}:push"], deploy_token: deploy_token } end it_behaves_like 'a pushable' @@ -901,18 +901,18 @@ RSpec.shared_examples 'a container registry auth service' do end context 'when deploy token does not have read_registry scope' do - let(:current_user) { create(:deploy_token, projects: [project], read_registry: false) } + let(:deploy_token) do + create(:deploy_token, projects: [project], read_registry: false) + end shared_examples 'unable to login' do context 'registry provides no container authentication_abilities' do - let(:current_params) { {} } let(:authentication_abilities) { [] } it_behaves_like 'a forbidden' end context 'registry provides inapplicable container authentication_abilities' do - let(:current_params) { {} } let(:authentication_abilities) { [:download_code] } it_behaves_like 'a forbidden' @@ -958,7 +958,7 @@ RSpec.shared_examples 'a container registry auth service' do end context 'when deploy token is not related to the project' do - let_it_be(:current_user) { create(:deploy_token, read_registry: false) } + let_it_be(:deploy_token) { create(:deploy_token, read_registry: false) } context 'for public project' do let_it_be(:project) { create(:project, :public) } @@ -986,7 +986,7 @@ RSpec.shared_examples 'a container registry auth service' do end context 'when deploy token has been revoked' do - let(:current_user) { create(:deploy_token, :revoked, projects: [project]) } + let(:deploy_token) { create(:deploy_token, :revoked, projects: [project]) } context 'for public project' do let_it_be(:project) { create(:project, :public) } diff --git a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb index 9ffeba1b1d0..10625862668 100644 --- a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb +++ b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb @@ -53,7 +53,9 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do .and change { component_file1.reload.updated_at }.to(current_time.round) debs = package.package_files.with_debian_file_type(:deb).preload_debian_file_metadata.to_a - pool_prefix = "pool/unstable/#{project.id}/p/#{package.name}" + pool_prefix = 'pool/unstable' + pool_prefix += "/#{project.id}" if container_type == :group + pool_prefix += "/p/#{package.name}/#{package.version}" expected_main_amd64_content = <<~EOF Package: libsample0 Source: #{package.name} |