From 8b1036168b0d395c379cbbaf457e256860147405 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 1 Mar 2023 15:10:00 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .gitlab-ci.yml | 3 + .gitlab/CODEOWNERS | 2 +- .gitlab/ci/package-and-test/main.gitlab-ci.yml | 2 + .../ci/package-and-test/variables.gitlab-ci.yml | 1 + .gitlab/ci/qa.gitlab-ci.yml | 3 + .gitlab/ci/release-environments.gitlab-ci.yml | 22 ++ .gitlab/ci/release-environments/main.gitlab-ci.yml | 62 +++++ .gitlab/ci/rules.gitlab-ci.yml | 18 ++ app/assets/javascripts/alert.js | 137 ++++++++++ app/assets/javascripts/flash.js | 137 ---------- app/assets/javascripts/service_ping_consent.js | 2 +- .../super_sidebar/components/nav_item.vue | 19 +- .../super_sidebar/components/user_bar.vue | 18 +- .../concerns/authenticates_with_two_factor.rb | 2 - app/helpers/sidebars_helper.rb | 1 + .../ci_batch_project_includes_context.yml | 8 + .../use_traversal_ids_for_ancestor_scopes.yml | 2 +- config/initializers/1_settings.rb | 2 +- config/settings.rb | 7 + config/webpack.config.js | 3 + ...elines_source_partition_id_and_source_job_id.rb | 15 ++ ...builds_ci_sources_pipelines_on_source_job_id.rb | 35 +++ db/schema_migrations/20230227123949 | 1 + db/schema_migrations/20230227123950 | 1 + db/structure.sql | 5 +- doc/user/project/repository/tags/index.md | 7 +- jest.config.base.js | 3 + lib/gitlab/ci/config/external/file/base.rb | 12 + lib/gitlab/ci/config/external/file/project.rb | 68 ++++- lib/gitlab/ci/config/external/mapper/verifier.rb | 33 +++ .../security/vulnerability_reports_comparer.rb | 29 ++- lib/sidebars/menu.rb | 1 + lib/sidebars/menu_item.rb | 6 +- spec/config/settings_spec.rb | 14 ++ spec/db/schema_spec.rb | 2 +- spec/frontend/alert_spec.js | 276 +++++++++++++++++++++ spec/frontend/clusters_list/store/actions_spec.js | 2 +- spec/frontend/contributors/store/actions_spec.js | 2 +- spec/frontend/deploy_freeze/store/actions_spec.js | 2 +- .../frontend/design_management/pages/index_spec.js | 2 +- .../design_management/utils/cache_update_spec.js | 2 +- spec/frontend/error_tracking/store/actions_spec.js | 2 +- .../error_tracking/store/details/actions_spec.js | 2 +- .../error_tracking/store/list/actions_spec.js | 2 +- spec/frontend/flash_spec.js | 276 --------------------- .../frontend/merge_conflicts/store/actions_spec.js | 2 +- .../components/details/store/actions_spec.js | 2 +- .../components/list/stores/actions_spec.js | 2 +- .../pipelines/test_reports/stores/actions_spec.js | 2 +- .../test_reports/stores/mutations_spec.js | 2 +- .../frontend/projects/commit/store/actions_spec.js | 2 +- .../super_sidebar/components/nav_item_spec.js | 34 +++ .../super_sidebar/components/user_bar_spec.js | 25 +- spec/frontend/super_sidebar/mock_data.js | 2 + spec/helpers/sidebars_helper_spec.rb | 1 + .../gitlab/ci/config/external/file/project_spec.rb | 30 +++ .../ci/config/external/mapper/verifier_spec.rb | 77 +++++- .../vulnerability_reports_comparer_spec.rb | 26 +- spec/lib/gitlab/sidekiq_queue_spec.rb | 10 +- spec/lib/sidebars/menu_spec.rb | 36 ++- spec/lib/sidebars/static_menu_spec.rb | 6 +- spec/support/helpers/query_recorder.rb | 4 + spec/support/rspec_order_todo.yml | 5 - spec/workers/concerns/cluster_agent_queue_spec.rb | 1 - spec/workers/concerns/cluster_queue_spec.rb | 21 -- spec/workers/concerns/cronjob_queue_spec.rb | 4 - .../concerns/gitlab/github_import/queue_spec.rb | 18 -- .../concerns/pipeline_background_queue_spec.rb | 21 -- spec/workers/concerns/pipeline_queue_spec.rb | 21 -- .../concerns/repository_check_queue_spec.rb | 4 - .../ssh_keys/expired_notification_worker_spec.rb | 1 - .../expiring_soon_notification_worker_spec.rb | 1 - 72 files changed, 1017 insertions(+), 594 deletions(-) create mode 100644 .gitlab/ci/release-environments.gitlab-ci.yml create mode 100644 .gitlab/ci/release-environments/main.gitlab-ci.yml create mode 100644 app/assets/javascripts/alert.js delete mode 100644 app/assets/javascripts/flash.js create mode 100644 config/feature_flags/development/ci_batch_project_includes_context.yml create mode 100644 db/post_migrate/20230227123949_validate_fk_on_ci_sources_pipelines_source_partition_id_and_source_job_id.rb create mode 100644 db/post_migrate/20230227123950_remove_fk_to_ci_builds_ci_sources_pipelines_on_source_job_id.rb create mode 100644 db/schema_migrations/20230227123949 create mode 100644 db/schema_migrations/20230227123950 create mode 100644 spec/frontend/alert_spec.js delete mode 100644 spec/frontend/flash_spec.js create mode 100644 spec/frontend/super_sidebar/components/nav_item_spec.js delete mode 100644 spec/workers/concerns/cluster_queue_spec.rb delete mode 100644 spec/workers/concerns/gitlab/github_import/queue_spec.rb delete mode 100644 spec/workers/concerns/pipeline_background_queue_spec.rb delete mode 100644 spec/workers/concerns/pipeline_queue_spec.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3b22e2fc21e..29e51b25200 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,6 +12,7 @@ stages: - post-qa - pages - notify + - release-environments # always use `gitlab-org` runners, however # in cases where jobs require Docker-in-Docker, the job @@ -32,6 +33,8 @@ default: .ruby2-variables: &ruby2-variables RUBY_VERSION: "2.7" + OMNIBUS_GITLAB_RUBY2_BUILD: "true" + OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB_RUBY2" .default-branch-incident-variables: &default-branch-incident-variables CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true" diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 67bd749d3e0..53933fe3ee0 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -798,7 +798,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /doc/integration/jenkins.md @ashrafkhamis /doc/integration/jira/ @ashrafkhamis /doc/integration/mattermost/ @axil -/doc/integration/partner_marketplace.md @drcatherinepope +/doc/integration/partner_marketplace.md @fneill /doc/integration/recaptcha.md @phillipwells /doc/integration/security_partners/ @rdickenson /doc/integration/slash_commands.md @ashrafkhamis diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml index 301a2ddb5f1..9a8acbb3af0 100644 --- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml +++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml @@ -122,6 +122,7 @@ trigger-omnibus-env: echo "OMNIBUS_GITLAB_CACHE_UPDATE=${OMNIBUS_GITLAB_CACHE_UPDATE:-false}" >> $BUILD_ENV for version_file in *_VERSION; do echo "$version_file=$(cat $version_file)" >> $BUILD_ENV; done echo "OMNIBUS_GITLAB_RUBY3_BUILD=${OMNIBUS_GITLAB_RUBY3_BUILD:-false}" >> $BUILD_ENV + echo "OMNIBUS_GITLAB_RUBY2_BUILD=${OMNIBUS_GITLAB_RUBY2_BUILD:-false}" >> $BUILD_ENV echo "OMNIBUS_GITLAB_CACHE_EDITION=${OMNIBUS_GITLAB_CACHE_EDITION:-GITLAB}" >> $BUILD_ENV echo "GITLAB_ASSETS_TAG=$(assets_image_tag)" >> $BUILD_ENV echo "Built environment file for omnibus build:" @@ -152,6 +153,7 @@ trigger-omnibus: SECURITY_SOURCES: $SECURITY_SOURCES CACHE_UPDATE: $OMNIBUS_GITLAB_CACHE_UPDATE RUBY3_BUILD: $OMNIBUS_GITLAB_RUBY3_BUILD + RUBY2_BUILD: $OMNIBUS_GITLAB_RUBY2_BUILD CACHE_EDITION: $OMNIBUS_GITLAB_CACHE_EDITION SKIP_QA_DOCKER: "true" SKIP_QA_TEST: "true" diff --git a/.gitlab/ci/package-and-test/variables.gitlab-ci.yml b/.gitlab/ci/package-and-test/variables.gitlab-ci.yml index c45807e5a23..1ba046308a8 100644 --- a/.gitlab/ci/package-and-test/variables.gitlab-ci.yml +++ b/.gitlab/ci/package-and-test/variables.gitlab-ci.yml @@ -6,6 +6,7 @@ variables: SKIP_REPORT_IN_ISSUES: "true" OMNIBUS_GITLAB_CACHE_UPDATE: "false" OMNIBUS_GITLAB_RUBY3_BUILD: "false" + OMNIBUS_GITLAB_RUBY2_BUILD: "false" OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB" QA_LOG_LEVEL: "info" QA_TESTS: "" diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index a72e6fc0137..aea85dfd084 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -89,6 +89,9 @@ e2e:package-and-test: - DOCKER_VERSION - REGISTRY_GROUP - REGISTRY_HOST + - OMNIBUS_GITLAB_CACHE_EDITION + - OMNIBUS_GITLAB_RUBY3_BUILD + - OMNIBUS_GITLAB_RUBY2_BUILD trigger: strategy: depend forward: diff --git a/.gitlab/ci/release-environments.gitlab-ci.yml b/.gitlab/ci/release-environments.gitlab-ci.yml new file mode 100644 index 00000000000..a9d9c938ee0 --- /dev/null +++ b/.gitlab/ci/release-environments.gitlab-ci.yml @@ -0,0 +1,22 @@ +--- +start-release-environments-pipeline: + allow_failure: true + extends: + - .release-environments:rules:start-release-environments-pipeline + stage: release-environments + # We do not want to have ALL global variables passed as trigger variables, + # as they cannot be overridden. See this issue for more context: + # + # https://gitlab.com/gitlab-org/gitlab/-/issues/387183 + inherit: + variables: false + + # These variables are set in the pipeline schedules. + # They need to be explicitly passed on to the child pipeline. + # https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#pass-cicd-variables-to-a-downstream-pipeline-by-using-the-variables-keyword + variables: + # This is needed by `release-environments-build-cng-env` (`.gitlab/ci/release-environments/main.gitlab-ci.yml`). + PARENT_PIPELINE_ID: $CI_PIPELINE_ID + trigger: + strategy: depend + include: .gitlab/ci/release-environments/main.gitlab-ci.yml diff --git a/.gitlab/ci/release-environments/main.gitlab-ci.yml b/.gitlab/ci/release-environments/main.gitlab-ci.yml new file mode 100644 index 00000000000..e2fed0a6dbd --- /dev/null +++ b/.gitlab/ci/release-environments/main.gitlab-ci.yml @@ -0,0 +1,62 @@ +--- +default: + interruptible: true + +stages: + - prepare + +include: + - local: .gitlab/ci/global.gitlab-ci.yml + +release-environments-build-cng-env: + allow_failure: true + image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}ruby:${RUBY_VERSION}-alpine3.16 + stage: prepare + needs: + # We need this job because we need its `cached-assets-hash.txt` artifact, so that we can pass the assets image tag to the downstream CNG pipeline. + - pipeline: $PARENT_PIPELINE_ID + job: build-assets-image + variables: + BUILD_ENV: build.env + before_script: + - source ./scripts/utils.sh + - install_gitlab_gem + script: + - 'ruby -r./scripts/trigger-build.rb -e "puts Trigger.variables_for_env_file(Trigger::CNG.new.variables)" > $BUILD_ENV' + - echo "GITLAB_ASSETS_TAG=$(assets_image_tag)" >> $BUILD_ENV + - ruby -e 'puts "FULL_RUBY_VERSION=#{RUBY_VERSION}"' >> build.env + - cat $BUILD_ENV + artifacts: + reports: + dotenv: $BUILD_ENV + paths: + - $BUILD_ENV + expire_in: 7 days + when: always + +release-environments-build-cng: + allow_failure: true + stage: prepare + needs: ["release-environments-build-cng-env"] + inherit: + variables: false + variables: + GITLAB_REF_SLUG: "${GITLAB_REF_SLUG}" + # CNG pipeline specific variables + GITLAB_VERSION: "${GITLAB_VERSION}" + GITLAB_TAG: "${GITLAB_TAG}" + GITLAB_ASSETS_TAG: "${GITLAB_ASSETS_TAG}" + FORCE_RAILS_IMAGE_BUILDS: "${FORCE_RAILS_IMAGE_BUILDS}" + CE_PIPELINE: "${CE_PIPELINE}" # Based on https://docs.gitlab.com/ee/ci/jobs/job_control.html#check-if-a-variable-exists, `if: '$CE_PIPELINE'` will evaluate to `false` when this variable is empty + EE_PIPELINE: "${EE_PIPELINE}" # Based on https://docs.gitlab.com/ee/ci/jobs/job_control.html#check-if-a-variable-exists, `if: '$EE_PIPELINE'` will evaluate to `false` when this variable is empty + GITLAB_ELASTICSEARCH_INDEXER_VERSION: "${GITLAB_ELASTICSEARCH_INDEXER_VERSION}" + GITLAB_KAS_VERSION: "${GITLAB_KAS_VERSION}" + GITLAB_METRICS_EXPORTER_VERSION: "${GITLAB_METRICS_EXPORTER_VERSION}" + GITLAB_PAGES_VERSION: "${GITLAB_PAGES_VERSION}" + GITLAB_SHELL_VERSION: "${GITLAB_SHELL_VERSION}" + GITALY_SERVER_VERSION: "${GITALY_SERVER_VERSION}" + RUBY_VERSION: "${FULL_RUBY_VERSION}" + trigger: + project: gitlab-org/build/CNG-mirror + branch: $TRIGGER_BRANCH + strategy: depend diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 894dafca8c3..066654565b2 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -1905,6 +1905,13 @@ when: never - if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/' +.releases:rules:canonical-dot-com-gitlab-stable-branch-only-setup-test-env-patterns: + rules: + - if: '$CI_COMMIT_MESSAGE =~ /\[merge-train skip\]/' + when: never + - if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/' + changes: *setup-test-env-patterns + .releases:rules:canonical-dot-com-security-gitlab-stable-branch-only: rules: - if: '$CI_COMMIT_MESSAGE =~ /\[merge-train skip\]/' @@ -2299,3 +2306,14 @@ - <<: *if-dot-com-gitlab-org-merge-request changes: *feature-flag-development-config-patterns allow_failure: true # See https://gitlab.com/gitlab-org/gitlab/-/issues/351136 + +############################## +# release-environments rules # +############################## +.release-environments:rules:start-release-environments-pipeline: + rules: + - <<: *if-not-ee + when: never + - <<: *if-merge-request-labels-pipeline-expedite + when: never + - !reference [".releases:rules:canonical-dot-com-gitlab-stable-branch-only-setup-test-env-patterns", rules] diff --git a/app/assets/javascripts/alert.js b/app/assets/javascripts/alert.js new file mode 100644 index 00000000000..006c4f50d09 --- /dev/null +++ b/app/assets/javascripts/alert.js @@ -0,0 +1,137 @@ +import * as Sentry from '@sentry/browser'; +import Vue from 'vue'; +import { GlAlert } from '@gitlab/ui'; +import { __ } from '~/locale'; + +export const VARIANT_SUCCESS = 'success'; +export const VARIANT_WARNING = 'warning'; +export const VARIANT_DANGER = 'danger'; +export const VARIANT_INFO = 'info'; +export const VARIANT_TIP = 'tip'; + +/** + * Render an alert at the top of the page, or, optionally an + * arbitrary existing container. This alert is always dismissible. + * + * @example + * // Render a new alert + * import { createAlert, VARIANT_WARNING } from '~/alert'; + * + * createAlert({ message: 'My error message' }); + * createAlert({ message: 'My warning message', variant: VARIANT_WARNING }); + * + * @example + * // Dismiss this alert programmatically + * const alert = createAlert({ message: 'Message' }); + * + * // ... + * + * alert.dismiss(); + * + * @example + * // Respond to the alert being dismissed + * createAlert({ message: 'Message', onDismiss: () => {} }); + * + * @param {object} options - Options to control the flash message + * @param {string} options.message - Alert message text + * @param {string} [options.title] - Alert title + * @param {VARIANT_SUCCESS|VARIANT_WARNING|VARIANT_DANGER|VARIANT_INFO|VARIANT_TIP} [options.variant] - Which GlAlert variant to use; it defaults to VARIANT_DANGER. + * @param {object} [options.parent] - Reference to parent element under which alert needs to appear. Defaults to `document`. + * @param {Function} [options.onDismiss] - Handler to call when this alert is dismissed. + * @param {string} [options.containerSelector] - Selector for the container of the alert + * @param {boolean} [options.preservePrevious] - Set to `true` to preserve previous alerts. Defaults to `false`. + * @param {object} [options.primaryButton] - Object describing primary button of alert + * @param {string} [options.primaryButton.link] - Href of primary button + * @param {string} [options.primaryButton.text] - Text of primary button + * @param {Function} [options.primaryButton.clickHandler] - Handler to call when primary button is clicked on. The click event is sent as an argument. + * @param {object} [options.secondaryButton] - Object describing secondary button of alert + * @param {string} [options.secondaryButton.link] - Href of secondary button + * @param {string} [options.secondaryButton.text] - Text of secondary button + * @param {Function} [options.secondaryButton.clickHandler] - Handler to call when secondary button is clicked on. The click event is sent as an argument. + * @param {boolean} [options.captureError] - Whether to send error to Sentry + * @param {object} [options.error] - Error to be captured in Sentry + */ +export const createAlert = ({ + message, + title, + variant = VARIANT_DANGER, + parent = document, + containerSelector = '.flash-container', + preservePrevious = false, + primaryButton = null, + secondaryButton = null, + onDismiss = null, + captureError = false, + error = null, +}) => { + if (captureError && error) Sentry.captureException(error); + + const alertContainer = parent.querySelector(containerSelector); + if (!alertContainer) return null; + + const el = document.createElement('div'); + if (preservePrevious) { + alertContainer.appendChild(el); + } else { + alertContainer.replaceChildren(el); + } + + return new Vue({ + el, + components: { + GlAlert, + }, + methods: { + /** + * Public method to dismiss this alert and removes + * this Vue instance. + */ + dismiss() { + if (onDismiss) { + onDismiss(); + } + this.$destroy(); + this.$el.parentNode?.removeChild(this.$el); + }, + }, + render(h) { + const on = {}; + + on.dismiss = () => { + this.dismiss(); + }; + + if (primaryButton?.clickHandler) { + on.primaryAction = (e) => { + primaryButton.clickHandler(e); + }; + } + if (secondaryButton?.clickHandler) { + on.secondaryAction = (e) => { + secondaryButton.clickHandler(e); + }; + } + + return h( + GlAlert, + { + props: { + title, + dismissible: true, + dismissLabel: __('Dismiss'), + variant, + primaryButtonLink: primaryButton?.link, + primaryButtonText: primaryButton?.text, + secondaryButtonLink: secondaryButton?.link, + secondaryButtonText: secondaryButton?.text, + }, + attrs: { + 'data-testid': `alert-${variant}`, + }, + on, + }, + message, + ); + }, + }); +}; diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js deleted file mode 100644 index 483f1d2c7a0..00000000000 --- a/app/assets/javascripts/flash.js +++ /dev/null @@ -1,137 +0,0 @@ -import * as Sentry from '@sentry/browser'; -import Vue from 'vue'; -import { GlAlert } from '@gitlab/ui'; -import { __ } from '~/locale'; - -export const VARIANT_SUCCESS = 'success'; -export const VARIANT_WARNING = 'warning'; -export const VARIANT_DANGER = 'danger'; -export const VARIANT_INFO = 'info'; -export const VARIANT_TIP = 'tip'; - -/** - * Render an alert at the top of the page, or, optionally an - * arbitrary existing container. This alert is always dismissible. - * - * @example - * // Render a new alert - * import { createAlert, VARIANT_WARNING } from '~/flash'; - * - * createAlert({ message: 'My error message' }); - * createAlert({ message: 'My warning message', variant: VARIANT_WARNING }); - * - * @example - * // Dismiss this alert programmatically - * const alert = createAlert({ message: 'Message' }); - * - * // ... - * - * alert.dismiss(); - * - * @example - * // Respond to the alert being dismissed - * createAlert({ message: 'Message', onDismiss: () => {} }); - * - * @param {object} options - Options to control the flash message - * @param {string} options.message - Alert message text - * @param {string} [options.title] - Alert title - * @param {VARIANT_SUCCESS|VARIANT_WARNING|VARIANT_DANGER|VARIANT_INFO|VARIANT_TIP} [options.variant] - Which GlAlert variant to use; it defaults to VARIANT_DANGER. - * @param {object} [options.parent] - Reference to parent element under which alert needs to appear. Defaults to `document`. - * @param {Function} [options.onDismiss] - Handler to call when this alert is dismissed. - * @param {string} [options.containerSelector] - Selector for the container of the alert - * @param {boolean} [options.preservePrevious] - Set to `true` to preserve previous alerts. Defaults to `false`. - * @param {object} [options.primaryButton] - Object describing primary button of alert - * @param {string} [options.primaryButton.link] - Href of primary button - * @param {string} [options.primaryButton.text] - Text of primary button - * @param {Function} [options.primaryButton.clickHandler] - Handler to call when primary button is clicked on. The click event is sent as an argument. - * @param {object} [options.secondaryButton] - Object describing secondary button of alert - * @param {string} [options.secondaryButton.link] - Href of secondary button - * @param {string} [options.secondaryButton.text] - Text of secondary button - * @param {Function} [options.secondaryButton.clickHandler] - Handler to call when secondary button is clicked on. The click event is sent as an argument. - * @param {boolean} [options.captureError] - Whether to send error to Sentry - * @param {object} [options.error] - Error to be captured in Sentry - */ -export const createAlert = ({ - message, - title, - variant = VARIANT_DANGER, - parent = document, - containerSelector = '.flash-container', - preservePrevious = false, - primaryButton = null, - secondaryButton = null, - onDismiss = null, - captureError = false, - error = null, -}) => { - if (captureError && error) Sentry.captureException(error); - - const alertContainer = parent.querySelector(containerSelector); - if (!alertContainer) return null; - - const el = document.createElement('div'); - if (preservePrevious) { - alertContainer.appendChild(el); - } else { - alertContainer.replaceChildren(el); - } - - return new Vue({ - el, - components: { - GlAlert, - }, - methods: { - /** - * Public method to dismiss this alert and removes - * this Vue instance. - */ - dismiss() { - if (onDismiss) { - onDismiss(); - } - this.$destroy(); - this.$el.parentNode?.removeChild(this.$el); - }, - }, - render(h) { - const on = {}; - - on.dismiss = () => { - this.dismiss(); - }; - - if (primaryButton?.clickHandler) { - on.primaryAction = (e) => { - primaryButton.clickHandler(e); - }; - } - if (secondaryButton?.clickHandler) { - on.secondaryAction = (e) => { - secondaryButton.clickHandler(e); - }; - } - - return h( - GlAlert, - { - props: { - title, - dismissible: true, - dismissLabel: __('Dismiss'), - variant, - primaryButtonLink: primaryButton?.link, - primaryButtonText: primaryButton?.text, - secondaryButtonLink: secondaryButton?.link, - secondaryButtonText: secondaryButton?.text, - }, - attrs: { - 'data-testid': `alert-${variant}`, - }, - on, - }, - message, - ); - }, - }); -}; diff --git a/app/assets/javascripts/service_ping_consent.js b/app/assets/javascripts/service_ping_consent.js index 654263ba27b..1cb4e188e54 100644 --- a/app/assets/javascripts/service_ping_consent.js +++ b/app/assets/javascripts/service_ping_consent.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import { createAlert } from './flash'; +import { createAlert } from '~/flash'; import axios from './lib/utils/axios_utils'; import { parseBoolean } from './lib/utils/common_utils'; import { __ } from './locale'; diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue index 100f1d18793..173e62cefbd 100644 --- a/app/assets/javascripts/super_sidebar/components/nav_item.vue +++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue @@ -1,11 +1,12 @@ ' }); + + const html = document.querySelector('.flash-container').innerHTML; + + expect(html).toContain('<script>alert("a");</script>'); + expect(html).not.toContain(''); + }); + + it('adds alert into specified container', () => { + setHTMLFixture(` +
+
+ `); + + alert = createAlert({ message: mockMessage, containerSelector: '.my-alert-container' }); + + expect(document.querySelector('.my-alert-container .gl-alert')).not.toBeNull(); + expect(document.querySelector('.my-alert-container').innerText.trim()).toBe(mockMessage); + + expect(document.querySelector('.my-other-container .gl-alert')).toBeNull(); + expect(document.querySelector('.my-other-container').innerText.trim()).toBe(''); + }); + + it('adds alert into specified parent', () => { + setHTMLFixture(` +
+
+
+
+
+
+ `); + + alert = createAlert({ message: mockMessage, parent: document.getElementById('my-parent') }); + + expect(document.querySelector('#my-parent .flash-container .gl-alert')).not.toBeNull(); + expect(document.querySelector('#my-parent .flash-container').innerText.trim()).toBe( + mockMessage, + ); + + expect(document.querySelector('#my-other-parent .flash-container .gl-alert')).toBeNull(); + expect(document.querySelector('#my-other-parent .flash-container').innerText.trim()).toBe( + '', + ); + }); + + it('removes element after clicking', () => { + alert = createAlert({ message: mockMessage }); + + expect(document.querySelector('.flash-container .gl-alert')).not.toBeNull(); + + document.querySelector('.gl-dismiss-btn').click(); + + expect(document.querySelector('.flash-container .gl-alert')).toBeNull(); + }); + + it('does not capture error using Sentry', () => { + alert = createAlert({ + message: mockMessage, + captureError: false, + error: new Error('Error!'), + }); + + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + it('captures error using Sentry', () => { + alert = createAlert({ + message: mockMessage, + captureError: true, + error: new Error('Error!'), + }); + + expect(Sentry.captureException).toHaveBeenCalledWith(expect.any(Error)); + expect(Sentry.captureException).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Error!', + }), + ); + }); + + describe('with title', () => { + const mockTitle = 'my title'; + + it('shows title and message', () => { + createAlert({ + title: mockTitle, + message: mockMessage, + }); + + expect(findTextContent()).toBe(`${mockTitle} ${mockMessage}`); + }); + }); + + describe('with buttons', () => { + const findAlertAction = () => document.querySelector('.flash-container .gl-alert-action'); + + it('adds primary button', () => { + alert = createAlert({ + message: mockMessage, + primaryButton: { + text: 'Ok', + }, + }); + + expect(findAlertAction().textContent.trim()).toBe('Ok'); + }); + + it('creates link with href', () => { + alert = createAlert({ + message: mockMessage, + primaryButton: { + link: '/url', + text: 'Ok', + }, + }); + + const action = findAlertAction(); + + expect(action.textContent.trim()).toBe('Ok'); + expect(action.nodeName).toBe('A'); + expect(action.getAttribute('href')).toBe('/url'); + }); + + it('create button as href when no href is present', () => { + alert = createAlert({ + message: mockMessage, + primaryButton: { + text: 'Ok', + }, + }); + + const action = findAlertAction(); + + expect(action.nodeName).toBe('BUTTON'); + expect(action.getAttribute('href')).toBe(null); + }); + + it('escapes the title text', () => { + alert = createAlert({ + message: mockMessage, + primaryButton: { + text: '', + }, + }); + + const html = findAlertAction().innerHTML; + + expect(html).toContain('<script>alert("a")</script>'); + expect(html).not.toContain(''); + }); + + it('calls actionConfig clickHandler on click', () => { + const clickHandler = jest.fn(); + + alert = createAlert({ + message: mockMessage, + primaryButton: { + text: 'Ok', + clickHandler, + }, + }); + + expect(clickHandler).toHaveBeenCalledTimes(0); + + findAlertAction().click(); + + expect(clickHandler).toHaveBeenCalledTimes(1); + expect(clickHandler).toHaveBeenCalledWith(expect.any(MouseEvent)); + }); + }); + + describe('Alert API', () => { + describe('dismiss', () => { + it('dismiss programmatically with .dismiss()', () => { + expect(document.querySelector('.gl-alert')).toBeNull(); + + alert = createAlert({ message: mockMessage }); + + expect(document.querySelector('.gl-alert')).not.toBeNull(); + + alert.dismiss(); + + expect(document.querySelector('.gl-alert')).toBeNull(); + }); + + it('does not crash if calling .dismiss() twice', () => { + alert = createAlert({ message: mockMessage }); + + alert.dismiss(); + expect(() => alert.dismiss()).not.toThrow(); + }); + + it('calls onDismiss when dismissed', () => { + const dismissHandler = jest.fn(); + + alert = createAlert({ message: mockMessage, onDismiss: dismissHandler }); + + expect(dismissHandler).toHaveBeenCalledTimes(0); + + alert.dismiss(); + + expect(dismissHandler).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('when called multiple times', () => { + it('clears previous alerts', () => { + createAlert({ message: 'message 1' }); + createAlert({ message: 'message 2' }); + + expect(findTextContent()).toBe('message 2'); + }); + + it('preserves alerts when `preservePrevious` is true', () => { + createAlert({ message: 'message 1' }); + createAlert({ message: 'message 2', preservePrevious: true }); + + expect(findTextContent()).toBe('message 1 message 2'); + }); + }); + }); + }); +}); diff --git a/spec/frontend/clusters_list/store/actions_spec.js b/spec/frontend/clusters_list/store/actions_spec.js index 360fd3b2842..a2e2db3dcc2 100644 --- a/spec/frontend/clusters_list/store/actions_spec.js +++ b/spec/frontend/clusters_list/store/actions_spec.js @@ -11,7 +11,7 @@ import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status import Poll from '~/lib/utils/poll'; import { apiData } from '../mock_data'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); describe('Clusters store actions', () => { let captureException; diff --git a/spec/frontend/contributors/store/actions_spec.js b/spec/frontend/contributors/store/actions_spec.js index b2ebdf2f53c..72b22548aa2 100644 --- a/spec/frontend/contributors/store/actions_spec.js +++ b/spec/frontend/contributors/store/actions_spec.js @@ -6,7 +6,7 @@ import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); describe('Contributors store actions', () => { describe('fetchChartData', () => { diff --git a/spec/frontend/deploy_freeze/store/actions_spec.js b/spec/frontend/deploy_freeze/store/actions_spec.js index 9b96ce5d252..cd823e1fc28 100644 --- a/spec/frontend/deploy_freeze/store/actions_spec.js +++ b/spec/frontend/deploy_freeze/store/actions_spec.js @@ -11,7 +11,7 @@ import { freezePeriodsFixture } from '../helpers'; import { timezoneDataFixture } from '../../vue_shared/components/timezone_dropdown/helpers'; jest.mock('~/api.js'); -jest.mock('~/flash.js'); +jest.mock('~/flash'); describe('deploy freeze store actions', () => { const freezePeriodFixture = freezePeriodsFixture[0]; diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js index 76ece922ded..5f147220ef9 100644 --- a/spec/frontend/design_management/pages/index_spec.js +++ b/spec/frontend/design_management/pages/index_spec.js @@ -41,7 +41,7 @@ import { moveDesignMutationResponseWithErrors, } from '../mock_data/apollo_mock'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); const mockPageEl = { classList: { remove: jest.fn(), diff --git a/spec/frontend/design_management/utils/cache_update_spec.js b/spec/frontend/design_management/utils/cache_update_spec.js index 42777adfd58..1c8075fac02 100644 --- a/spec/frontend/design_management/utils/cache_update_spec.js +++ b/spec/frontend/design_management/utils/cache_update_spec.js @@ -13,7 +13,7 @@ import { import { createAlert } from '~/flash'; import design from '../mock_data/design'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); describe('Design Management cache update', () => { const mockErrors = ['code red!']; diff --git a/spec/frontend/error_tracking/store/actions_spec.js b/spec/frontend/error_tracking/store/actions_spec.js index 3ec43010d80..31bfda3ef8b 100644 --- a/spec/frontend/error_tracking/store/actions_spec.js +++ b/spec/frontend/error_tracking/store/actions_spec.js @@ -7,7 +7,7 @@ import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { visitUrl } from '~/lib/utils/url_utility'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); jest.mock('~/lib/utils/url_utility'); let mock; diff --git a/spec/frontend/error_tracking/store/details/actions_spec.js b/spec/frontend/error_tracking/store/details/actions_spec.js index 383d8aaeb20..66c1eb4573b 100644 --- a/spec/frontend/error_tracking/store/details/actions_spec.js +++ b/spec/frontend/error_tracking/store/details/actions_spec.js @@ -14,7 +14,7 @@ import Poll from '~/lib/utils/poll'; let mockedAdapter; let mockedRestart; -jest.mock('~/flash.js'); +jest.mock('~/flash'); jest.mock('~/lib/utils/url_utility'); describe('Sentry error details store actions', () => { diff --git a/spec/frontend/error_tracking/store/list/actions_spec.js b/spec/frontend/error_tracking/store/list/actions_spec.js index 590983bd93d..e6c2d4a149d 100644 --- a/spec/frontend/error_tracking/store/list/actions_spec.js +++ b/spec/frontend/error_tracking/store/list/actions_spec.js @@ -6,7 +6,7 @@ import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); describe('error tracking actions', () => { let mock; diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js deleted file mode 100644 index 17d6cea23df..00000000000 --- a/spec/frontend/flash_spec.js +++ /dev/null @@ -1,276 +0,0 @@ -import * as Sentry from '@sentry/browser'; -import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; -import { createAlert, VARIANT_WARNING } from '~/flash'; - -jest.mock('@sentry/browser'); - -describe('Flash', () => { - const findTextContent = (containerSelector = '.flash-container') => - document.querySelector(containerSelector).textContent.replace(/\s+/g, ' ').trim(); - - describe('createAlert', () => { - const mockMessage = 'a message'; - let alert; - - describe('no flash-container', () => { - it('does not add to the DOM', () => { - alert = createAlert({ message: mockMessage }); - - expect(alert).toBeNull(); - expect(document.querySelector('.gl-alert')).toBeNull(); - }); - }); - - describe('with flash-container', () => { - beforeEach(() => { - setHTMLFixture('
'); - }); - - afterEach(() => { - if (alert) { - alert.$destroy(); - } - resetHTMLFixture(); - }); - - it('adds alert element into the document by default', () => { - alert = createAlert({ message: mockMessage }); - - expect(findTextContent()).toBe(mockMessage); - expect(document.querySelector('.flash-container .gl-alert')).not.toBeNull(); - }); - - it('adds flash of a warning type', () => { - alert = createAlert({ message: mockMessage, variant: VARIANT_WARNING }); - - expect( - document.querySelector('.flash-container .gl-alert.gl-alert-warning'), - ).not.toBeNull(); - }); - - it('escapes text', () => { - alert = createAlert({ message: '' }); - - const html = document.querySelector('.flash-container').innerHTML; - - expect(html).toContain('<script>alert("a");</script>'); - expect(html).not.toContain(''); - }); - - it('adds alert into specified container', () => { - setHTMLFixture(` -
-
- `); - - alert = createAlert({ message: mockMessage, containerSelector: '.my-alert-container' }); - - expect(document.querySelector('.my-alert-container .gl-alert')).not.toBeNull(); - expect(document.querySelector('.my-alert-container').innerText.trim()).toBe(mockMessage); - - expect(document.querySelector('.my-other-container .gl-alert')).toBeNull(); - expect(document.querySelector('.my-other-container').innerText.trim()).toBe(''); - }); - - it('adds alert into specified parent', () => { - setHTMLFixture(` -
-
-
-
-
-
- `); - - alert = createAlert({ message: mockMessage, parent: document.getElementById('my-parent') }); - - expect(document.querySelector('#my-parent .flash-container .gl-alert')).not.toBeNull(); - expect(document.querySelector('#my-parent .flash-container').innerText.trim()).toBe( - mockMessage, - ); - - expect(document.querySelector('#my-other-parent .flash-container .gl-alert')).toBeNull(); - expect(document.querySelector('#my-other-parent .flash-container').innerText.trim()).toBe( - '', - ); - }); - - it('removes element after clicking', () => { - alert = createAlert({ message: mockMessage }); - - expect(document.querySelector('.flash-container .gl-alert')).not.toBeNull(); - - document.querySelector('.gl-dismiss-btn').click(); - - expect(document.querySelector('.flash-container .gl-alert')).toBeNull(); - }); - - it('does not capture error using Sentry', () => { - alert = createAlert({ - message: mockMessage, - captureError: false, - error: new Error('Error!'), - }); - - expect(Sentry.captureException).not.toHaveBeenCalled(); - }); - - it('captures error using Sentry', () => { - alert = createAlert({ - message: mockMessage, - captureError: true, - error: new Error('Error!'), - }); - - expect(Sentry.captureException).toHaveBeenCalledWith(expect.any(Error)); - expect(Sentry.captureException).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Error!', - }), - ); - }); - - describe('with title', () => { - const mockTitle = 'my title'; - - it('shows title and message', () => { - createAlert({ - title: mockTitle, - message: mockMessage, - }); - - expect(findTextContent()).toBe(`${mockTitle} ${mockMessage}`); - }); - }); - - describe('with buttons', () => { - const findAlertAction = () => document.querySelector('.flash-container .gl-alert-action'); - - it('adds primary button', () => { - alert = createAlert({ - message: mockMessage, - primaryButton: { - text: 'Ok', - }, - }); - - expect(findAlertAction().textContent.trim()).toBe('Ok'); - }); - - it('creates link with href', () => { - alert = createAlert({ - message: mockMessage, - primaryButton: { - link: '/url', - text: 'Ok', - }, - }); - - const action = findAlertAction(); - - expect(action.textContent.trim()).toBe('Ok'); - expect(action.nodeName).toBe('A'); - expect(action.getAttribute('href')).toBe('/url'); - }); - - it('create button as href when no href is present', () => { - alert = createAlert({ - message: mockMessage, - primaryButton: { - text: 'Ok', - }, - }); - - const action = findAlertAction(); - - expect(action.nodeName).toBe('BUTTON'); - expect(action.getAttribute('href')).toBe(null); - }); - - it('escapes the title text', () => { - alert = createAlert({ - message: mockMessage, - primaryButton: { - text: '', - }, - }); - - const html = findAlertAction().innerHTML; - - expect(html).toContain('<script>alert("a")</script>'); - expect(html).not.toContain(''); - }); - - it('calls actionConfig clickHandler on click', () => { - const clickHandler = jest.fn(); - - alert = createAlert({ - message: mockMessage, - primaryButton: { - text: 'Ok', - clickHandler, - }, - }); - - expect(clickHandler).toHaveBeenCalledTimes(0); - - findAlertAction().click(); - - expect(clickHandler).toHaveBeenCalledTimes(1); - expect(clickHandler).toHaveBeenCalledWith(expect.any(MouseEvent)); - }); - }); - - describe('Alert API', () => { - describe('dismiss', () => { - it('dismiss programmatically with .dismiss()', () => { - expect(document.querySelector('.gl-alert')).toBeNull(); - - alert = createAlert({ message: mockMessage }); - - expect(document.querySelector('.gl-alert')).not.toBeNull(); - - alert.dismiss(); - - expect(document.querySelector('.gl-alert')).toBeNull(); - }); - - it('does not crash if calling .dismiss() twice', () => { - alert = createAlert({ message: mockMessage }); - - alert.dismiss(); - expect(() => alert.dismiss()).not.toThrow(); - }); - - it('calls onDismiss when dismissed', () => { - const dismissHandler = jest.fn(); - - alert = createAlert({ message: mockMessage, onDismiss: dismissHandler }); - - expect(dismissHandler).toHaveBeenCalledTimes(0); - - alert.dismiss(); - - expect(dismissHandler).toHaveBeenCalledTimes(1); - }); - }); - }); - - describe('when called multiple times', () => { - it('clears previous alerts', () => { - createAlert({ message: 'message 1' }); - createAlert({ message: 'message 2' }); - - expect(findTextContent()).toBe('message 2'); - }); - - it('preserves alerts when `preservePrevious` is true', () => { - createAlert({ message: 'message 1' }); - createAlert({ message: 'message 2', preservePrevious: true }); - - expect(findTextContent()).toBe('message 1 message 2'); - }); - }); - }); - }); -}); diff --git a/spec/frontend/merge_conflicts/store/actions_spec.js b/spec/frontend/merge_conflicts/store/actions_spec.js index 19ef4b7db25..a5519331347 100644 --- a/spec/frontend/merge_conflicts/store/actions_spec.js +++ b/spec/frontend/merge_conflicts/store/actions_spec.js @@ -10,7 +10,7 @@ import * as actions from '~/merge_conflicts/store/actions'; import * as types from '~/merge_conflicts/store/mutation_types'; import { restoreFileLinesState, markLine, decorateFiles } from '~/merge_conflicts/utils'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); jest.mock('~/merge_conflicts/utils'); jest.mock('~/lib/utils/cookies'); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js index bb970336b94..ae5742fdf6c 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js @@ -15,7 +15,7 @@ import { } from '~/packages_and_registries/shared/constants'; import { npmPackage as packageEntity } from '../../mock_data'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); jest.mock('~/api.js'); describe('Actions Package details store', () => { diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js index 2c185e040f4..b5251aba40f 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js @@ -9,7 +9,7 @@ import * as actions from '~/packages_and_registries/infrastructure_registry/list import * as types from '~/packages_and_registries/infrastructure_registry/list/stores/mutation_types'; import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages_and_registries/shared/constants'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); jest.mock('~/api.js'); describe('Actions Package list store', () => { diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js index f6287107ed0..e813a63a53f 100644 --- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js @@ -8,7 +8,7 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import * as actions from '~/pipelines/stores/test_reports/actions'; import * as types from '~/pipelines/stores/test_reports/mutation_types'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); describe('Actions TestReports Store', () => { let mock; diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js index ed0cc71eb97..82c70c6db58 100644 --- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js @@ -3,7 +3,7 @@ import * as types from '~/pipelines/stores/test_reports/mutation_types'; import mutations from '~/pipelines/stores/test_reports/mutations'; import { createAlert } from '~/flash'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); describe('Mutations TestReports Store', () => { let mockState; diff --git a/spec/frontend/projects/commit/store/actions_spec.js b/spec/frontend/projects/commit/store/actions_spec.js index 008710984b9..1502985cfc7 100644 --- a/spec/frontend/projects/commit/store/actions_spec.js +++ b/spec/frontend/projects/commit/store/actions_spec.js @@ -8,7 +8,7 @@ import * as types from '~/projects/commit/store/mutation_types'; import getInitialState from '~/projects/commit/store/state'; import mockData from '../mock_data'; -jest.mock('~/flash.js'); +jest.mock('~/flash'); describe('Commit form modal store actions', () => { let axiosMock; diff --git a/spec/frontend/super_sidebar/components/nav_item_spec.js b/spec/frontend/super_sidebar/components/nav_item_spec.js new file mode 100644 index 00000000000..deea4f13c55 --- /dev/null +++ b/spec/frontend/super_sidebar/components/nav_item_spec.js @@ -0,0 +1,34 @@ +import { GlBadge } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import NavItem from '~/super_sidebar/components/nav_item.vue'; + +describe('NavItem component', () => { + let wrapper; + + const findPill = () => wrapper.findComponent(GlBadge); + const createWrapper = (item, props = {}) => { + wrapper = shallowMountExtended(NavItem, { + propsData: { + item, + ...props, + }, + }); + }; + + describe('pills', () => { + it.each([0, 5, 3.4, 'foo', '10%'])('item with pill_data `%p` renders a pill', (pillCount) => { + createWrapper({ title: 'Foo', pill_count: pillCount }); + + expect(findPill().text()).toEqual(pillCount.toString()); + }); + + it.each([null, undefined, false, true, '', NaN, Number.POSITIVE_INFINITY])( + 'item with pill_data `%p` renders no pill', + (pillCount) => { + createWrapper({ title: 'Foo', pill_count: pillCount }); + + expect(findPill().exists()).toEqual(false); + }, + ); + }); +}); diff --git a/spec/frontend/super_sidebar/components/user_bar_spec.js b/spec/frontend/super_sidebar/components/user_bar_spec.js index eceb792c3db..a5faad967fc 100644 --- a/spec/frontend/super_sidebar/components/user_bar_spec.js +++ b/spec/frontend/super_sidebar/components/user_bar_spec.js @@ -1,3 +1,4 @@ +import { GlBadge } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { __ } from '~/locale'; import CreateMenu from '~/super_sidebar/components/create_menu.vue'; @@ -13,11 +14,10 @@ describe('UserBar component', () => { const findCounter = (at) => wrapper.findAllComponents(Counter).at(at); const findMergeRequestMenu = () => wrapper.findComponent(MergeRequestMenu); - const createWrapper = (props = {}) => { + const createWrapper = (extraSidebarData = {}) => { wrapper = shallowMountExtended(UserBar, { propsData: { - sidebarData, - ...props, + sidebarData: { ...sidebarData, ...extraSidebarData }, }, provide: { rootPath: '/', @@ -56,4 +56,23 @@ describe('UserBar component', () => { expect(findCounter(2).props('label')).toBe(__('To-Do list')); }); }); + + describe('GitLab Next badge', () => { + describe('when on canary', () => { + it('should render a badge to switch off GitLab Next', () => { + createWrapper({ gitlab_com_and_canary: true }); + const badge = wrapper.findComponent(GlBadge); + expect(badge.text()).toBe('Next'); + expect(badge.attributes('href')).toBe(sidebarData.canary_toggle_com_url); + }); + }); + + describe('when not on canary', () => { + it('should not render the GitLab Next badge', () => { + createWrapper({ gitlab_com_and_canary: false }); + const badge = wrapper.findComponent(GlBadge); + expect(badge.exists()).toBe(false); + }); + }); + }); }); diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js index 52dad82aca6..d549018ca67 100644 --- a/spec/frontend/super_sidebar/mock_data.js +++ b/spec/frontend/super_sidebar/mock_data.js @@ -81,6 +81,8 @@ export const sidebarData = { show_version_check: false, gitlab_version: { major: 16, minor: 0 }, gitlab_version_check: { severity: 'success' }, + gitlab_com_and_canary: false, + canary_toggle_com_url: 'https://next.gitlab.com', }; export const userMenuMockStatus = { diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index f40e897925c..318fbea746a 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -123,6 +123,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do gitlab_version: Gitlab.version_info, gitlab_version_check: helper.gitlab_version_check, gitlab_com_but_not_canary: Gitlab.com_but_not_canary?, + gitlab_com_and_canary: Gitlab.com_and_canary?, canary_toggle_com_url: Gitlab::Saas.canary_toggle_com_url }) end diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb index 9c6f2e1364f..0ef39a22932 100644 --- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb @@ -97,6 +97,36 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project, feature_category: :p end end + context 'when a valid path is used in uppercase' do + let(:params) do + { project: project.full_path.upcase, file: '/file.yml' } + end + + around do |example| + create_and_delete_files(project, { '/file.yml' => 'image: image:1.0' }) do + example.run + end + end + + it { is_expected.to be_truthy } + end + + context 'when a valid different case path is used' do + let_it_be(:project) { create(:project, :repository, path: 'mY-teSt-proJect', name: 'My Test Project') } + + let(:params) do + { project: "#{project.namespace.full_path}/my-test-projecT", file: '/file.yml' } + end + + around do |example| + create_and_delete_files(project, { '/file.yml' => 'image: image:1.0' }) do + example.run + end + end + + it { is_expected.to be_truthy } + end + context 'when a valid path with custom ref is used' do let(:params) do { project: project.full_path, ref: 'master', file: '/file.yml' } diff --git a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb index b4576fb7a1d..478b26e97cd 100644 --- a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb @@ -84,42 +84,101 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category: end context 'when files are project files' do - let_it_be(:included_project) { create(:project, :repository, namespace: project.namespace, creator: user) } + let_it_be(:included_project1) { create(:project, :repository, namespace: project.namespace, creator: user) } + let_it_be(:included_project2) { create(:project, :repository, namespace: project.namespace, creator: user) } let(:files) do [ Gitlab::Ci::Config::External::File::Project.new( - { file: 'myfolder/file1.yml', project: included_project.full_path }, context + { file: 'myfolder/file1.yml', project: included_project1.full_path }, context ), Gitlab::Ci::Config::External::File::Project.new( - { file: 'myfolder/file2.yml', project: included_project.full_path }, context + { file: 'myfolder/file2.yml', project: included_project1.full_path }, context ), Gitlab::Ci::Config::External::File::Project.new( - { file: 'myfolder/file3.yml', project: included_project.full_path }, context + { file: 'myfolder/file3.yml', project: included_project1.full_path, ref: 'master' }, context + ), + Gitlab::Ci::Config::External::File::Project.new( + { file: 'myfolder/file1.yml', project: included_project2.full_path }, context + ), + Gitlab::Ci::Config::External::File::Project.new( + { file: 'myfolder/file2.yml', project: included_project2.full_path }, context ) ] end around(:all) do |example| - create_and_delete_files(included_project, project_files) do - example.run + create_and_delete_files(included_project1, project_files) do + create_and_delete_files(included_project2, project_files) do + example.run + end end end it 'returns an array of file objects' do expect(process.map(&:location)).to contain_exactly( - 'myfolder/file1.yml', 'myfolder/file2.yml', 'myfolder/file3.yml' + 'myfolder/file1.yml', 'myfolder/file2.yml', 'myfolder/file3.yml', 'myfolder/file1.yml', 'myfolder/file2.yml' ) end it 'adds files to the expandset' do - expect { process }.to change { context.expandset.count }.by(3) + expect { process }.to change { context.expandset.count }.by(5) end it 'calls Gitaly only once for all files', :request_store do - # 1 for project.commit.id, 3 for the sha check, 1 for the files + files # calling this to load project creations and the `project.commit.id` call + + # 3 for the sha check, 2 for the files in batch expect { process }.to change { Gitlab::GitalyClient.get_request_count }.by(5) end + + it 'queries with batch', :use_sql_query_cache do + files # calling this to load project creations and the `project.commit.id` call + + queries = ActiveRecord::QueryRecorder.new(skip_cached: false) { process } + projects_queries = queries.occurrences_starting_with('SELECT "projects"') + access_check_queries = queries.occurrences_starting_with('SELECT MAX("project_authorizations"."access_level")') + + expect(projects_queries.values.sum).to eq(1) + expect(access_check_queries.values.sum).to eq(2) + end + + context 'when the FF ci_batch_project_includes_context is disabled' do + before do + stub_feature_flags(ci_batch_project_includes_context: false) + end + + it 'returns an array of file objects' do + expect(process.map(&:location)).to contain_exactly( + 'myfolder/file1.yml', 'myfolder/file2.yml', 'myfolder/file3.yml', + 'myfolder/file1.yml', 'myfolder/file2.yml' + ) + end + + it 'adds files to the expandset' do + expect { process }.to change { context.expandset.count }.by(5) + end + + it 'calls Gitaly for all files', :request_store do + files # calling this to load project creations and the `project.commit.id` call + + # 5 for the sha check, 2 for the files in batch + expect { process }.to change { Gitlab::GitalyClient.get_request_count }.by(7) + end + + it 'queries without batch', :use_sql_query_cache do + files # calling this to load project creations and the `project.commit.id` call + + queries = ActiveRecord::QueryRecorder.new(skip_cached: false) { process } + projects_queries = queries.occurrences_starting_with('SELECT "projects"') + access_check_queries = queries.occurrences_starting_with( + 'SELECT MAX("project_authorizations"."access_level")' + ) + + expect(projects_queries.values.sum).to eq(5) + expect(access_check_queries.values.sum).to eq(5) + end + end end context 'when a file includes other files' do diff --git a/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb index 6f75e2c55e8..393d65ff102 100644 --- a/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb +++ b/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do +RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer, feature_category: :vulnerability_management do let(:identifier) { build(:ci_reports_security_identifier) } let_it_be(:project) { create(:project, :repository) } @@ -90,6 +90,18 @@ RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do expect(subject.added).to eq([vuln, low_vuln]) end end + + describe 'number of findings' do + let(:head_report) { build(:ci_reports_security_aggregated_reports, findings: [head_vulnerability, vuln, low_vuln]) } + + before do + stub_const("#{described_class}::MAX_FINDINGS_COUNT", 1) + end + + it 'returns no more than `MAX_FINDINGS_COUNT`' do + expect(subject.added).to eq([vuln]) + end + end end describe '#fixed' do @@ -123,6 +135,18 @@ RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do expect(subject.fixed).to eq(vul_findings) end end + + describe 'number of findings' do + let(:base_report) { build(:ci_reports_security_aggregated_reports, findings: [vuln, medium_vuln, base_vulnerability]) } + + before do + stub_const("#{described_class}::MAX_FINDINGS_COUNT", 1) + end + + it 'returns no more than `MAX_FINDINGS_COUNT`' do + expect(subject.fixed).to eq([vuln]) + end + end end describe 'with empty vulnerabilities' do diff --git a/spec/lib/gitlab/sidekiq_queue_spec.rb b/spec/lib/gitlab/sidekiq_queue_spec.rb index 5e91282612e..93632848788 100644 --- a/spec/lib/gitlab/sidekiq_queue_spec.rb +++ b/spec/lib/gitlab/sidekiq_queue_spec.rb @@ -4,15 +4,15 @@ require 'spec_helper' RSpec.describe Gitlab::SidekiqQueue, :clean_gitlab_redis_queues do around do |example| - Sidekiq::Queue.new('default').clear + Sidekiq::Queue.new('foobar').clear Sidekiq::Testing.disable!(&example) - Sidekiq::Queue.new('default').clear + Sidekiq::Queue.new('foobar').clear end def add_job(args, user:, klass: 'AuthorizedProjectsWorker') Sidekiq::Client.push( 'class' => klass, - 'queue' => 'default', + 'queue' => 'foobar', 'args' => args, 'meta.user' => user.username ) @@ -20,7 +20,7 @@ RSpec.describe Gitlab::SidekiqQueue, :clean_gitlab_redis_queues do describe '#drop_jobs!' do shared_examples 'queue processing' do - let(:sidekiq_queue) { described_class.new('default') } + let(:sidekiq_queue) { described_class.new('foobar') } let_it_be(:sidekiq_queue_user) { create(:user) } before do @@ -80,7 +80,7 @@ RSpec.describe Gitlab::SidekiqQueue, :clean_gitlab_redis_queues do it 'raises NoMetadataError' do add_job([1], user: create(:user)) - expect { described_class.new('default').drop_jobs!({ username: 'sidekiq_queue_user' }, timeout: 1) } + expect { described_class.new('foobar').drop_jobs!({ username: 'sidekiq_queue_user' }, timeout: 1) } .to raise_error(described_class::NoMetadataError) end end diff --git a/spec/lib/sidebars/menu_spec.rb b/spec/lib/sidebars/menu_spec.rb index 7c4d74aecc8..641f1c6e7e6 100644 --- a/spec/lib/sidebars/menu_spec.rb +++ b/spec/lib/sidebars/menu_spec.rb @@ -23,14 +23,23 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do end describe '#serialize_for_super_sidebar' do + before do + allow(menu).to receive(:title).and_return('Title') + allow(menu).to receive(:active_routes).and_return({ path: 'foo' }) + end + it 'returns a tree-like structure of itself and all menu items' do menu.add_item(Sidebars::MenuItem.new(title: 'Is active', link: 'foo2', active_routes: { controller: 'fooc' })) - menu.add_item(Sidebars::MenuItem.new(title: 'Not active', link: 'foo3', active_routes: { controller: 'barc' })) + menu.add_item(Sidebars::MenuItem.new( + title: 'Not active', + link: 'foo3', + active_routes: { controller: 'barc' }, + has_pill: true, + pill_count: 10 + )) menu.add_item(nil_menu_item) allow(context).to receive(:route_is_active).and_return(->(x) { x[:controller] == 'fooc' }) - allow(menu).to receive(:title).and_return('Title') - allow(menu).to receive(:active_routes).and_return({ path: 'foo' }) expect(menu.serialize_for_super_sidebar).to eq( { @@ -38,22 +47,39 @@ RSpec.describe Sidebars::Menu, feature_category: :navigation do icon: nil, link: "foo2", is_active: true, + pill_count: nil, items: [ { title: "Is active", icon: nil, link: "foo2", - is_active: true + is_active: true, + pill_count: nil }, { title: "Not active", icon: nil, link: "foo3", - is_active: false + is_active: false, + pill_count: 10 } ] }) end + + it 'returns pill data if defined' do + allow(menu).to receive(:has_pill?).and_return(true) + allow(menu).to receive(:pill_count).and_return('foo') + expect(menu.serialize_for_super_sidebar).to eq( + { + title: "Title", + icon: nil, + link: nil, + is_active: false, + pill_count: 'foo', + items: [] + }) + end end describe '#serialize_as_menu_item_args' do diff --git a/spec/lib/sidebars/static_menu_spec.rb b/spec/lib/sidebars/static_menu_spec.rb index a42fed4b170..086eb332a15 100644 --- a/spec/lib/sidebars/static_menu_spec.rb +++ b/spec/lib/sidebars/static_menu_spec.rb @@ -21,13 +21,15 @@ RSpec.describe Sidebars::StaticMenu, feature_category: :navigation do title: "Is active", icon: nil, link: "foo2", - is_active: true + is_active: true, + pill_count: nil }, { title: "Not active", icon: nil, link: "foo3", - is_active: false + is_active: false, + pill_count: nil } ] ) diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb index 5be9ba9ae1e..e8fa73a1b95 100644 --- a/spec/support/helpers/query_recorder.rb +++ b/spec/support/helpers/query_recorder.rb @@ -102,6 +102,10 @@ module ActiveRecord @occurrences ||= @log.group_by(&:to_s).transform_values(&:count) end + def occurrences_starting_with(str) + occurrences.select { |query, _count| query.starts_with?(str) } + end + def ignorable?(values) return true if skip_schema_queries && values[:name]&.include?("SCHEMA") return true if values[:name]&.match(/License Load/) diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 7b7b7da13e5..2e3014b2f51 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -3199,7 +3199,6 @@ - './ee/spec/workers/concerns/elastic/indexing_control_spec.rb' - './ee/spec/workers/concerns/elastic/migration_obsolete_spec.rb' - './ee/spec/workers/concerns/elastic/migration_options_spec.rb' -- './ee/spec/workers/concerns/geo_queue_spec.rb' - './ee/spec/workers/concerns/update_orchestration_policy_configuration_spec.rb' - './ee/spec/workers/create_github_webhook_worker_spec.rb' - './ee/spec/workers/deployments/auto_rollback_worker_spec.rb' @@ -10377,18 +10376,14 @@ - './spec/workers/clusters/integrations/check_prometheus_health_worker_spec.rb' - './spec/workers/concerns/application_worker_spec.rb' - './spec/workers/concerns/cluster_agent_queue_spec.rb' -- './spec/workers/concerns/cluster_queue_spec.rb' - './spec/workers/concerns/cronjob_queue_spec.rb' - './spec/workers/concerns/gitlab/github_import/object_importer_spec.rb' -- './spec/workers/concerns/gitlab/github_import/queue_spec.rb' - './spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb' - './spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb' - './spec/workers/concerns/gitlab/notify_upon_death_spec.rb' - './spec/workers/concerns/limited_capacity/job_tracker_spec.rb' - './spec/workers/concerns/limited_capacity/worker_spec.rb' - './spec/workers/concerns/packages/cleanup_artifact_worker_spec.rb' -- './spec/workers/concerns/pipeline_background_queue_spec.rb' -- './spec/workers/concerns/pipeline_queue_spec.rb' - './spec/workers/concerns/project_import_options_spec.rb' - './spec/workers/concerns/reenqueuer_spec.rb' - './spec/workers/concerns/repository_check_queue_spec.rb' diff --git a/spec/workers/concerns/cluster_agent_queue_spec.rb b/spec/workers/concerns/cluster_agent_queue_spec.rb index b5189cbd8c8..4f67102a0be 100644 --- a/spec/workers/concerns/cluster_agent_queue_spec.rb +++ b/spec/workers/concerns/cluster_agent_queue_spec.rb @@ -14,6 +14,5 @@ RSpec.describe ClusterAgentQueue do end end - it { expect(worker.queue).to eq('cluster_agent:example') } it { expect(worker.get_feature_category).to eq(:kubernetes_management) } end diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb deleted file mode 100644 index c03ca9cea48..00000000000 --- a/spec/workers/concerns/cluster_queue_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ClusterQueue do - let(:worker) do - Class.new do - def self.name - 'DummyWorker' - end - - include ApplicationWorker - include ClusterQueue - end - end - - it 'sets a default pipelines queue automatically' do - expect(worker.sidekiq_options['queue']) - .to eq 'gcp_cluster:dummy' - end -end diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb index 0244535051f..7dd016fc78a 100644 --- a/spec/workers/concerns/cronjob_queue_spec.rb +++ b/spec/workers/concerns/cronjob_queue_spec.rb @@ -40,10 +40,6 @@ RSpec.describe CronjobQueue do stub_const("AnotherWorker", another_worker) end - it 'sets the queue name of a worker' do - expect(worker.sidekiq_options['queue'].to_s).to eq('cronjob:dummy') - end - it 'disables retrying of failed jobs' do expect(worker.sidekiq_options['retry']).to eq(false) end diff --git a/spec/workers/concerns/gitlab/github_import/queue_spec.rb b/spec/workers/concerns/gitlab/github_import/queue_spec.rb deleted file mode 100644 index beca221b593..00000000000 --- a/spec/workers/concerns/gitlab/github_import/queue_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::GithubImport::Queue do - it 'sets the Sidekiq options for the worker' do - worker = Class.new do - def self.name - 'DummyWorker' - end - - include ApplicationWorker - include Gitlab::GithubImport::Queue - end - - expect(worker.sidekiq_options['queue']).to eq('github_importer:dummy') - end -end diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb deleted file mode 100644 index 77c7e7440c5..00000000000 --- a/spec/workers/concerns/pipeline_background_queue_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe PipelineBackgroundQueue do - let(:worker) do - Class.new do - def self.name - 'DummyWorker' - end - - include ApplicationWorker - include PipelineBackgroundQueue - end - end - - it 'sets a default object storage queue automatically' do - expect(worker.sidekiq_options['queue']) - .to eq 'pipeline_background:dummy' - end -end diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb deleted file mode 100644 index 6c1ac2052e4..00000000000 --- a/spec/workers/concerns/pipeline_queue_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe PipelineQueue do - let(:worker) do - Class.new do - def self.name - 'DummyWorker' - end - - include ApplicationWorker - include PipelineQueue - end - end - - it 'sets a default pipelines queue automatically' do - expect(worker.sidekiq_options['queue']) - .to eq 'pipeline_default:dummy' - end -end diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb index ae377c09b37..08ac73aac7b 100644 --- a/spec/workers/concerns/repository_check_queue_spec.rb +++ b/spec/workers/concerns/repository_check_queue_spec.rb @@ -14,10 +14,6 @@ RSpec.describe RepositoryCheckQueue do end end - it 'sets the queue name of a worker' do - expect(worker.sidekiq_options['queue'].to_s).to eq('repository_check:dummy') - end - it 'disables retrying of failed jobs' do expect(worker.sidekiq_options['retry']).to eq(false) end diff --git a/spec/workers/ssh_keys/expired_notification_worker_spec.rb b/spec/workers/ssh_keys/expired_notification_worker_spec.rb index 26d9460d73e..f93d02e86c0 100644 --- a/spec/workers/ssh_keys/expired_notification_worker_spec.rb +++ b/spec/workers/ssh_keys/expired_notification_worker_spec.rb @@ -7,7 +7,6 @@ RSpec.describe SshKeys::ExpiredNotificationWorker, type: :worker do it 'uses a cronjob queue' do expect(worker.sidekiq_options_hash).to include( - 'queue' => 'cronjob:ssh_keys_expired_notification', 'queue_namespace' => :cronjob ) end diff --git a/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb b/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb index e907d035020..ed6701532a5 100644 --- a/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb +++ b/spec/workers/ssh_keys/expiring_soon_notification_worker_spec.rb @@ -7,7 +7,6 @@ RSpec.describe SshKeys::ExpiringSoonNotificationWorker, type: :worker do it 'uses a cronjob queue' do expect(worker.sidekiq_options_hash).to include( - 'queue' => 'cronjob:ssh_keys_expiring_soon_notification', 'queue_namespace' => :cronjob ) end -- cgit v1.2.1