summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-27 12:10:54 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-27 12:10:54 +0000
commit7a20b3758e651fe79032a5165db2208183877317 (patch)
treeca4964f3e851cd4b77879652aec225ea5daa1ca4
parent2458ea514066142e3ca8e5131e44925398902a77 (diff)
downloadgitlab-ce-7a20b3758e651fe79032a5165db2208183877317.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/images/gitorious-logo-black.pngbin631 -> 0 bytes
-rw-r--r--app/assets/images/gitorious-logo-blue.pngbin201 -> 0 bytes
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue27
-rw-r--r--app/assets/javascripts/main.js230
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/help_state.vue10
-rw-r--r--app/controllers/jwt_controller.rb7
-rw-r--r--app/helpers/application_settings_helper.rb6
-rw-r--r--app/models/integrations/jira.rb3
-rw-r--r--app/models/packages/debian.rb2
-rw-r--r--app/services/auth/container_registry_authentication_service.rb21
-rw-r--r--app/services/auth/dependency_proxy_authentication_service.rb2
-rw-r--r--app/services/packages/debian/generate_distribution_service.rb4
-rw-r--r--app/views/admin/application_settings/_signup.html.haml3
-rw-r--r--app/views/projects/show.html.haml1
-rw-r--r--config/application.rb1
-rw-r--r--config/feature_flags/development/gitaly_backup.yml2
-rw-r--r--db/migrate/20210719145532_add_foreign_keys_view.rb26
-rw-r--r--db/schema_migrations/202107191455321
-rw-r--r--db/structure.sql12
-rw-r--r--doc/api/pipelines.md53
-rw-r--r--doc/development/testing_guide/frontend_testing.md34
-rw-r--r--doc/raketasks/backup_restore.md44
-rw-r--r--lib/api/ci/pipelines.rb13
-rw-r--r--lib/api/concerns/packages/debian_package_endpoints.rb166
-rw-r--r--lib/api/debian_group_packages.rb45
-rw-r--r--lib/api/debian_project_packages.rb50
-rw-r--r--lib/gitlab/auth/result.rb8
-rw-r--r--lib/gitlab/database/postgres_foreign_key.rb15
-rw-r--r--lib/tasks/gitlab/backup.rake2
-rw-r--r--locale/gitlab.pot24
-rw-r--r--spec/frontend/__helpers__/set_window_location_helper.js75
-rw-r--r--spec/frontend/__helpers__/set_window_location_helper_spec.js134
-rw-r--r--spec/frontend/diffs/components/compare_versions_spec.js25
-rw-r--r--spec/frontend/diffs/store/getters_versions_dropdowns_spec.js10
-rw-r--r--spec/frontend/issues_list/components/issuables_list_app_spec.js31
-rw-r--r--spec/frontend/issues_list/components/issues_list_app_spec.js24
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js83
-rw-r--r--spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js9
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js16
-rw-r--r--spec/frontend/releases/components/app_edit_new_spec.js7
-rw-r--r--spec/frontend/releases/components/release_block_header_spec.js8
-rw-r--r--spec/frontend/runner/admin_runners/admin_runners_app_spec.js21
-rw-r--r--spec/frontend/search/index_spec.js22
-rw-r--r--spec/frontend_integration/diffs/diffs_interopability_spec.js5
-rw-r--r--spec/frontend_integration/ide/helpers/start.js5
-rw-r--r--spec/helpers/application_settings_helper_spec.rb30
-rw-r--r--spec/lib/gitlab/auth/result_spec.rb31
-rw-r--r--spec/lib/gitlab/database/postgres_foreign_key_spec.rb41
-rw-r--r--spec/models/integrations/jira_spec.rb10
-rw-r--r--spec/requests/api/ci/pipelines_spec.rb39
-rw-r--r--spec/requests/api/debian_group_packages_spec.rb27
-rw-r--r--spec/requests/api/debian_project_packages_spec.rb27
-rw-r--r--spec/requests/jwt_controller_spec.rb2
-rw-r--r--spec/services/auth/dependency_proxy_authentication_service_spec.rb6
-rw-r--r--spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb4
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
deleted file mode 100644
index 4a55fdc225a..00000000000
--- a/app/assets/images/gitorious-logo-black.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/gitorious-logo-blue.png b/app/assets/images/gitorious-logo-blue.png
deleted file mode 100644
index 5eaa327d3df..00000000000
--- a/app/assets/images/gitorious-logo-blue.png
+++ /dev/null
Binary files differ
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&not[label_name][]=Afterpod&not[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}