diff options
132 files changed, 1743 insertions, 389 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1b76a3088fb..295216a6fea 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,6 @@ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.1 stages: - sync - prepare - - quick-test - test - post-test - review-prepare diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6648b358557..f6aa0e77005 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -660,129 +660,3 @@ Style/UnneededCondition: # Cop supports --auto-correct. Style/UnneededInterpolation: Enabled: false - -RSpec/ReceiveCounts: - Exclude: - - 'ee/spec/models/broadcast_message_spec.rb' - - 'ee/spec/services/geo/project_housekeeping_service_spec.rb' - - 'ee/spec/services/geo/repository_sync_service_spec.rb' - - 'spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb' - - 'spec/lib/gitlab/ci/trace/section_parser_spec.rb' - - 'spec/lib/gitlab/cleanup/project_uploads_spec.rb' - - 'spec/lib/gitlab/danger/teammate_spec.rb' - - 'spec/lib/gitlab/git_access_spec.rb' - - 'spec/lib/gitlab/gpg_spec.rb' - - 'spec/lib/gitlab/legacy_github_import/importer_spec.rb' - - 'spec/lib/gitlab/sanitizers/exif_spec.rb' - - 'spec/models/concerns/avatarable_spec.rb' - - 'spec/models/concerns/triggerable_hooks_spec.rb' - - 'spec/requests/api/graphql_spec.rb' - - 'spec/services/ci/ensure_stage_service_spec.rb' - - 'spec/services/clusters/cleanup/app_service_spec.rb' - - 'spec/services/notification_service_spec.rb' - - 'spec/services/projects/housekeeping_service_spec.rb' - - 'spec/tasks/gitlab/cleanup_rake_spec.rb' - - 'spec/uploaders/gitlab_uploader_spec.rb' - - 'spec/workers/create_gpg_signature_worker_spec.rb' - -RSpec/ContextMethod: - Exclude: - - 'ee/spec/controllers/ee/projects/autocomplete_sources_controller_spec.rb' - - 'ee/spec/controllers/groups/autocomplete_sources_controller_spec.rb' - - 'ee/spec/finders/geo/file_registry_finder_spec.rb' - - 'ee/spec/helpers/feature_flags_helper_spec.rb' - - 'ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_spec.rb' - - 'ee/spec/lib/ee/gitlab/ci/config/entry/trigger_spec.rb' - - 'ee/spec/lib/elastic/latest/snippet_instance_proxy_spec.rb' - - 'ee/spec/lib/gitlab/contribution_analytics/data_collector_spec.rb' - - 'ee/spec/lib/gitlab/geo/replication/file_transfer_spec.rb' - - 'ee/spec/lib/gitlab/geo/replication/job_artifact_downloader_spec.rb' - - 'ee/spec/lib/gitlab/geo/replication/job_artifact_retriever_spec.rb' - - 'ee/spec/lib/gitlab/geo/replication/job_artifact_transfer_spec.rb' - - 'ee/spec/lib/gitlab/geo/replication/lfs_downloader_spec.rb' - - 'ee/spec/lib/gitlab/geo/replication/lfs_retriever_spec.rb' - - 'ee/spec/lib/gitlab/geo/replication/lfs_transfer_spec.rb' - - 'ee/spec/lib/gitlab/geo_spec.rb' - - 'ee/spec/models/approval_state_spec.rb' - - 'ee/spec/models/approval_wrapped_any_approver_rule_spec.rb' - - 'ee/spec/models/concerns/approval_rule_like_spec.rb' - - 'ee/spec/models/concerns/elastic/issue_spec.rb' - - 'ee/spec/models/concerns/elastic/note_spec.rb' - - 'ee/spec/models/concerns/elastic/project_spec.rb' - - 'ee/spec/models/concerns/epic_tree_sorting_spec.rb' - - 'ee/spec/models/concerns/has_timelogs_report_spec.rb' - - 'ee/spec/models/ee/clusters/applications/prometheus_spec.rb' - - 'ee/spec/models/ee/clusters/platforms/kubernetes_spec.rb' - - 'ee/spec/models/user_spec.rb' - - 'ee/spec/presenters/ci/pipeline_presenter_spec.rb' - - 'ee/spec/requests/api/epics_spec.rb' - - 'ee/spec/services/boards/update_service_spec.rb' - - 'ee/spec/services/ee/boards/issues/list_service_spec.rb' - - 'ee/spec/services/ee/merge_requests/refresh_service_spec.rb' - - 'ee/spec/services/groups/update_service_spec.rb' - - 'ee/spec/services/projects/create_from_template_service_spec.rb' - - 'ee/spec/services/projects/update_service_spec.rb' - - 'ee/spec/services/update_build_minutes_service_spec.rb' - - 'ee/spec/support/shared_examples/features/gold_trial_callout_shared_examples.rb' - - 'ee/spec/support/shared_examples/models/concerns/elastic/limited_indexing_shared_examples.rb' - - 'qa/spec/ee/scenario/test/integration/group_saml_spec.rb' - - 'qa/spec/resource/api_fabricator_spec.rb' - - 'qa/spec/scenario/test/integration/github_spec.rb' - - 'qa/spec/scenario/test/integration/instance_saml_spec.rb' - - 'qa/spec/scenario/test/integration/kubernetes_spec.rb' - - 'qa/spec/scenario/test/integration/ldap_spec.rb' - - 'qa/spec/scenario/test/integration/mattermost_spec.rb' - - 'qa/spec/scenario/test/integration/oauth_spec.rb' - - 'qa/spec/scenario/test/integration/object_storage_spec.rb' - - 'qa/spec/specs/runner_spec.rb' - - 'spec/controllers/application_controller_spec.rb' - - 'spec/controllers/projects/milestones_controller_spec.rb' - - 'spec/features/projects/blobs/blob_show_spec.rb' - - 'spec/finders/concerns/finder_with_cross_project_access_spec.rb' - - 'spec/helpers/diff_helper_spec.rb' - - 'spec/helpers/nav_helper_spec.rb' - - 'spec/helpers/sourcegraph_helper_spec.rb' - - 'spec/lib/container_registry/registry_spec.rb' - - 'spec/lib/container_registry/tag_spec.rb' - - 'spec/lib/gitlab/ci/trace/chunked_io_spec.rb' - - 'spec/lib/gitlab/content_security_policy/config_loader_spec.rb' - - 'spec/lib/gitlab/database/count_spec.rb' - - 'spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb' - - 'spec/lib/gitlab/git_ref_validator_spec.rb' - - 'spec/lib/gitlab/private_commit_email_spec.rb' - - 'spec/lib/gitlab/rugged_instrumentation_spec.rb' - - 'spec/lib/omni_auth/strategies/jwt_spec.rb' - - 'spec/lib/safe_zip/entry_spec.rb' - - 'spec/lib/safe_zip/extract_spec.rb' - - 'spec/models/badge_spec.rb' - - 'spec/models/badges/project_badge_spec.rb' - - 'spec/models/ci/artifact_blob_spec.rb' - - 'spec/models/ci/persistent_ref_spec.rb' - - 'spec/models/ci/runner_spec.rb' - - 'spec/models/concerns/routable_spec.rb' - - 'spec/models/deployment_spec.rb' - - 'spec/models/issue_spec.rb' - - 'spec/models/notification_recipient_spec.rb' - - 'spec/models/project_services/bamboo_service_spec.rb' - - 'spec/models/project_services/buildkite_service_spec.rb' - - 'spec/models/project_services/chat_message/issue_message_spec.rb' - - 'spec/models/project_services/drone_ci_service_spec.rb' - - 'spec/models/project_services/hipchat_service_spec.rb' - - 'spec/models/project_services/jira_service_spec.rb' - - 'spec/models/project_spec.rb' - - 'spec/models/remote_mirror_spec.rb' - - 'spec/models/user_spec.rb' - - 'spec/models/wiki_page_spec.rb' - - 'spec/requests/api/issues/get_group_issues_spec.rb' - - 'spec/requests/api/merge_requests_spec.rb' - - 'spec/serializers/diff_file_entity_spec.rb' - - 'spec/services/branches/delete_merged_service_spec.rb' - - 'spec/services/labels/available_labels_service_spec.rb' - - 'spec/services/projects/batch_open_issues_count_service_spec.rb' - - 'spec/services/projects/destroy_service_spec.rb' - - 'spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb' - - 'spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb' - - 'spec/services/projects/open_issues_count_service_spec.rb' - - 'spec/services/quick_actions/interpret_service_spec.rb' - - 'spec/support/shared_examples/services/boards/issues_list_service_shared_examples.rb' - - 'spec/uploaders/namespace_file_uploader_spec.rb' diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index 7db9898396b..f8bf778b9e7 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -307,7 +307,7 @@ export default { <a v-if="titleLink" :href="titleLink" - target="blank" + target="_blank" rel="noopener noreferrer" class="js-cluster-application-title" >{{ title }}</a diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index 48114f9919c..731c53a7339 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -285,7 +285,7 @@ export default { ref="viewButton" v-gl-tooltip.hover :href="diffFile.view_path" - target="blank" + target="_blank" class="view-file" data-track-event="click_toggle_view_sha_button" data-track-label="diff_toggle_view_sha_button" diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 202363a1dda..267b49e9d98 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -166,7 +166,7 @@ export const setUrlFragment = (url, fragment) => { export function visitUrl(url, external = false) { if (external) { - // Simulate `target="blank" rel="noopener noreferrer"` + // Simulate `target="_blank" rel="noopener noreferrer"` // See https://mathiasbynens.github.io/rel-noopener/ const otherWindow = window.open(); otherWindow.opener = null; diff --git a/app/controllers/groups/registry/repositories_controller.rb b/app/controllers/groups/registry/repositories_controller.rb index a4ef33ecc6a..84c25cfb180 100644 --- a/app/controllers/groups/registry/repositories_controller.rb +++ b/app/controllers/groups/registry/repositories_controller.rb @@ -14,13 +14,24 @@ module Groups track_event(:list_repositories) - render json: ContainerRepositoriesSerializer + serializer = ContainerRepositoriesSerializer .new(current_user: current_user) - .represent_read_only(@images) + + if Feature.enabled?(:vue_container_registry_explorer) + render json: serializer.with_pagination(request, response) + .represent_read_only(@images) + else + render json: serializer.represent_read_only(@images) + end end end end + # The show action renders index to allow frontend routing to work on page refresh + def show + render :index + end + private def feature_flag_group_container_registry_browser! diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb index 5e933b3b51f..b31a74540e2 100644 --- a/app/controllers/projects/registry/repositories_controller.rb +++ b/app/controllers/projects/registry/repositories_controller.rb @@ -14,13 +14,23 @@ module Projects track_event(:list_repositories) - render json: ContainerRepositoriesSerializer + serializer = ContainerRepositoriesSerializer .new(project: project, current_user: current_user) - .represent(@images) + + if Feature.enabled?(:vue_container_registry_explorer) + render json: serializer.with_pagination(request, response).represent(@images) + else + render json: serializer.represent(@images) + end end end end + # The show action renders index to allow frontend routing to work on page refresh + def show + render :index + end + def destroy DeleteContainerRepositoryWorker.perform_async(current_user.id, image.id) track_event(:delete_repository) diff --git a/app/finders/projects/prometheus/alerts_finder.rb b/app/finders/projects/prometheus/alerts_finder.rb new file mode 100644 index 00000000000..3e3b72647c5 --- /dev/null +++ b/app/finders/projects/prometheus/alerts_finder.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Projects + module Prometheus + # Find Prometheus alerts by +project+, +environment+, +id+, + # or any combo thereof. + # + # Optionally filter by +metric+. + # + # Arguments: + # params: + # project: Project | integer + # environment: Environment | integer + # metric: PrometheusMetric | integer + class AlertsFinder + def initialize(params = {}) + unless params[:project] || params[:environment] || params[:id] + raise ArgumentError, + 'Please provide one or more of the following params: :project, :environment, :id' + end + + @params = params + end + + # Find all matching alerts + # + # @return [ActiveRecord::Relation<PrometheusAlert>] + def execute + relation = by_project(PrometheusAlert) + relation = by_environment(relation) + relation = by_metric(relation) + relation = by_id(relation) + relation = ordered(relation) + + relation + end + + private + + attr_reader :params + + def by_project(relation) + return relation unless params[:project] + + relation.for_project(params[:project]) + end + + def by_environment(relation) + return relation unless params[:environment] + + relation.for_environment(params[:environment]) + end + + def by_metric(relation) + return relation unless params[:metric] + + relation.for_metric(params[:metric]) + end + + def by_id(relation) + return relation unless params[:id] + + relation.id_in(params[:id]) + end + + def ordered(relation) + relation.order_by('id_asc') + end + end + end +end diff --git a/app/finders/protected_branches_finder.rb b/app/finders/protected_branches_finder.rb new file mode 100644 index 00000000000..68e8d2a9f54 --- /dev/null +++ b/app/finders/protected_branches_finder.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# ProtectedBranchesFinder +# +# Used to filter protected branches by set of params +# +# Arguments: +# project - which project to scope to +# params: +# search: string +class ProtectedBranchesFinder + LIMIT = 100 + + attr_accessor :project, :params + + def initialize(project, params = {}) + @project = project + @params = params + end + + def execute + protected_branches = project.limited_protected_branches(LIMIT) + protected_branches = by_name(protected_branches) + + protected_branches + end + + private + + def by_name(protected_branches) + return protected_branches unless params[:search].present? + + protected_branches.by_name(params[:search]) + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 78c3114ce9c..816d964519d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2304,6 +2304,10 @@ class Project < ApplicationRecord ci_config_path.blank? || ci_config_path == Gitlab::FileDetector::PATTERNS[:gitlab_ci] end + def limited_protected_branches(limit) + protected_branches.limit(limit) + end + private def closest_namespace_setting(name) diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 735e2bdea81..94c3b83564f 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -2,6 +2,7 @@ class ProtectedBranch < ApplicationRecord include ProtectedRef + include Gitlab::SQL::Pattern scope :requiring_code_owner_approval, -> { where(code_owner_approval_required: true) } @@ -45,6 +46,12 @@ class ProtectedBranch < ApplicationRecord # NOOP # end + + def self.by_name(query) + return none if query.blank? + + where(fuzzy_arel_match(:name, query.downcase)) + end end ProtectedBranch.prepend_if_ee('EE::ProtectedBranch') diff --git a/app/presenters/projects/prometheus/alert_presenter.rb b/app/presenters/projects/prometheus/alert_presenter.rb new file mode 100644 index 00000000000..8988c567c5c --- /dev/null +++ b/app/presenters/projects/prometheus/alert_presenter.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module Projects + module Prometheus + class AlertPresenter < Gitlab::View::Presenter::Delegated + RESERVED_ANNOTATIONS = %w(gitlab_incident_markdown title).freeze + GENERIC_ALERT_SUMMARY_ANNOTATIONS = %w(monitoring_tool service hosts).freeze + MARKDOWN_LINE_BREAK = " \n".freeze + + def full_title + [environment_name, alert_title].compact.join(': ') + end + + def project_full_path + project.full_path + end + + def metric_query + gitlab_alert&.full_query + end + + def environment_name + environment&.name + end + + def performance_dashboard_link + if environment + metrics_project_environment_url(project, environment) + else + metrics_project_environments_url(project) + end + end + + def starts_at + super&.rfc3339 + end + + def issue_summary_markdown + <<~MARKDOWN.chomp + #### Summary + + #{metadata_list} + #{alert_details} + MARKDOWN + end + + private + + def alert_title + query_title || title + end + + def query_title + return unless gitlab_alert + + "#{gitlab_alert.title} #{gitlab_alert.computed_operator} #{gitlab_alert.threshold} for 5 minutes" + end + + def metadata_list + metadata = [] + + metadata << list_item('Start time', starts_at) if starts_at + metadata << list_item('full_query', backtick(full_query)) if full_query + metadata << list_item(service.label.humanize, service.value) if service + metadata << list_item(monitoring_tool.label.humanize, monitoring_tool.value) if monitoring_tool + metadata << list_item(hosts.label.humanize, host_links) if hosts + + metadata.join(MARKDOWN_LINE_BREAK) + end + + def alert_details + if annotation_list.present? + <<~MARKDOWN.chomp + + #### Alert Details + + #{annotation_list} + MARKDOWN + end + end + + def annotation_list + strong_memoize(:annotation_list) do + annotations + .reject { |annotation| annotation.label.in?(RESERVED_ANNOTATIONS | GENERIC_ALERT_SUMMARY_ANNOTATIONS) } + .map { |annotation| list_item(annotation.label, annotation.value) } + .join(MARKDOWN_LINE_BREAK) + end + end + + def list_item(key, value) + "**#{key}:** #{value}".strip + end + + def backtick(value) + "`#{value}`" + end + + GENERIC_ALERT_SUMMARY_ANNOTATIONS.each do |annotation_name| + define_method(annotation_name) do + annotations.find { |a| a.label == annotation_name } + end + end + + def host_links + Array(hosts.value).join(' ') + end + end + end +end diff --git a/app/serializers/container_repositories_serializer.rb b/app/serializers/container_repositories_serializer.rb index bc35a67ff24..0e9bdee187b 100644 --- a/app/serializers/container_repositories_serializer.rb +++ b/app/serializers/container_repositories_serializer.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class ContainerRepositoriesSerializer < BaseSerializer + include WithPagination entity ContainerRepositoryEntity def represent_read_only(resource) diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb index a49983a84fc..ea5b2f401b3 100644 --- a/app/services/git/base_hooks_service.rb +++ b/app/services/git/base_hooks_service.rb @@ -81,15 +81,17 @@ module Git end def pipeline_params - { - before: oldrev, - after: newrev, - ref: ref, - variables_attributes: generate_vars_from_push_options || [], - push_options: params[:push_options] || {}, - checkout_sha: Gitlab::DataBuilder::Push.checkout_sha( - project.repository, newrev, ref) - } + strong_memoize(:pipeline_params) do + { + before: oldrev, + after: newrev, + ref: ref, + variables_attributes: generate_vars_from_push_options || [], + push_options: params[:push_options] || {}, + checkout_sha: Gitlab::DataBuilder::Push.checkout_sha( + project.repository, newrev, ref) + } + end end def ci_variables_from_push_options @@ -156,12 +158,16 @@ module Git project_path: project.full_path, message: "Error creating pipeline", errors: exception.to_s, - pipeline_params: pipeline_params + pipeline_params: sanitized_pipeline_params } logger.warn(data) end + def sanitized_pipeline_params + pipeline_params.except(:push_options) + end + def logger if Gitlab::Runtime.sidekiq? Sidekiq.logger diff --git a/changelogs/unreleased/23315-routing-changes-and-pagination-for-container-registry.yml b/changelogs/unreleased/23315-routing-changes-and-pagination-for-container-registry.yml new file mode 100644 index 00000000000..62a60cc5dbd --- /dev/null +++ b/changelogs/unreleased/23315-routing-changes-and-pagination-for-container-registry.yml @@ -0,0 +1,6 @@ +--- +title: Add show routes for group and project repositories_controllers and add pagination + to the index responses +merge_request: 23151 +author: +type: added diff --git a/changelogs/unreleased/37722-configurable-ds-advisory-db.yml b/changelogs/unreleased/37722-configurable-ds-advisory-db.yml new file mode 100644 index 00000000000..c03927b0902 --- /dev/null +++ b/changelogs/unreleased/37722-configurable-ds-advisory-db.yml @@ -0,0 +1,5 @@ +--- +title: 'Add CI variables to configure bundler-audit advisory database (Dependency Scanning)' +merge_request: 23717 +author: +type: added diff --git a/changelogs/unreleased/460-protected-branches-api-search.yml b/changelogs/unreleased/460-protected-branches-api-search.yml new file mode 100644 index 00000000000..ed9d89a6a64 --- /dev/null +++ b/changelogs/unreleased/460-protected-branches-api-search.yml @@ -0,0 +1,5 @@ +--- +title: Add search support for protected branches API +merge_request: 24137 +author: +type: added diff --git a/changelogs/unreleased/fix-do-not-update-private-profile-in-api.yml b/changelogs/unreleased/fix-do-not-update-private-profile-in-api.yml new file mode 100644 index 00000000000..f2fa26176b6 --- /dev/null +++ b/changelogs/unreleased/fix-do-not-update-private-profile-in-api.yml @@ -0,0 +1,6 @@ +--- +title: Fix inconditionally setting user profile to public when updating via + API and private_profile parameter is not present in the request +merge_request: 24456 +author: Diego Louzán +type: fixed diff --git a/changelogs/unreleased/sh-sanitize-pipeline-params.yml b/changelogs/unreleased/sh-sanitize-pipeline-params.yml new file mode 100644 index 00000000000..93e99b18ffd --- /dev/null +++ b/changelogs/unreleased/sh-sanitize-pipeline-params.yml @@ -0,0 +1,5 @@ +--- +title: Redact push options from error logs +merge_request: 24540 +author: +type: fixed diff --git a/config/routes/group.rb b/config/routes/group.rb index 8f572a685d8..68e239faf6d 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -76,7 +76,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do end end - resources :container_registries, only: [:index], controller: 'registry/repositories' + resources :container_registries, only: [:index, :show], controller: 'registry/repositories' end scope(path: '*id', diff --git a/config/routes/project.rb b/config/routes/project.rb index 3f3d7452a84..82f998f185c 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -371,7 +371,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end - resources :container_registry, only: [:index, :destroy], + resources :container_registry, only: [:index, :destroy, :show], controller: 'registry/repositories' namespace :registry do diff --git a/db/migrate/20200128141125_add_index_web_hooks_on_group_id.rb b/db/migrate/20200128141125_add_index_web_hooks_on_group_id.rb new file mode 100644 index 00000000000..8c7f6426587 --- /dev/null +++ b/db/migrate/20200128141125_add_index_web_hooks_on_group_id.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexWebHooksOnGroupId < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :web_hooks, :group_id, where: "type = 'GroupHook'" + end + + def down + remove_concurrent_index :web_hooks, :group_id, where: "type = 'GroupHook'" + end +end diff --git a/db/schema.rb b/db/schema.rb index 79c00c10a2d..f55f3df31cf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -4477,6 +4477,7 @@ ActiveRecord::Schema.define(version: 2020_02_05_143231) do t.string "encrypted_token_iv" t.string "encrypted_url" t.string "encrypted_url_iv" + t.index ["group_id"], name: "index_web_hooks_on_group_id", where: "((type)::text = 'GroupHook'::text)" t.index ["project_id"], name: "index_web_hooks_on_project_id" t.index ["type"], name: "index_web_hooks_on_type" end diff --git a/doc/api/users.md b/doc/api/users.md index d77df9bc6c7..e147637ca59 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -385,7 +385,7 @@ Parameters: - `skip_confirmation` (optional) - Skip confirmation - true or false (default) - `external` (optional) - Flags the user as external - true or false (default) - `avatar` (optional) - Image file for user's avatar -- `private_profile` (optional) - User's profile is private - true or false (default) +- `private_profile` (optional) - User's profile is private - true, false (default), or null (will be converted to false) - `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)** - `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)** @@ -423,7 +423,7 @@ Parameters: - `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user - `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user - `avatar` (optional) - Image file for user's avatar -- `private_profile` (optional) - User's profile is private - true or false (default) +- `private_profile` (optional) - User's profile is private - true, false (default), or null (will be converted to false) - `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)** - `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)** - `note` (optional) - Admin notes for this user **(STARTER)** diff --git a/doc/development/README.md b/doc/development/README.md index f94da66085b..ba1714b2746 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -83,6 +83,7 @@ Complementary reads: - [Cycle Analytics development guide](cycle_analytics.md) - [Issue types vs first-class types](issue_types.md) - [Application limits](application_limits.md) +- [Redis guidelines](redis.md) ## Performance guides diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index 42b4e7385dc..32b4c97cb62 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -19,9 +19,6 @@ The current stages are: <https://gitlab.com/gitlab-org/gitlab-foss>. - `prepare`: This stage includes jobs that prepare artifacts that are needed by jobs in subsequent stages. -- `quick-test`: This stage includes test jobs that should run first and fail the - pipeline early (currently used to run Geo tests when the branch name starts - with `geo-`, `geo/`, or ends with `-geo`). - `test`: This stage includes most of the tests, DB/migration jobs, and static analysis jobs. - `post-test`: This stage includes jobs that build reports or gather data from the `test` stage's jobs (e.g. coverage, Knapsack metadata etc.). @@ -138,54 +135,109 @@ duplicating the `if` conditions and `changes` patterns lists since they cannot b **If you update an `if` condition or `changes` patterns list, make sure to mass-update those across all the CI config files (i.e. `.gitlab/ci/*.yml`).** -### Canonical commits only +### Canonical/security namespace merge requests only -This condition limits jobs creation to commits under the `gitlab-org/` top-level group -on GitLab.com only. This is similar to the `.only:variables-canonical-dot-com` CI definition: +This condition limits jobs creation to merge requests under the `gitlab-org/` top-level group +on GitLab.com only (i.e. this won't run for `master`, stable or auto-deploy branches). +This is similar to the `.only:variables-canonical-dot-com` + `only:refs: [merge_requests]` +CI definitions. -```yaml -.if-canonical-gitlab: &if-canonical-gitlab - if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/)/' -``` +The definition for `if-canonical-dot-com-gitlab-org-groups-merge-request` can be +seen in <https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/docs.gitlab-ci.yml>. -### Canonical merge requests only +### Canonical/security namespace tags only -Same as the "Canonical commits only" condition above but further limits jobs creation -to merge requests only (i.e. this won't run for `master`, stable or auto-deploy branches). -This is similar to the `.only:variables-canonical-dot-com` + `.except:refs-master-tags-stable-deploy` -CI definitions: +This condition limits jobs creation to tags under the `gitlab-org/` top-level group +on GitLab.com only. +This is similar to the `.only:variables-canonical-dot-com` + `only:refs: [tags]` CI definition: -```yaml -.if-canonical-gitlab-merge-request: &if-canonical-gitlab-merge-request - if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/)/ && $CI_MERGE_REQUEST_IID' -``` +The definition for `if-canonical-dot-com-gitlab-org-groups-tag` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/cng.gitlab-ci.yml>. + +### Canonical namespace `master` only + +This condition limits jobs creation to `master` pipelines for the `gitlab-org` top-level group +on GitLab.com only. +This is similar to the `.only:variables-canonical-dot-com` + `only:refs: [master]` CI definition: + +The definition for `if-canonical-dot-com-gitlab-org-group-master-refs` can be +seen in <https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/pages.gitlab-ci.yml>. + +### Canonical namespace schedules only + +This condition limits jobs creation to scheduled pipelines for the `gitlab-org` top-level group +on GitLab.com only. +This is similar to the `.only:variables-canonical-dot-com` + `only:refs: [schedules]` CI definition: + +The definition for `if-canonical-dot-com-gitlab-org-group-schedule` can be seen +in <https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/qa.gitlab-ci.yml>. + +### Not canonical/security namespace + +This condition matches if the project isn't in the canonical/security namespace. +Useful to **not** create a job if the project is a fork, or in other words, when +a job should only run in the canonical projects. + +The definition for `if-not-canonical-namespace` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/frontend.gitlab-ci.yml>. + +### Not EE + +This condition matches if the project isn't EE. Useful to **not** create a job if +the project is GitLab, or in other words, when a job should only run in the GitLab +FOSS project. + +The definition for `if-not-ee` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/frontend.gitlab-ci.yml>. + +### Default refs only + +This condition is the equivalent of `.default-only`. + +The definition for `if-default-refs` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/frontend.gitlab-ci.yml>. + +### `master` refs only + +This condition is the equivalent of `only:refs: [master]`. + +The definition for `if-master-refs` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/frontend.gitlab-ci.yml>. ### Code changes patterns Similar patterns as for `.only:changes-code`: -```yaml -.code-patterns: &code-patterns - - ... -``` +The definition for `code-patterns` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/qa.gitlab-ci.yml>. ### QA changes patterns Similar patterns as for `.only:changes-qa`: -```yaml -.qa-patterns: &qa-patterns - - ... -``` +The definition for `qa-patterns` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/qa.gitlab-ci.yml>. + +### Docs changes patterns + +Similar patterns as for `.only:changes-docs`: + +The definition for `docs-patterns` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/docs.gitlab-ci.yml>. ### Code and QA changes patterns Similar patterns as for `.only:changes-code-qa`: -```yaml -.code-qa-patterns: &code-qa-patterns - - ... -``` +The definition for `code-qa-patterns` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/review.gitlab-ci.yml>. + +### Code, backstage and QA changes patterns + +Similar patterns as for `.only:changes-code-backstage-qa`: + +The definition for `code-backstage-qa-patterns` can be seen in +<https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/frontend.gitlab-ci.yml>. ## Directed acyclic graph @@ -195,21 +247,21 @@ execute jobs out of order for the following jobs: ```mermaid graph RL; A[setup-test-env]; - B["gitlab:assets:compile pull-push-cache<br/>(master only)"]; - C[gitlab:assets:compile pull-cache]; + B["gitlab:assets:compile pull-push-cache<br/>(canonical master only)"]; + C["gitlab:assets:compile pull-cache<br/>(canonical default refs only)"]; D["cache gems<br/>(master and tags only)"]; E[review-build-cng]; F[build-qa-image]; G[review-deploy]; G2["schedule:review-deploy<br/>(master only)"]; - H[karma]; - I[jest]; + I["karma, jest, webpack-dev-server, static-analysis"]; + I2["karma-foss, jest-foss<br/>(EE default refs only)"]; J["compile-assets pull-push-cache<br/>(master only)"]; + J2["compile-assets pull-push-cache foss<br/>(EE master only)"]; K[compile-assets pull-cache]; - L[webpack-dev-server]; + K2["compile-assets pull-cache foss<br/>(EE default refs only)"]; M[coverage]; - N[pages]; - O[static-analysis]; + N["pages (master only)"]; Q[package-and-qa]; S["RSpec<br/>(e.g. rspec unit pg9)"] T[retrieve-tests-metadata]; @@ -220,58 +272,55 @@ subgraph "`prepare` stage" C F K + K2 J + J2 T end subgraph "`test` stage" - D --> |needs| A; - H -.-> |needs and depends on| A; - H -.-> |needs and depends on| K; + D -.-> |needs| A; I -.-> |needs and depends on| A; I -.-> |needs and depends on| K; + I2 -.-> |needs and depends on| A; + I2 -.-> |needs and depends on| K; L -.-> |needs and depends on| A; - L -.-> |needs and depends on| K; - O -.-> |needs and depends on| A; - O -.-> |needs and depends on| K; S -.-> |needs and depends on| A; S -.-> |needs and depends on| K; S -.-> |needs and depends on| T; - downtime_check --> |needs and depends on| A; - db:* --> |needs| A; - gitlab:setup --> |needs| A; - downtime_check --> |needs and depends on| A; - graphql-docs-verify --> |needs| A; + L["db:*, gitlab:setup, graphql-docs-verify, downtime_check"] -.-> |needs| A; + end + +subgraph "`post-test` stage" + M --> |happens after| S end subgraph "`review-prepare` stage" - E --> |needs| C; - X["schedule:review-build-cng<br/>(master schedule only)"] --> |needs| C; + E -.-> |needs| C; + E2["schedule:review-build-cng<br/>(master schedule only)"] -.-> |needs| C; end subgraph "`review` stage" - G - G2 + G --> |happens after| E + G2 --> |happens after| E2 end subgraph "`qa` stage" - Q --> |needs| C; - Q --> |needs| F; - review-qa-smoke -.-> |needs and depends on| G; - review-qa-all -.-> |needs and depends on| G; - review-performance -.-> |needs and depends on| G; - X2["schedule:review-performance<br/>(master only)"] -.-> |needs and depends on| G2; - dast -.-> |needs and depends on| G; + Q -.-> |needs| C; + Q -.-> |needs| F; + QA1["review-qa-smoke, review-qa-all, review-performance, dast"] -.-> |needs and depends on| G; + QA2["schedule:review-performance<br/>(master only)"] -.-> |needs and depends on| G2; end -subgraph "`post-test` stage" - M - end +subgraph "`post-qa` stage" + PQA1["parallel-spec-reports"] -.-> |depends on `review-qa-all`| QA1; + end subgraph "`pages` stage" N -.-> |depends on| C; - N -.-> |depends on| H; + N -.-> |depends on karma| I; N -.-> |depends on| M; + N --> |happens after| PQA1 end ``` diff --git a/doc/development/redis.md b/doc/development/redis.md new file mode 100644 index 00000000000..a4a87155f5a --- /dev/null +++ b/doc/development/redis.md @@ -0,0 +1,43 @@ +# Redis guidelines + +GitLab uses [Redis](https://redis.io) for three distinct purposes: + +- Caching via `Rails.cache`. +- As a job processing queue with [Sidekiq](sidekiq_style_guide.md). +- To manage the shared application state. + +Every application process is configured to use the same Redis servers, so they +can be used for inter-process communication in cases where [PostgreSQL](sql.md) +is less appropriate, for example, transient state or data that is written much +more often than it is read. + +If [Geo](geo.md) is enabled, each Geo node gets its own, independent Redis +database. + +## Key naming + +Redis is a flat namespace with no hierarchy, which means we must pay attention +to key names to avoid collisions. Typically we use colon-separated elements to +provide a semblence of structure at application level. An example might be +`projects:1:somekey`. + +Although we split our Redis usage into three separate purposes, and those may +map to separate Redis servers in a [Highly Available](../administration/high_availability/redis.md) +configuration, the default Omnibus and GDK setups share a single Redis server. +This means that keys should **always** be globally unique across the three +purposes. + +It is usually better to use immutable identifiers - project ID rather than +full path, for instance - in Redis key names. If full path is used, the key will +stop being consulted if the project is renamed. If the contents of the key are +invalidated by a name change, it is better to include a hook that will expire +the entry, instead of relying on the key changing. + +We don't use [Redis Cluster](https://redis.io/topics/cluster-tutorial) at the +moment, but may wish to in the future: [#118820](https://gitlab.com/gitlab-org/gitlab/issues/118820). + +This imposes an additional constraint on naming: where GitLab is performing +operations that require several keys to be held on the same Redis server - for +instance, diffing two sets held in Redis - the keys should ensure that by +enclosing the changeable parts in curly braces, such as, `project:{1}:set_a` and +`project:{1}:set_b`. diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb index c7665c20234..1fd86d1e720 100644 --- a/lib/api/protected_branches.rb +++ b/lib/api/protected_branches.rb @@ -19,10 +19,15 @@ module API end params do use :pagination + optional :search, type: String, desc: 'Search for a protected branch by name' end # rubocop: disable CodeReuse/ActiveRecord get ':id/protected_branches' do - protected_branches = user_project.protected_branches.preload(:push_access_levels, :merge_access_levels) + protected_branches = + ProtectedBranchesFinder + .new(user_project, params) + .execute + .preload(:push_access_levels, :merge_access_levels) present paginate(protected_branches), with: Entities::ProtectedBranch, project: user_project end diff --git a/lib/api/users.rb b/lib/api/users.rb index eba7c50435c..da64ff7e306 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -52,7 +52,7 @@ module API optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 optional :avatar, type: File, desc: 'Avatar image for user' # rubocop:disable Scalability/FileUploads - optional :private_profile, type: Boolean, default: false, desc: 'Flag indicating the user has a private profile' + optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile' all_or_none_of :extern_uid, :provider use :optional_params_ee diff --git a/lib/gitlab/alerting/alert.rb b/lib/gitlab/alerting/alert.rb new file mode 100644 index 00000000000..531307b93d4 --- /dev/null +++ b/lib/gitlab/alerting/alert.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +module Gitlab + module Alerting + class Alert + include ActiveModel::Model + include Gitlab::Utils::StrongMemoize + include Presentable + + attr_accessor :project, :payload + + def gitlab_alert + strong_memoize(:gitlab_alert) do + parse_gitlab_alert_from_payload + end + end + + def metric_id + strong_memoize(:metric_id) do + payload&.dig('labels', 'gitlab_alert_id') + end + end + + def title + strong_memoize(:title) do + gitlab_alert&.title || parse_title_from_payload + end + end + + def description + strong_memoize(:description) do + parse_description_from_payload + end + end + + def environment + strong_memoize(:environment) do + gitlab_alert&.environment || parse_environment_from_payload + end + end + + def annotations + strong_memoize(:annotations) do + parse_annotations_from_payload || [] + end + end + + def starts_at + strong_memoize(:starts_at) do + parse_datetime_from_payload('startsAt') + end + end + + def starts_at_raw + strong_memoize(:starts_at_raw) do + payload&.dig('startsAt') + end + end + + def ends_at + strong_memoize(:ends_at) do + parse_datetime_from_payload('endsAt') + end + end + + def full_query + strong_memoize(:full_query) do + gitlab_alert&.full_query || parse_expr_from_payload + end + end + + def alert_markdown + strong_memoize(:alert_markdown) do + parse_alert_markdown_from_payload + end + end + + def status + strong_memoize(:status) do + payload&.dig('status') + end + end + + def firing? + status == 'firing' + end + + def resolved? + status == 'resolved' + end + + def gitlab_managed? + metric_id.present? + end + + def valid? + payload.respond_to?(:dig) && project && title && starts_at + end + + def present + super(presenter_class: Projects::Prometheus::AlertPresenter) + end + + private + + def parse_environment_from_payload + environment_name = payload&.dig('labels', 'gitlab_environment_name') + + return unless environment_name + + EnvironmentsFinder.new(project, nil, { name: environment_name }) + .find + &.first + end + + def parse_gitlab_alert_from_payload + return unless metric_id + + Projects::Prometheus::AlertsFinder + .new(project: project, metric: metric_id) + .execute + .first + end + + def parse_title_from_payload + payload&.dig('annotations', 'title') || + payload&.dig('annotations', 'summary') || + payload&.dig('labels', 'alertname') + end + + def parse_description_from_payload + payload&.dig('annotations', 'description') + end + + def parse_annotations_from_payload + payload&.dig('annotations')&.map do |label, value| + Alerting::AlertAnnotation.new(label: label, value: value) + end + end + + def parse_datetime_from_payload(field) + value = payload&.dig(field) + return unless value + + Time.rfc3339(value) + rescue ArgumentError + end + + # Parses `g0.expr` from `generatorURL`. + # + # Example: http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1 + def parse_expr_from_payload + url = payload&.dig('generatorURL') + return unless url + + uri = URI(url) + + Rack::Utils.parse_query(uri.query).fetch('g0.expr') + rescue URI::InvalidURIError, KeyError + end + + def parse_alert_markdown_from_payload + payload&.dig('annotations', 'gitlab_incident_markdown') + end + end + end +end diff --git a/lib/gitlab/alerting/alert_annotation.rb b/lib/gitlab/alerting/alert_annotation.rb new file mode 100644 index 00000000000..a4b3a97b08c --- /dev/null +++ b/lib/gitlab/alerting/alert_annotation.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Gitlab + module Alerting + class AlertAnnotation + include ActiveModel::Model + + attr_accessor :label, :value + end + end +end diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml index 225fb7e5606..5ff6413898f 100644 --- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml @@ -57,6 +57,8 @@ dependency_scanning: PIP_REQUIREMENTS_FILE \ MAVEN_CLI_OPTS \ BUNDLER_AUDIT_UPDATE_DISABLED \ + BUNDLER_AUDIT_ADVISORY_DB_URL \ + BUNDLER_AUDIT_ADVISORY_DB_REF_NAME \ ) \ --volume "$PWD:/code" \ --volume /var/run/docker.sock:/var/run/docker.sock \ diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb index 6c7f80705e2..57d2c02a27b 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb @@ -3,7 +3,7 @@ module QA context 'Plan', :reliable do describe 'check xss occurence in @mentions in issues', :requires_admin do - it 'user mentions a user in comment' do + it 'mentions a user in a comment' do QA::Runtime::Env.personal_access_token = QA::Runtime::Env.admin_personal_access_token unless QA::Runtime::Env.personal_access_token diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb index fd00e010b85..d075dc742e2 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb @@ -18,7 +18,7 @@ module QA push_commit('Initial commit') end - it 'user closes an issue by pushing commit' do + it 'closes an issue by pushing a commit' do push_commit("Closes ##{issue_id}", false) issue.visit! diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb index a2b7904a84d..33d2c7026b3 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb @@ -17,7 +17,7 @@ module QA end end - it 'user collapses and expands reply for comments in an issue' do + it 'collapses and expands reply for comments in an issue' do Page::Project::Issue::Show.perform do |show| one_reply = "1 reply" diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb index c4a8d2c4e20..4667eb6c587 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb @@ -9,7 +9,7 @@ module QA Resource::Issue.fabricate_via_api!.visit! end - it 'user comments on an issue and edits the comment' do + it 'comments on an issue and edits the comment' do Page::Project::Issue::Show.perform do |show| first_version_of_comment = 'First version of the comment' second_version_of_comment = 'Second version of the comment' diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index 0ea81483a66..1c741a2e8bf 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -7,7 +7,7 @@ module QA Flow::Login.sign_in end - it 'user creates an issue' do + it 'creates an issue' do issue = Resource::Issue.fabricate_via_browser_ui! Page::Project::Menu.perform(&:click_issues) @@ -27,7 +27,7 @@ module QA Resource::Issue.fabricate_via_api!.visit! end - it 'user comments on an issue with an attachment' do + it 'comments on an issue with an attachment' do Page::Project::Issue::Show.perform do |show| show.comment('See attached banana for scale', attachment: file_to_attach) diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb index f1013d38a1f..b7687f785a8 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb @@ -9,7 +9,7 @@ module QA Resource::Issue.fabricate_via_api!.visit! end - it 'user filters comments and activities in an issue' do + it 'filters comments and activities in an issue' do Page::Project::Issue::Show.perform do |show| my_own_comment = "My own comment" made_the_issue_confidential = "made the issue confidential" diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb index 6c851fd8141..9b46a066c8e 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb @@ -13,7 +13,7 @@ module QA end.project.visit! end - it 'user sees issue suggestions when creating a new issue' do + it 'shows issue suggestions when creating a new issue' do Page::Project::Show.perform(&:go_to_new_issue) Page::Project::Issue::New.perform do |new_page| new_page.add_title("issue") diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb index a76cb57b4a7..3e575517ecb 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb @@ -20,7 +20,7 @@ module QA end.visit! end - it 'user mentions another user in an issue' do + it 'mentions another user in an issue' do Page::Project::Issue::Show.perform do |show| at_username = "@#{@user.username}" diff --git a/qa/spec/resource/api_fabricator_spec.rb b/qa/spec/resource/api_fabricator_spec.rb index a5ed4422f6e..eb2bdd1be64 100644 --- a/qa/spec/resource/api_fabricator_spec.rb +++ b/qa/spec/resource/api_fabricator_spec.rb @@ -120,7 +120,7 @@ describe QA::Resource::ApiFabricator do end end - context '#transform_api_resource' do + describe '#transform_api_resource' do let(:resource) do Class.new do def self.name diff --git a/qa/spec/scenario/test/integration/github_spec.rb b/qa/spec/scenario/test/integration/github_spec.rb index 6112ba7c694..b2d577bd552 100644 --- a/qa/spec/scenario/test/integration/github_spec.rb +++ b/qa/spec/scenario/test/integration/github_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::Github do - context '#perform' do + describe '#perform' do let(:env) { spy('Runtime::Env') } before do diff --git a/qa/spec/scenario/test/integration/instance_saml_spec.rb b/qa/spec/scenario/test/integration/instance_saml_spec.rb index cb8a6a630cc..15f15b2e643 100644 --- a/qa/spec/scenario/test/integration/instance_saml_spec.rb +++ b/qa/spec/scenario/test/integration/instance_saml_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::InstanceSAML do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:instance_saml] } end diff --git a/qa/spec/scenario/test/integration/kubernetes_spec.rb b/qa/spec/scenario/test/integration/kubernetes_spec.rb index cb43994b229..51ee7b9acff 100644 --- a/qa/spec/scenario/test/integration/kubernetes_spec.rb +++ b/qa/spec/scenario/test/integration/kubernetes_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::Kubernetes do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:kubernetes] } end diff --git a/qa/spec/scenario/test/integration/ldap_spec.rb b/qa/spec/scenario/test/integration/ldap_spec.rb index 86747cd8eb7..c493cde6c7a 100644 --- a/qa/spec/scenario/test/integration/ldap_spec.rb +++ b/qa/spec/scenario/test/integration/ldap_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::LDAPNoTLS do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:ldap_no_tls] } end @@ -9,7 +9,7 @@ describe QA::Scenario::Test::Integration::LDAPNoTLS do end describe QA::Scenario::Test::Integration::LDAPNoServer do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:ldap_no_server] } end @@ -17,7 +17,7 @@ describe QA::Scenario::Test::Integration::LDAPNoServer do end describe QA::Scenario::Test::Integration::LDAPTLS do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:ldap_tls] } end diff --git a/qa/spec/scenario/test/integration/mattermost_spec.rb b/qa/spec/scenario/test/integration/mattermost_spec.rb index 4e75e72f4d2..4452e890ebe 100644 --- a/qa/spec/scenario/test/integration/mattermost_spec.rb +++ b/qa/spec/scenario/test/integration/mattermost_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::Mattermost do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:args) { %w[gitlab_address mattermost_address] } let(:args) do diff --git a/qa/spec/scenario/test/integration/oauth_spec.rb b/qa/spec/scenario/test/integration/oauth_spec.rb index c1c320be576..ab7ea905a29 100644 --- a/qa/spec/scenario/test/integration/oauth_spec.rb +++ b/qa/spec/scenario/test/integration/oauth_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::OAuth do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:oauth] } end diff --git a/qa/spec/scenario/test/integration/object_storage_spec.rb b/qa/spec/scenario/test/integration/object_storage_spec.rb index 2b7188223e0..8b4367bee32 100644 --- a/qa/spec/scenario/test/integration/object_storage_spec.rb +++ b/qa/spec/scenario/test/integration/object_storage_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::ObjectStorage do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:object_storage] } end diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb index 3d98f03a982..1a3efe0b46c 100644 --- a/qa/spec/specs/runner_spec.rb +++ b/qa/spec/specs/runner_spec.rb @@ -11,7 +11,7 @@ describe QA::Specs::Runner do end end - context '#perform' do + describe '#perform' do before do allow(QA::Runtime::Browser).to receive(:configure!) end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index c1cfe9f20d2..bdac7369780 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -896,7 +896,7 @@ describe ApplicationController do end end - context '#set_current_context' do + describe '#set_current_context' do controller(described_class) do def index Labkit::Context.with_context do |context| diff --git a/spec/controllers/groups/registry/repositories_controller_spec.rb b/spec/controllers/groups/registry/repositories_controller_spec.rb index 9463483b7b0..eb702d65325 100644 --- a/spec/controllers/groups/registry/repositories_controller_spec.rb +++ b/spec/controllers/groups/registry/repositories_controller_spec.rb @@ -14,7 +14,7 @@ describe Groups::Registry::RepositoriesController do sign_in(user) end - context 'GET #index' do + shared_examples 'renders a list of repositories' do context 'when container registry is enabled' do it 'show index page' do expect(Gitlab::Tracking).not_to receive(:event) @@ -33,6 +33,7 @@ describe Groups::Registry::RepositoriesController do } expect(response).to match_response_schema('registry/repositories') + expect(response).to include_pagination_headers end it 'returns a list of projects for json format' do @@ -89,5 +90,29 @@ describe Groups::Registry::RepositoriesController do expect(response).to have_gitlab_http_status(:not_found) end end + + context 'with :vue_container_registry_explorer feature flag disabled' do + before do + stub_feature_flags(vue_container_registry_explorer: false) + end + + it 'has the correct response schema' do + get :index, params: { + group_id: group, + format: :json + } + + expect(response).to match_response_schema('registry/repositories') + expect(response).not_to include_pagination_headers + end + end + end + + context 'GET #index' do + it_behaves_like 'renders a list of repositories' + end + + context 'GET #show' do + it_behaves_like 'renders a list of repositories' end end diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 3efdeda6171..6b698c6da66 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -245,7 +245,7 @@ describe Projects::MilestonesController do end end - context '#participants' do + describe '#participants' do render_views context "when guest user" do diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb index 4e832a478af..5b9c0211b39 100644 --- a/spec/controllers/projects/registry/repositories_controller_spec.rb +++ b/spec/controllers/projects/registry/repositories_controller_spec.rb @@ -16,7 +16,7 @@ describe Projects::Registry::RepositoriesController do project.add_developer(user) end - describe 'GET index' do + shared_examples 'renders a list of repositories' do context 'when root container repository exists' do before do create(:container_repository, :root, project: project) @@ -58,6 +58,7 @@ describe Projects::Registry::RepositoriesController do expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('registry/repositories') + expect(response).to include_pagination_headers end end @@ -84,9 +85,33 @@ describe Projects::Registry::RepositoriesController do end end end + + context 'with :vue_container_registry_explorer feature flag disabled' do + before do + stub_feature_flags(vue_container_registry_explorer: false) + stub_container_registry_tags(repository: project.full_path, + tags: %w[rc1 latest]) + end + + it 'json has a list of projects' do + go_to_index(format: :json) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + expect(response).not_to include_pagination_headers + end + end + end + + describe 'GET #index' do + it_behaves_like 'renders a list of repositories' + end + + describe 'GET #show' do + it_behaves_like 'renders a list of repositories' end - describe 'DELETE destroy' do + describe 'DELETE #destroy' do context 'when root container repository exists' do let!(:repository) do create(:container_repository, :root, project: project) @@ -115,7 +140,7 @@ describe Projects::Registry::RepositoriesController do end context 'when user does not have access to registry' do - describe 'GET index' do + describe 'GET #index' do it 'responds with 404' do go_to_index diff --git a/spec/factories/alerting/alert.rb b/spec/factories/alerting/alert.rb new file mode 100644 index 00000000000..285bb14efa2 --- /dev/null +++ b/spec/factories/alerting/alert.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :alerting_alert, class: 'Gitlab::Alerting::Alert' do + project + payload { {} } + + transient do + metric_id { nil } + + after(:build) do |alert, evaluator| + unless alert.payload.key?('startsAt') + alert.payload['startsAt'] = Time.now.rfc3339 + end + + if metric_id = evaluator.metric_id + alert.payload['labels'] ||= {} + alert.payload['labels']['gitlab_alert_id'] = metric_id.to_s + end + end + end + + skip_create + end +end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index e714d0f7cad..0ff3e45c956 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -453,7 +453,7 @@ describe 'File blob', :js do end end - context '.gitlab-ci.yml' do + describe '.gitlab-ci.yml' do before do project.add_maintainer(project.creator) @@ -481,7 +481,7 @@ describe 'File blob', :js do end end - context '.gitlab/route-map.yml' do + describe '.gitlab/route-map.yml' do before do project.add_maintainer(project.creator) diff --git a/spec/finders/concerns/finder_with_cross_project_access_spec.rb b/spec/finders/concerns/finder_with_cross_project_access_spec.rb index 6ba98b79176..f3365309b05 100644 --- a/spec/finders/concerns/finder_with_cross_project_access_spec.rb +++ b/spec/finders/concerns/finder_with_cross_project_access_spec.rb @@ -128,7 +128,7 @@ describe FinderWithCrossProjectAccess do end end - context '.finder_model' do + describe '.finder_model' do it 'is set correctly' do expect(finder_class.finder_model).to eq(Project) end diff --git a/spec/finders/projects/prometheus/alerts_finder_spec.rb b/spec/finders/projects/prometheus/alerts_finder_spec.rb new file mode 100644 index 00000000000..bb59e77cca8 --- /dev/null +++ b/spec/finders/projects/prometheus/alerts_finder_spec.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::Prometheus::AlertsFinder do + let(:finder) { described_class.new(params) } + let(:params) { {} } + + describe 'with params' do + let_it_be(:project) { create(:project) } + let_it_be(:other_project) { create(:project) } + let_it_be(:other_env) { create(:environment, project: other_project) } + let_it_be(:production) { create(:environment, project: project) } + let_it_be(:staging) { create(:environment, project: project) } + let_it_be(:alert) { create_alert(project, production) } + let_it_be(:alert2) { create_alert(project, production) } + let_it_be(:stg_alert) { create_alert(project, staging) } + let_it_be(:other_alert) { create_alert(other_project, other_env) } + + describe '#execute' do + subject { finder.execute } + + context 'with project' do + before do + params[:project] = project + end + + it { is_expected.to eq([alert, alert2, stg_alert]) } + + context 'with matching metric' do + before do + params[:metric] = alert.prometheus_metric + end + + it { is_expected.to eq([alert]) } + end + + context 'with matching metric id' do + before do + params[:metric] = alert.prometheus_metric_id + end + + it { is_expected.to eq([alert]) } + end + + context 'with project non-specific metric' do + before do + params[:metric] = other_alert.prometheus_metric + end + + it { is_expected.to be_empty } + end + end + + context 'with environment' do + before do + params[:environment] = production + end + + it { is_expected.to eq([alert, alert2]) } + + context 'with matching metric' do + before do + params[:metric] = alert.prometheus_metric + end + + it { is_expected.to eq([alert]) } + end + + context 'with environment non-specific metric' do + before do + params[:metric] = stg_alert.prometheus_metric + end + + it { is_expected.to be_empty } + end + end + + context 'with matching project and environment' do + before do + params[:project] = project + params[:environment] = production + end + + it { is_expected.to eq([alert, alert2]) } + + context 'with matching metric' do + before do + params[:metric] = alert.prometheus_metric + end + + it { is_expected.to eq([alert]) } + end + + context 'with environment non-specific metric' do + before do + params[:metric] = stg_alert.prometheus_metric + end + + it { is_expected.to be_empty } + end + + context 'with matching id' do + before do + params[:id] = alert.id + end + + it { is_expected.to eq([alert]) } + end + + context 'with a nil id' do + before do + params[:id] = nil + end + + it { is_expected.to eq([alert, alert2]) } + end + end + + context 'with non-matching project-environment pair' do + before do + params[:project] = project + params[:environment] = other_env + end + + it { is_expected.to be_empty } + end + + context 'with id' do + before do + params[:id] = alert.id + end + + it { is_expected.to eq([alert]) } + end + + context 'with multiple ids' do + before do + params[:id] = [alert.id, other_alert.id] + end + + it { is_expected.to eq([alert, other_alert]) } + end + + context 'with non-matching id' do + before do + params[:id] = -5 + end + + it { is_expected.to be_empty } + end + end + + private + + def create_alert(project, environment) + create(:prometheus_alert, project: project, environment: environment) + end + end + + describe 'without params' do + subject { finder } + + it 'raises an error' do + expect { subject } + .to raise_error(ArgumentError, 'Please provide one or more of the following params: :project, :environment, :id') + end + end +end diff --git a/spec/finders/protected_branches_finder_spec.rb b/spec/finders/protected_branches_finder_spec.rb new file mode 100644 index 00000000000..e6a2cf4577c --- /dev/null +++ b/spec/finders/protected_branches_finder_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ProtectedBranchesFinder do + let(:project) { create(:project) } + let!(:protected_branch) { create(:protected_branch, project: project) } + let!(:another_protected_branch) { create(:protected_branch, project: project) } + let!(:other_protected_branch) { create(:protected_branch) } + let(:params) { {} } + + describe '#execute' do + subject { described_class.new(project, params).execute } + + it 'returns all protected branches of project by default' do + expect(subject).to match_array([protected_branch, another_protected_branch]) + end + + context 'when search param is present' do + let(:params) { { search: protected_branch.name } } + + it 'filters by search param' do + expect(subject).to eq([protected_branch]) + end + end + + context 'when there are more protected branches than the limit' do + before do + stub_const("#{described_class}::LIMIT", 1) + end + + it 'returns limited protected branches of project' do + expect(subject).to eq([another_protected_branch]) + end + end + end +end diff --git a/spec/frontend/environments/environment_external_url_spec.js b/spec/frontend/environments/environment_external_url_spec.js new file mode 100644 index 00000000000..9997ea94941 --- /dev/null +++ b/spec/frontend/environments/environment_external_url_spec.js @@ -0,0 +1,16 @@ +import { shallowMount } from '@vue/test-utils'; +import ExternalUrlComp from '~/environments/components/environment_external_url.vue'; + +describe('External URL Component', () => { + let wrapper; + const externalUrl = 'https://gitlab.com'; + + beforeEach(() => { + wrapper = shallowMount(ExternalUrlComp, { propsData: { externalUrl } }); + }); + + it('should link to the provided externalUrl prop', () => { + expect(wrapper.attributes('href')).toEqual(externalUrl); + expect(wrapper.find('a').exists()).toBe(true); + }); +}); diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 7f988c60817..63aa41bbad5 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -258,7 +258,7 @@ describe DiffHelper do end end - context '#render_overflow_warning?' do + describe '#render_overflow_warning?' do let(:diffs_collection) { instance_double(Gitlab::Diff::FileCollection::MergeRequestDiff, raw_diff_files: diff_files) } let(:diff_files) { Gitlab::Git::DiffCollection.new(files) } let(:safe_file) { { too_large: false, diff: '' } } @@ -303,7 +303,7 @@ describe DiffHelper do end end - context '#diff_file_path_text' do + describe '#diff_file_path_text' do it 'returns full path by default' do expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path) end diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb index 8d7572c5b5f..a3e879a3f39 100644 --- a/spec/helpers/nav_helper_spec.rb +++ b/spec/helpers/nav_helper_spec.rb @@ -106,13 +106,13 @@ describe NavHelper, :do_not_mock_admin_mode do end end - context '.admin_monitoring_nav_links' do + describe '.admin_monitoring_nav_links' do subject { helper.admin_monitoring_nav_links } it { is_expected.to all(be_a(String)) } end - context '.group_issues_sub_menu_items' do + describe '.group_issues_sub_menu_items' do subject { helper.group_issues_sub_menu_items } it { is_expected.to all(be_a(String)) } diff --git a/spec/helpers/sourcegraph_helper_spec.rb b/spec/helpers/sourcegraph_helper_spec.rb index 830bbb3129f..3e8486a5632 100644 --- a/spec/helpers/sourcegraph_helper_spec.rb +++ b/spec/helpers/sourcegraph_helper_spec.rb @@ -34,7 +34,7 @@ describe SourcegraphHelper do end end - context '#sourcegraph_experimental_message' do + describe '#sourcegraph_experimental_message' do let(:feature_conditional) { false } let(:public_only) { false } diff --git a/spec/javascripts/environments/environment_external_url_spec.js b/spec/javascripts/environments/environment_external_url_spec.js deleted file mode 100644 index 056d68a26e9..00000000000 --- a/spec/javascripts/environments/environment_external_url_spec.js +++ /dev/null @@ -1,22 +0,0 @@ -import Vue from 'vue'; -import externalUrlComp from '~/environments/components/environment_external_url.vue'; - -describe('External URL Component', () => { - let ExternalUrlComponent; - - beforeEach(() => { - ExternalUrlComponent = Vue.extend(externalUrlComp); - }); - - it('should link to the provided externalUrl prop', () => { - const externalURL = 'https://gitlab.com'; - const component = new ExternalUrlComponent({ - propsData: { - externalUrl: externalURL, - }, - }).$mount(); - - expect(component.$el.getAttribute('href')).toEqual(externalURL); - expect(component.$el.querySelector('fa-external-link')).toBeDefined(); - }); -}); diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb index 7cf70a1f562..e509566fae8 100644 --- a/spec/lib/container_registry/registry_spec.rb +++ b/spec/lib/container_registry/registry_spec.rb @@ -14,7 +14,7 @@ describe ContainerRegistry::Registry do it { expect(subject).not_to be_nil } - context '#path' do + describe '#path' do subject { registry.path } context 'path from URL' do diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb index 9447112e4a8..085c73caa97 100644 --- a/spec/lib/container_registry/tag_spec.rb +++ b/spec/lib/container_registry/tag_spec.rb @@ -70,26 +70,26 @@ describe ContainerRegistry::Tag do headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v1+prettyjws' }) end - context '#layers' do + describe '#layers' do subject { tag.layers } it { expect(subject.length).to eq(1) } end - context '#total_size' do + describe '#total_size' do subject { tag.total_size } it { is_expected.to be_nil } end context 'config processing' do - context '#config' do + describe '#config' do subject { tag.config } it { is_expected.to be_nil } end - context '#created_at' do + describe '#created_at' do subject { tag.created_at } it { is_expected.to be_nil } @@ -113,7 +113,7 @@ describe ContainerRegistry::Tag do body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob_helm.json')) end - context '#created_at' do + describe '#created_at' do subject { tag.created_at } it { is_expected.to be_nil } @@ -130,13 +130,13 @@ describe ContainerRegistry::Tag do headers: { 'Content-Type' => 'application/vnd.docker.distribution.manifest.v2+json' }) end - context '#layers' do + describe '#layers' do subject { tag.layers } it { expect(subject.length).to eq(1) } end - context '#total_size' do + describe '#total_size' do subject { tag.total_size } it { is_expected.to eq(2319870) } @@ -144,13 +144,13 @@ describe ContainerRegistry::Tag do context 'config processing' do shared_examples 'a processable' do - context '#config' do + describe '#config' do subject { tag.config } it { is_expected.not_to be_nil } end - context '#created_at' do + describe '#created_at' do subject { tag.created_at } it { is_expected.not_to be_nil } diff --git a/spec/lib/gitlab/alerting/alert_spec.rb b/spec/lib/gitlab/alerting/alert_spec.rb new file mode 100644 index 00000000000..90e93d189e2 --- /dev/null +++ b/spec/lib/gitlab/alerting/alert_spec.rb @@ -0,0 +1,226 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Alerting::Alert do + let_it_be(:project) { create(:project) } + + let(:alert) { build(:alerting_alert, project: project, payload: payload) } + let(:payload) { {} } + + shared_context 'gitlab alert' do + let(:gitlab_alert_id) { gitlab_alert.prometheus_metric_id.to_s } + let!(:gitlab_alert) { create(:prometheus_alert, project: project) } + + before do + payload['labels'] = { 'gitlab_alert_id' => gitlab_alert_id } + end + end + + shared_examples 'invalid alert' do + it 'is invalid' do + expect(alert).not_to be_valid + end + end + + shared_examples 'parse payload' do |*pairs| + context 'without payload' do + it { is_expected.to be_nil } + end + + pairs.each do |pair| + context "with #{pair}" do + let(:value) { 'some value' } + + before do + section, name = pair.split('/') + payload[section] = { name => value } + end + + it { is_expected.to eq(value) } + end + end + end + + describe '#gitlab_alert' do + subject { alert.gitlab_alert } + + context 'without payload' do + it { is_expected.to be_nil } + end + + context 'with gitlab alert' do + include_context 'gitlab alert' + + it { is_expected.to eq(gitlab_alert) } + end + + context 'with unknown gitlab alert' do + include_context 'gitlab alert' do + let(:gitlab_alert_id) { 'unknown' } + end + + it { is_expected.to be_nil } + end + end + + describe '#title' do + subject { alert.title } + + it_behaves_like 'parse payload', + 'annotations/title', + 'annotations/summary', + 'labels/alertname' + + context 'with gitlab alert' do + include_context 'gitlab alert' + + context 'with annotations/title' do + let(:value) { 'annotation title' } + + before do + payload['annotations'] = { 'title' => value } + end + + it { is_expected.to eq(gitlab_alert.title) } + end + end + end + + describe '#description' do + subject { alert.description } + + it_behaves_like 'parse payload', 'annotations/description' + end + + describe '#annotations' do + subject { alert.annotations } + + context 'without payload' do + it { is_expected.to eq([]) } + end + + context 'with payload' do + before do + payload['annotations'] = { 'foo' => 'value1', 'bar' => 'value2' } + end + + it 'parses annotations' do + expect(subject.size).to eq(2) + expect(subject.map(&:label)).to eq(%w[foo bar]) + expect(subject.map(&:value)).to eq(%w[value1 value2]) + end + end + end + + describe '#environment' do + subject { alert.environment } + + context 'without gitlab_alert' do + it { is_expected.to be_nil } + end + + context 'with gitlab alert' do + include_context 'gitlab alert' + + it { is_expected.to eq(gitlab_alert.environment) } + end + end + + describe '#starts_at' do + subject { alert.starts_at } + + context 'with empty startsAt' do + before do + payload['startsAt'] = nil + end + + it { is_expected.to be_nil } + end + + context 'with invalid startsAt' do + before do + payload['startsAt'] = 'invalid' + end + + it { is_expected.to be_nil } + end + + context 'with payload' do + let(:time) { Time.now.change(usec: 0) } + + before do + payload['startsAt'] = time.rfc3339 + end + + it { is_expected.to eq(time) } + end + end + + describe '#full_query' do + using RSpec::Parameterized::TableSyntax + + subject { alert.full_query } + + where(:generator_url, :expected_query) do + nil | nil + 'http://localhost' | nil + 'invalid url' | nil + 'http://localhost:9090/graph?g1.expr=vector%281%29' | nil + 'http://localhost:9090/graph?g0.expr=vector%281%29' | 'vector(1)' + end + + with_them do + before do + payload['generatorURL'] = generator_url + end + + it { is_expected.to eq(expected_query) } + end + + context 'with gitlab alert' do + include_context 'gitlab alert' + + before do + payload['generatorURL'] = 'http://localhost:9090/graph?g0.expr=vector%281%29' + end + + it { is_expected.to eq(gitlab_alert.full_query) } + end + end + + describe '#alert_markdown' do + subject { alert.alert_markdown } + + it_behaves_like 'parse payload', 'annotations/gitlab_incident_markdown' + end + + describe '#valid?' do + before do + payload.update( + 'annotations' => { 'title' => 'some title' }, + 'startsAt' => Time.now.rfc3339 + ) + end + + subject { alert } + + it { is_expected.to be_valid } + + context 'without project' do + # Redefine to prevent: + # project is a NilClass - rspec-set works with ActiveRecord models only + let(:alert) { build(:alerting_alert, project: nil, payload: payload) } + + it { is_expected.not_to be_valid } + end + + context 'without starts_at' do + before do + payload['startsAt'] = nil + end + + it { is_expected.not_to be_valid } + end + end +end diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb index 3605bac7dfc..10843a1435a 100644 --- a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb +++ b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do before do stub_const('Gitlab::Ci::Build::Rules::Rule::Clause::Exists::MAX_PATTERN_COMPARISONS', 2) - expect(File).to receive(:fnmatch?).exactly(2).times.and_call_original + expect(File).to receive(:fnmatch?).twice.and_call_original end it { is_expected.to be_truthy } diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb index 795e8e51276..1b034656e7d 100644 --- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb +++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do stub_feature_flags(ci_enable_live_trace: true) end - context "#initialize" do + describe "#initialize" do context 'when a chunk exists' do before do build.trace.set('ABC') @@ -35,7 +35,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do end end - context "#seek" do + describe "#seek" do subject { chunked_io.seek(pos, where) } before do @@ -66,7 +66,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do end end - context "#eof?" do + describe "#eof?" do subject { chunked_io.eof? } before do @@ -90,7 +90,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do end end - context "#each_line" do + describe "#each_line" do let(:string_io) { StringIO.new(sample_trace_raw) } context 'when buffer size is smaller than file size' do @@ -134,7 +134,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do end end - context "#read" do + describe "#read" do subject { chunked_io.read(length) } context 'when read the whole size' do @@ -254,7 +254,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do end end - context "#readline" do + describe "#readline" do subject { chunked_io.readline } let(:string_io) { StringIO.new(sample_trace_raw) } @@ -334,7 +334,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do end end - context "#write" do + describe "#write" do subject { chunked_io.write(data) } let(:data) { sample_trace_raw } @@ -399,7 +399,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do end end - context "#truncate" do + describe "#truncate" do let(:offset) { 10 } context 'when data does not exist' do @@ -432,7 +432,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do end end - context "#destroy!" do + describe "#destroy!" do subject { chunked_io.destroy! } before do diff --git a/spec/lib/gitlab/ci/trace/section_parser_spec.rb b/spec/lib/gitlab/ci/trace/section_parser_spec.rb index 6e8504a1584..24ce4d34411 100644 --- a/spec/lib/gitlab/ci/trace/section_parser_spec.rb +++ b/spec/lib/gitlab/ci/trace/section_parser_spec.rb @@ -74,7 +74,7 @@ describe Gitlab::Ci::Trace::SectionParser do let(:lines) { build_lines(trace) } it 'must handle correctly byte positioning' do - expect(subject).to receive(:find_next_marker).exactly(2).times.and_call_original + expect(subject).to receive(:find_next_marker).twice.and_call_original subject.parse! diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb index 5787cce7d20..d1e3a73686e 100644 --- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb +++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb @@ -8,8 +8,8 @@ describe Gitlab::Cleanup::ProjectUploads do let(:logger) { double(:logger) } before do - allow(logger).to receive(:info).at_least(1).times - allow(logger).to receive(:debug).at_least(1).times + allow(logger).to receive(:info).at_least(:once) + allow(logger).to receive(:debug).at_least(:once) end describe '#run!' do diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index 1d404915617..bbbbf91bd44 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::ContentSecurityPolicy::ConfigLoader do } end - context '.default_settings_hash' do + describe '.default_settings_hash' do it 'returns empty defaults' do settings = described_class.default_settings_hash @@ -33,7 +33,7 @@ describe Gitlab::ContentSecurityPolicy::ConfigLoader do end end - context '#load' do + describe '#load' do subject { described_class.new(csp_config[:directives]) } def expected_config(directive) diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb index bf6152ff3c2..570f4bd27cc 100644 --- a/spec/lib/gitlab/danger/teammate_spec.rb +++ b/spec/lib/gitlab/danger/teammate_spec.rb @@ -176,7 +176,7 @@ describe Gitlab::Danger::Teammate do it 'returns true if request fails' do expect(Gitlab::Danger::RequestHelper).to receive(:http_get_json) - .exactly(2).times + .twice .and_raise(Gitlab::Danger::RequestHelper::HTTPError.new) expect(subject.available?).to be true diff --git a/spec/lib/gitlab/database/count_spec.rb b/spec/lib/gitlab/database/count_spec.rb index 71c25f23b6b..2469ce482e7 100644 --- a/spec/lib/gitlab/database/count_spec.rb +++ b/spec/lib/gitlab/database/count_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::Database::Count do let(:models) { [Project, Identity] } - context '.approximate_counts' do + describe '.approximate_counts' do context 'fallbacks' do subject { described_class.approximate_counts(models, strategies: strategies) } diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb index 1e9083950d0..300d7bb14b6 100644 --- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb +++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do Gitlab::GitalyClient.instance_variable_set(:@can_use_disk, {}) end - context '#execute_rugged_call', :request_store do + describe '#execute_rugged_call', :request_store do let(:args) { ['refs/heads/master', 1] } before do diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 231bcb4150c..0831021b22b 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -757,7 +757,7 @@ describe Gitlab::GitAccess do allow(project).to receive(:lfs_enabled?).and_return(true) expect_next_instance_of(Gitlab::Checks::LfsIntegrity) do |instance| - expect(instance).to receive(:objects_missing?).exactly(1).times + expect(instance).to receive(:objects_missing?).once end push_access_check diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb index 1531317c514..28cc13f02de 100644 --- a/spec/lib/gitlab/git_ref_validator_spec.rb +++ b/spec/lib/gitlab/git_ref_validator_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Gitlab::GitRefValidator do using RSpec::Parameterized::TableSyntax - context '.validate' do + describe '.validate' do it { expect(described_class.validate('feature/new')).to be true } it { expect(described_class.validate('implement_@all')).to be true } it { expect(described_class.validate('my_new_feature')).to be true } @@ -37,7 +37,7 @@ describe Gitlab::GitRefValidator do it { expect(described_class.validate("\xA0\u0000\xB0")).to be false } end - context '.validate_merge_request_branch' do + describe '.validate_merge_request_branch' do it { expect(described_class.validate_merge_request_branch('HEAD')).to be true } it { expect(described_class.validate_merge_request_branch('feature/new')).to be true } it { expect(described_class.validate_merge_request_branch('implement_@all')).to be true } diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb index fac2dbfab71..c7b9775f642 100644 --- a/spec/lib/gitlab/gpg_spec.rb +++ b/spec/lib/gitlab/gpg_spec.rb @@ -215,8 +215,8 @@ describe Gitlab::Gpg do end it 'tries at least 2 times to remove the tmp dir before raising', :aggregate_failures do - expect(Retriable).to receive(:sleep).at_least(2).times - expect(FileUtils).to receive(:remove_entry).with(tmp_dir).at_least(2).times.and_raise('Deletion failed') + expect(Retriable).to receive(:sleep).at_least(:twice) + expect(FileUtils).to receive(:remove_entry).with(tmp_dir).at_least(:twice).and_raise('Deletion failed') expect { described_class.using_tmp_keychain { } }.to raise_error(described_class::CleanupError) end diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb index c6ee0a3c094..7fef763f64d 100644 --- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb @@ -205,7 +205,7 @@ describe Gitlab::LegacyGithubImport::Importer do let(:gh_pull_request) { Gitlab::LegacyGithubImport::PullRequestFormatter.new(project, closed_pull_request) } it 'does remove branches' do - expect(subject).to receive(:remove_branch).at_least(2).times + expect(subject).to receive(:remove_branch).at_least(:twice) subject.send(:clean_up_restored_branches, gh_pull_request) end end diff --git a/spec/lib/gitlab/private_commit_email_spec.rb b/spec/lib/gitlab/private_commit_email_spec.rb index 10bf624bbdd..7b7a0f7c0ca 100644 --- a/spec/lib/gitlab/private_commit_email_spec.rb +++ b/spec/lib/gitlab/private_commit_email_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::PrivateCommitEmail do let(:valid_email) { "#{id}-foo@#{hostname}" } let(:invalid_email) { "#{id}-foo@users.noreply.bar.com" } - context '.regex' do + describe '.regex' do subject { described_class.regex } it { is_expected.to match("1-foo@#{hostname}") } @@ -18,7 +18,7 @@ describe Gitlab::PrivateCommitEmail do it { is_expected.not_to match('foobar@gitlab.com') } end - context '.user_id_for_email' do + describe '.user_id_for_email' do it 'parses user id from email' do expect(described_class.user_id_for_email(valid_email)).to eq(id) end @@ -28,7 +28,7 @@ describe Gitlab::PrivateCommitEmail do end end - context '.user_ids_for_email' do + describe '.user_ids_for_email' do it 'returns deduplicated user IDs for each valid email' do result = described_class.user_ids_for_emails([valid_email, valid_email, invalid_email]) @@ -41,7 +41,7 @@ describe Gitlab::PrivateCommitEmail do end end - context '.for_user' do + describe '.for_user' do it 'returns email in the format id-username@hostname' do user = create(:user) diff --git a/spec/lib/gitlab/rugged_instrumentation_spec.rb b/spec/lib/gitlab/rugged_instrumentation_spec.rb index 4dcc8ae514a..64c0ce1b65e 100644 --- a/spec/lib/gitlab/rugged_instrumentation_spec.rb +++ b/spec/lib/gitlab/rugged_instrumentation_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::RuggedInstrumentation, :request_store do end end - context '.increment_query_count' do + describe '.increment_query_count' do it 'tracks query counts' do expect(subject.query_count).to eq(0) diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb index 11e430e0be4..f0b733817b3 100644 --- a/spec/lib/gitlab/sanitizers/exif_spec.rb +++ b/spec/lib/gitlab/sanitizers/exif_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Sanitizers::Exif do end it 'processes only uploads created since specified date' do - expect(sanitizer).to receive(:clean).exactly(2).times + expect(sanitizer).to receive(:clean).twice sanitizer.batch_clean(since: 2.days.ago) end diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb index a8c565aa705..f2b682850e3 100644 --- a/spec/lib/omni_auth/strategies/jwt_spec.rb +++ b/spec/lib/omni_auth/strategies/jwt_spec.rb @@ -6,7 +6,7 @@ describe OmniAuth::Strategies::Jwt do include Rack::Test::Methods include DeviseHelpers - context '#decoded' do + describe '#decoded' do subject { described_class.new({}) } let(:timestamp) { Time.now.to_i } diff --git a/spec/lib/safe_zip/entry_spec.rb b/spec/lib/safe_zip/entry_spec.rb index 0974f732188..be3d46917ee 100644 --- a/spec/lib/safe_zip/entry_spec.rb +++ b/spec/lib/safe_zip/entry_spec.rb @@ -25,13 +25,13 @@ describe SafeZip::Entry do FileUtils.remove_entry_secure(target_path) end - context '#path_dir' do + describe '#path_dir' do subject { entry.path_dir } it { is_expected.to eq(target_path + '/public/folder') } end - context '#exist?' do + describe '#exist?' do subject { entry.exist? } context 'when entry does not exist' do diff --git a/spec/lib/safe_zip/extract_spec.rb b/spec/lib/safe_zip/extract_spec.rb index 3b8c64c1c9f..d388135c3fb 100644 --- a/spec/lib/safe_zip/extract_spec.rb +++ b/spec/lib/safe_zip/extract_spec.rb @@ -12,7 +12,7 @@ describe SafeZip::Extract do FileUtils.remove_entry_secure(target_path) end - context '#extract' do + describe '#extract' do subject { object.extract(directories: directories, to: target_path) } shared_examples 'extracts archive' do |param| diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb index c661f5384ea..60ae579eb03 100644 --- a/spec/models/badge_spec.rb +++ b/spec/models/badge_spec.rb @@ -81,13 +81,13 @@ describe Badge do let(:badge) { build(:badge, link_url: placeholder_url, image_url: placeholder_url) } let!(:project) { create(:project) } - context '#rendered_link_url' do + describe '#rendered_link_url' do let(:method) { :link_url } it_behaves_like 'rendered_links' end - context '#rendered_image_url' do + describe '#rendered_image_url' do let(:method) { :image_url } it_behaves_like 'rendered_links' diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb index d41c5cf2ca1..c0e85d3de87 100644 --- a/spec/models/badges/project_badge_spec.rb +++ b/spec/models/badges/project_badge_spec.rb @@ -30,13 +30,13 @@ describe ProjectBadge do let(:badge) { build(:project_badge, link_url: placeholder_url, image_url: placeholder_url) } let!(:project) { badge.project } - context '#rendered_link_url' do + describe '#rendered_link_url' do let(:method) { :link_url } it_behaves_like 'rendered_links' end - context '#rendered_image_url' do + describe '#rendered_image_url' do let(:method) { :image_url } it_behaves_like 'rendered_links' diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb index 8a66b55cc15..99983686670 100644 --- a/spec/models/ci/artifact_blob_spec.rb +++ b/spec/models/ci/artifact_blob_spec.rb @@ -51,7 +51,7 @@ describe Ci::ArtifactBlob do allow(Gitlab.config.pages).to receive(:artifacts_server).and_return(true) end - context '.gif extension' do + describe '.gif extension' do it 'returns nil' do expect(subject.external_url(build.project, build)).to be_nil end diff --git a/spec/models/ci/persistent_ref_spec.rb b/spec/models/ci/persistent_ref_spec.rb index ece478fdd36..4cece0664cf 100644 --- a/spec/models/ci/persistent_ref_spec.rb +++ b/spec/models/ci/persistent_ref_spec.rb @@ -11,7 +11,7 @@ describe Ci::PersistentRef do pipeline.succeed! end - context '#exist?' do + describe '#exist?' do subject { pipeline.persistent_ref.exist? } let(:pipeline) { create(:ci_pipeline, sha: sha, project: project) } @@ -31,7 +31,7 @@ describe Ci::PersistentRef do end end - context '#create' do + describe '#create' do subject { pipeline.persistent_ref.create } let(:pipeline) { create(:ci_pipeline, sha: sha, project: project) } @@ -81,7 +81,7 @@ describe Ci::PersistentRef do end end - context '#delete' do + describe '#delete' do subject { pipeline.persistent_ref.delete } let(:pipeline) { create(:ci_pipeline, sha: sha, project: project) } diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 5c9a03a26ec..3e494d19233 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -25,7 +25,7 @@ describe Ci::Runner do end end - context '#exactly_one_group' do + describe '#exactly_one_group' do let(:group) { create(:group) } let(:runner) { create(:ci_runner, :group, groups: [group]) } diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb index c750be6b75c..100953549ea 100644 --- a/spec/models/concerns/avatarable_spec.rb +++ b/spec/models/concerns/avatarable_spec.rb @@ -48,14 +48,14 @@ describe Avatarable do end it 'calls local_url twice for path and URLs' do - expect(project.avatar).to receive(:local_url).exactly(2).times.and_call_original + expect(project.avatar).to receive(:local_url).twice.and_call_original expect(project.avatar_path(only_path: true)).to eq(avatar_path) expect(project.avatar_path(only_path: false)).to eq(avatar_url) end it 'calls local_url twice for different sizes' do - expect(project.avatar).to receive(:local_url).exactly(2).times.and_call_original + expect(project.avatar).to receive(:local_url).twice.and_call_original expect(project.avatar_path).to eq(avatar_path) expect(project.avatar_path(size: 40)).to eq(avatar_path + "?width=40") @@ -64,7 +64,7 @@ describe Avatarable do it 'handles unpersisted objects' do new_project = build(:project, :with_avatar) path = [relative_url_root, new_project.avatar.local_url].join - expect(new_project.avatar).to receive(:local_url).exactly(2).times.and_call_original + expect(new_project.avatar).to receive(:local_url).twice.and_call_original 2.times do expect(new_project.avatar_path).to eq(path) diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index f78a089bc2e..c891fdcb6b5 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -58,7 +58,7 @@ describe Group, 'Routable' do end end - context '.find_by_full_path' do + describe '.find_by_full_path' do let!(:nested_group) { create(:group, parent: group) } context 'without any redirect routes' do diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb index ac1bc51d950..10a6c1aa821 100644 --- a/spec/models/concerns/triggerable_hooks_spec.rb +++ b/spec/models/concerns/triggerable_hooks_spec.rb @@ -49,7 +49,7 @@ RSpec.describe TriggerableHooks do TestableHook.create!(url: 'http://example2.com', push_events: true) filter1 = double(:filter1) filter2 = double(:filter2) - allow(ActiveHookFilter).to receive(:new).exactly(2).times.and_return(filter1, filter2) + allow(ActiveHookFilter).to receive(:new).twice.and_return(filter1, filter2) expect(filter1).to receive(:matches?).and_return(true) expect(filter2).to receive(:matches?).and_return(false) diff --git a/spec/models/deployment_metrics_spec.rb b/spec/models/deployment_metrics_spec.rb index 32c04e15b73..5a4ae0bbe79 100644 --- a/spec/models/deployment_metrics_spec.rb +++ b/spec/models/deployment_metrics_spec.rb @@ -87,7 +87,7 @@ describe DeploymentMetrics do expect(prometheus_adapter).to receive(:query).with(:deployment, deployment).and_return(simple_metrics) end - it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) } + it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.finished_at.to_i })) } end end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 0c1b259d6bf..c1beef0b759 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -495,7 +495,7 @@ describe Deployment do end end - context '#update_status' do + describe '#update_status' do let(:deploy) { create(:deployment, status: :running) } it 'changes the status' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 5aa74134692..c0501fb16c6 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -359,7 +359,7 @@ describe Issue do allow(subject.project).to receive(:repository).and_return(repository) end - context '#to_branch_name does not exists' do + describe '#to_branch_name does not exists' do before do allow(repository).to receive(:branch_exists?).and_return(false) end @@ -369,7 +369,7 @@ describe Issue do end end - context '#to_branch_name exists not ending with -index' do + describe '#to_branch_name exists not ending with -index' do before do allow(repository).to receive(:branch_exists?).and_return(true) allow(repository).to receive(:branch_exists?).with(/#{subject.to_branch_name}-\d/).and_return(false) @@ -380,7 +380,7 @@ describe Issue do end end - context '#to_branch_name exists ending with -index' do + describe '#to_branch_name exists ending with -index' do before do allow(repository).to receive(:branch_exists?).and_return(true) allow(repository).to receive(:branch_exists?).with("#{subject.to_branch_name}-3").and_return(false) diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb index 2ba53818e54..f6a36dbb3fc 100644 --- a/spec/models/notification_recipient_spec.rb +++ b/spec/models/notification_recipient_spec.rb @@ -80,7 +80,7 @@ describe NotificationRecipient do end end - context '#notification_setting' do + describe '#notification_setting' do context 'for child groups' do let!(:moved_group) { create(:group) } let(:group) { create(:group) } diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index 65d227a17f9..1b946278790 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -148,7 +148,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do end shared_examples 'reactive cache calculation' do - context '#build_page' do + describe '#build_page' do subject { service.calculate_reactive_cache('123', 'unused')[:build_page] } it 'returns a specific URL when status is 500' do @@ -180,7 +180,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do end end - context '#commit_status' do + describe '#commit_status' do subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } it 'sets commit status to :error when status is 500' do diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index ca196069055..c622b7706c6 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -83,7 +83,7 @@ describe BuildkiteService, :use_clean_rails_memory_store_caching do end describe '#calculate_reactive_cache' do - context '#commit_status' do + describe '#commit_status' do subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } it 'sets commit status to :error when status is 500' do diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index d3adc62c38e..c4d10be8331 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -31,7 +31,7 @@ describe ChatMessage::IssueMessage do context 'without markdown' do let(:color) { '#C95823' } - context '#initialize' do + describe '#initialize' do before do args[:object_attributes][:description] = nil end diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index a771d1bf27f..0639a4c1f23 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -86,7 +86,7 @@ describe DroneCiService, :use_clean_rails_memory_store_caching do describe '#calculate_reactive_cache' do include_context :drone_ci_service - context '#commit_status' do + describe '#commit_status' do subject { drone.calculate_reactive_cache(sha, branch)[:commit_status] } it 'sets commit status to :error when status is 500' do diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index a1bd0855708..ae6e93cfe3a 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -352,7 +352,7 @@ describe HipchatService do end end - context "#message_options" do + describe "#message_options" do it "is set to the defaults" do expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' }) end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index c573bf7793a..832c19adf1d 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -152,7 +152,7 @@ describe JiraService do end end - context '#update' do + describe '#update' do context 'basic update' do let(:new_username) { 'new_username' } let(:new_url) { 'http://jira-new.example.com' } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index fdcee973fa7..ae4db1c2158 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -4966,7 +4966,7 @@ describe Project do end end - context '#members_among' do + describe '#members_among' do let(:users) { create_list(:user, 3) } set(:group) { create(:group) } @@ -5507,6 +5507,18 @@ describe Project do end end + describe '#limited_protected_branches' do + let(:project) { create(:project) } + let!(:protected_branch) { create(:protected_branch, project: project) } + let!(:another_protected_branch) { create(:protected_branch, project: project) } + + subject { project.limited_protected_branches(1) } + + it 'returns limited number of protected branches based on specified limit' do + expect(subject).to eq([another_protected_branch]) + end + end + def rugged_config rugged_repo(project.repository).config end diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index 267434a4148..7f8a60dafa8 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -220,4 +220,32 @@ describe ProtectedBranch do end end end + + describe '.by_name' do + let!(:protected_branch) { create(:protected_branch, name: 'master') } + let!(:another_protected_branch) { create(:protected_branch, name: 'stable') } + + it 'returns protected branches with a matching name' do + expect(described_class.by_name(protected_branch.name)) + .to eq([protected_branch]) + end + + it 'returns protected branches with a partially matching name' do + expect(described_class.by_name(protected_branch.name[0..2])) + .to eq([protected_branch]) + end + + it 'returns protected branches with a matching name regardless of the casing' do + expect(described_class.by_name(protected_branch.name.upcase)) + .to eq([protected_branch]) + end + + it 'returns nothing when nothing matches' do + expect(described_class.by_name('unknown')).to be_empty + end + + it 'return nothing when query is blank' do + expect(described_class.by_name('')).to be_empty + end + end end diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index 79d45da8a1e..f5e718e0e09 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -227,7 +227,7 @@ describe RemoteMirror, :mailer do end end - context '#sync' do + describe '#sync' do let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first } around do |example| @@ -297,7 +297,7 @@ describe RemoteMirror, :mailer do end end - context '#ensure_remote!' do + describe '#ensure_remote!' do let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first } let(:project) { remote_mirror.project } let(:repository) { project.repository } @@ -321,7 +321,7 @@ describe RemoteMirror, :mailer do end end - context '#url=' do + describe '#url=' do let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first } it 'resets all the columns when URL changes' do @@ -340,7 +340,7 @@ describe RemoteMirror, :mailer do end end - context '#updated_since?' do + describe '#updated_since?' do let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first } let(:timestamp) { Time.now - 5.minutes } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d441f54a0fb..ac001ec3118 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -681,7 +681,7 @@ describe User, :do_not_mock_admin_mode do end describe 'before save hook' do - context '#default_private_profile_to_false' do + describe '#default_private_profile_to_false' do let(:user) { create(:user, private_profile: true) } it 'converts nil to false' do @@ -3161,7 +3161,7 @@ describe User, :do_not_mock_admin_mode do end end - context '.active' do + describe '.active' do before do described_class.ghost create(:user, name: 'user', state: 'active') @@ -3181,7 +3181,7 @@ describe User, :do_not_mock_admin_mode do end end - context '#invalidate_issue_cache_counts' do + describe '#invalidate_issue_cache_counts' do let(:user) { build_stubbed(:user) } it 'invalidates cache for issue counter' do @@ -3195,7 +3195,7 @@ describe User, :do_not_mock_admin_mode do end end - context '#invalidate_merge_request_cache_counts' do + describe '#invalidate_merge_request_cache_counts' do let(:user) { build_stubbed(:user) } it 'invalidates cache for Merge Request counter' do @@ -3209,7 +3209,7 @@ describe User, :do_not_mock_admin_mode do end end - context '#invalidate_personal_projects_count' do + describe '#invalidate_personal_projects_count' do let(:user) { build_stubbed(:user) } it 'invalidates cache for personal projects counter' do diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index a7c28519c5a..166620a927d 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -44,7 +44,7 @@ describe WikiPage do WikiDirectory.new('dir_2', pages) end - context "#list_pages" do + describe "#list_pages" do context 'sort by title' do let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages) } let(:expected_grouped_entries) { [dir_1_1, dir_1, page_dir_2, dir_2, page_1, page_6] } diff --git a/spec/presenters/projects/prometheus/alert_presenter_spec.rb b/spec/presenters/projects/prometheus/alert_presenter_spec.rb new file mode 100644 index 00000000000..fc6ddcbfe02 --- /dev/null +++ b/spec/presenters/projects/prometheus/alert_presenter_spec.rb @@ -0,0 +1,235 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::Prometheus::AlertPresenter do + let_it_be(:project) { create(:project) } + + let(:presenter) { described_class.new(alert) } + let(:payload) { {} } + let(:alert) { create(:alerting_alert, project: project, payload: payload) } + + describe '#project_full_path' do + subject { presenter.project_full_path } + + it { is_expected.to eq(project.full_path) } + end + + describe '#starts_at' do + subject { presenter.starts_at } + + before do + payload['startsAt'] = starts_at + end + + context 'with valid datetime' do + let(:datetime) { Time.now } + let(:starts_at) { datetime.rfc3339 } + + it { is_expected.to eq(datetime.rfc3339) } + end + + context 'with invalid datetime' do + let(:starts_at) { 'invalid' } + + it { is_expected.to be_nil } + end + end + + describe '#issue_summary_markdown' do + let(:markdown_line_break) { ' ' } + + subject { presenter.issue_summary_markdown } + + context 'without default payload' do + it do + is_expected.to eq( + <<~MARKDOWN.chomp + #### Summary + + **Start time:** #{presenter.starts_at} + + MARKDOWN + ) + end + end + + context 'with annotations' do + before do + payload['annotations'] = { 'title' => 'Alert Title', 'foo' => 'value1', 'bar' => 'value2' } + end + + it do + is_expected.to eq( + <<~MARKDOWN.chomp + #### Summary + + **Start time:** #{presenter.starts_at} + + #### Alert Details + + **foo:** value1#{markdown_line_break} + **bar:** value2 + MARKDOWN + ) + end + end + + context 'with full query' do + before do + payload['generatorURL'] = 'http://host?g0.expr=query' + end + + it do + is_expected.to eq( + <<~MARKDOWN.chomp + #### Summary + + **Start time:** #{presenter.starts_at}#{markdown_line_break} + **full_query:** `query` + + MARKDOWN + ) + end + end + + context 'with the Generic Alert parameters' do + let(:generic_alert_params) do + { + 'title' => 'The Generic Alert Title', + 'description' => 'The Generic Alert Description', + 'monitoring_tool' => 'monitoring_tool_name', + 'service' => 'service_name', + 'hosts' => ['http://localhost:3000', 'http://localhost:3001'] + } + end + + before do + payload['annotations'] = generic_alert_params + end + + it do + is_expected.to eq( + <<~MARKDOWN.chomp + #### Summary + + **Start time:** #{presenter.starts_at}#{markdown_line_break} + **Service:** service_name#{markdown_line_break} + **Monitoring tool:** monitoring_tool_name#{markdown_line_break} + **Hosts:** http://localhost:3000 http://localhost:3001 + + #### Alert Details + + **description:** The Generic Alert Description + MARKDOWN + ) + end + + context 'when hosts is a string' do + before do + payload['annotations'] = { 'hosts' => 'http://localhost:3000' } + end + + it do + is_expected.to eq( + <<~MARKDOWN.chomp + #### Summary + + **Start time:** #{presenter.starts_at}#{markdown_line_break} + **Hosts:** http://localhost:3000 + + MARKDOWN + ) + end + end + end + end + + context 'with gitlab alert' do + let(:gitlab_alert) { create(:prometheus_alert, project: project) } + let(:metric_id) { gitlab_alert.prometheus_metric_id } + + let(:alert) do + create(:alerting_alert, project: project, metric_id: metric_id) + end + + describe '#full_title' do + let(:query_title) do + "#{gitlab_alert.title} #{gitlab_alert.computed_operator} #{gitlab_alert.threshold} for 5 minutes" + end + + let(:expected_subject) do + "#{alert.environment.name}: #{query_title}" + end + + subject { presenter.full_title } + + it { is_expected.to eq(expected_subject) } + end + + describe '#metric_query' do + subject { presenter.metric_query } + + it { is_expected.to eq(gitlab_alert.full_query) } + end + + describe '#environment_name' do + subject { presenter.environment_name } + + it { is_expected.to eq(alert.environment.name) } + end + + describe '#performance_dashboard_link' do + let(:expected_link) do + Gitlab::Routing.url_helpers + .metrics_project_environment_url(project, alert.environment) + end + + subject { presenter.performance_dashboard_link } + + it { is_expected.to eq(expected_link) } + end + end + + context 'without gitlab alert' do + describe '#full_title' do + subject { presenter.full_title } + + context 'with title' do + let(:title) { 'some title' } + + before do + expect(alert).to receive(:title).and_return(title) + end + + it { is_expected.to eq(title) } + end + + context 'without title' do + it { is_expected.to eq('') } + end + end + + describe '#metric_query' do + subject { presenter.metric_query } + + it { is_expected.to be_nil } + end + + describe '#environment_name' do + subject { presenter.environment_name } + + it { is_expected.to be_nil } + end + + describe '#performance_dashboard_link' do + let(:expected_link) do + Gitlab::Routing.url_helpers.metrics_project_environments_url(project) + end + + subject { presenter.performance_dashboard_link } + + it { is_expected.to eq(expected_link) } + end + end +end diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index d0378278600..cad9329fcb8 100644 --- a/spec/requests/api/graphql_spec.rb +++ b/spec/requests/api/graphql_spec.rb @@ -46,7 +46,7 @@ describe 'GraphQL' do end it 'logs the exception in Sentry and continues with the request' do - expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(1).times + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once) expect(Gitlab::GraphqlLogger).to receive(:info) post_graphql(query, variables: {}) diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb index ef63902ffd7..f2a1b335589 100644 --- a/spec/requests/api/issues/get_group_issues_spec.rb +++ b/spec/requests/api/issues/get_group_issues_spec.rb @@ -689,7 +689,7 @@ describe API::Issues do end end - context "#to_reference" do + describe "#to_reference" do it 'exposes reference path in context of group' do get api(base_url, user) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index adfe865da90..245a8aa4905 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -781,7 +781,7 @@ describe API::MergeRequests do it_behaves_like 'merge requests list' end - context "#to_reference" do + describe "#to_reference" do it 'exposes reference path in context of group' do get api("/groups/#{group.id}/merge_requests", user) diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb index 67ce704b3f3..8499a165d8b 100644 --- a/spec/requests/api/protected_branches_spec.rb +++ b/spec/requests/api/protected_branches_spec.rb @@ -12,18 +12,18 @@ describe API::ProtectedBranches do end describe "GET /projects/:id/protected_branches" do + let(:params) { {} } let(:route) { "/projects/#{project.id}/protected_branches" } shared_examples_for 'protected branches' do it 'returns the protected branches' do - get api(route, user), params: { per_page: 100 } + get api(route, user), params: params.merge(per_page: 100) expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array protected_branch_names = json_response.map { |x| x['name'] } - expected_branch_names = project.protected_branches.map { |x| x['name'] } expect(protected_branch_names).to match_array(expected_branch_names) end end @@ -33,7 +33,19 @@ describe API::ProtectedBranches do project.add_maintainer(user) end - it_behaves_like 'protected branches' + context 'when search param is not present' do + it_behaves_like 'protected branches' do + let(:expected_branch_names) { project.protected_branches.map { |x| x['name'] } } + end + end + + context 'when search param is present' do + it_behaves_like 'protected branches' do + let(:another_protected_branch) { create(:protected_branch, project: project, name: 'stable') } + let(:params) { { search: another_protected_branch.name } } + let(:expected_branch_names) { [another_protected_branch.name] } + end + end end context 'when authenticated as a guest' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 84e1f95828a..f6ff2020c79 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -778,6 +778,12 @@ describe API::Users do expect(user.reload.external?).to be_truthy end + it "private profile is false by default" do + put api("/users/#{user.id}", admin), params: {} + + expect(user.reload.private_profile).to eq(false) + end + it "updates private profile" do put api("/users/#{user.id}", admin), params: { private_profile: true } @@ -785,14 +791,24 @@ describe API::Users do expect(user.reload.private_profile).to eq(true) end - it "updates private profile when nil is given to false" do - admin.update(private_profile: true) + it "updates private profile to false when nil is given" do + user.update(private_profile: true) put api("/users/#{user.id}", admin), params: { private_profile: nil } + expect(response).to have_gitlab_http_status(200) expect(user.reload.private_profile).to eq(false) end + it "does not modify private profile when field is not provided" do + user.update(private_profile: true) + + put api("/users/#{user.id}", admin), params: {} + + expect(response).to have_gitlab_http_status(200) + expect(user.reload.private_profile).to eq(true) + end + it "does not update admin status" do put api("/users/#{admin_user.id}", admin), params: { can_create_group: false } diff --git a/spec/serializers/container_repositories_serializer_spec.rb b/spec/serializers/container_repositories_serializer_spec.rb new file mode 100644 index 00000000000..382778389b3 --- /dev/null +++ b/spec/serializers/container_repositories_serializer_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ContainerRepositoriesSerializer do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:resource) { create(:container_repository, name: 'image', project: project) } + let(:params) { { current_user: user, project: project } } + + before do + project.add_developer(user) + + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: /image/, tags: %w(rootA latest)) + end + + describe '#represent' do + subject do + described_class.new(params).represent(resource) + end + + it 'has basic attributes' do + expect(subject).to include(:id, :name, :path, :location, :created_at, :tags_path, :destroy_path) + end + end + + describe '#represent_read_only' do + subject do + described_class.new(current_user: user, project: project).represent_read_only(resource) + end + + it 'does not include destroy_path' do + expect(subject).to include(:id, :name, :path, :location, :created_at, :tags_path) + expect(subject).not_to include(:destroy_path) + end + end + + describe '#with_pagination' do + let(:request) do + double( + url: "#{Gitlab.config.gitlab.url}:8080/#{project.namespace_id}/#{project.id}/container_registry?#{query.to_query}", + query_parameters: query + ) + end + + let(:response) { spy('response') } + let(:resource) { ContainerRepository.all } + let(:query) { { page: 1, per_page: 2 } } + + let(:serializer) do + described_class + .new(current_user: user, project: project) + .with_pagination(request, response) + end + + subject do + serializer.represent(resource) + end + + it 'creates a paginated serializer' do + expect(serializer).to be_paginated + end + + context 'when multiple ContainerRepository objects are serialized' do + before do + create_list(:container_repository, 5, project: project) + end + + it 'serializes appropriate number of objects' do + expect(subject.count).to be 2 + end + + it 'appends relevant headers' do + expect(response).to include_pagination_headers + expect(response).to receive(:[]=).with('X-Total', '5') + expect(response).to receive(:[]=).with('X-Total-Pages', '3') + expect(response).to receive(:[]=).with('X-Per-Page', '2') + + subject + end + end + end +end diff --git a/spec/serializers/diff_file_entity_spec.rb b/spec/serializers/diff_file_entity_spec.rb index 65b62f8aa16..3e341a58a15 100644 --- a/spec/serializers/diff_file_entity_spec.rb +++ b/spec/serializers/diff_file_entity_spec.rb @@ -49,7 +49,7 @@ describe DiffFileEntity do end end - context '#parallel_diff_lines' do + describe '#parallel_diff_lines' do let(:options) { { diff_view: :parallel } } it 'exposes parallel diff lines correctly' do diff --git a/spec/services/branches/delete_merged_service_spec.rb b/spec/services/branches/delete_merged_service_spec.rb index 962af8110f7..5c87f156ec7 100644 --- a/spec/services/branches/delete_merged_service_spec.rb +++ b/spec/services/branches/delete_merged_service_spec.rb @@ -9,7 +9,7 @@ describe Branches::DeleteMergedService do let(:project) { create(:project, :repository) } - context '#execute' do + describe '#execute' do it 'deletes a branch that was merged' do service.execute @@ -74,7 +74,7 @@ describe Branches::DeleteMergedService do end end - context '#async_execute' do + describe '#async_execute' do it 'calls DeleteMergedBranchesWorker async' do expect(DeleteMergedBranchesWorker).to receive(:perform_async) diff --git a/spec/services/ci/ensure_stage_service_spec.rb b/spec/services/ci/ensure_stage_service_spec.rb index de07a1ae238..8a270d77bae 100644 --- a/spec/services/ci/ensure_stage_service_spec.rb +++ b/spec/services/ci/ensure_stage_service_spec.rb @@ -44,7 +44,7 @@ describe Ci::EnsureStageService, '#execute' do it 'retries up to two times' do job.assign_attributes(stage_id: nil) - expect(service).to receive(:find_stage).exactly(2).times + expect(service).to receive(:find_stage).twice expect { service.execute(job) } .to raise_error(Ci::EnsureStageService::EnsureStageError) diff --git a/spec/services/clusters/cleanup/app_service_spec.rb b/spec/services/clusters/cleanup/app_service_spec.rb index cc27f409086..14bfca02fee 100644 --- a/spec/services/clusters/cleanup/app_service_spec.rb +++ b/spec/services/clusters/cleanup/app_service_spec.rb @@ -85,7 +85,7 @@ describe Clusters::Cleanup::AppService do it 'logs application uninstalls and next execution' do expect(logger).to receive(:info) - .with(log_meta.merge(event: :uninstalling_app, application: kind_of(String))).exactly(2).times + .with(log_meta.merge(event: :uninstalling_app, application: kind_of(String))).twice expect(logger).to receive(:info) .with(log_meta.merge(event: :scheduling_execution, next_execution: 1)) diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb index c64b93a2532..8b4f45010ed 100644 --- a/spec/services/git/branch_push_service_spec.rb +++ b/spec/services/git/branch_push_service_spec.rb @@ -12,6 +12,7 @@ describe Git::BranchPushService, services: true do let(:newrev) { sample_commit.id } let(:branch) { 'master' } let(:ref) { "refs/heads/#{branch}" } + let(:push_options) { nil } before do project.add_maintainer(user) @@ -19,7 +20,7 @@ describe Git::BranchPushService, services: true do describe 'Push branches' do subject do - execute_service(project, user, oldrev: oldrev, newrev: newrev, ref: ref) + execute_service(project, user, oldrev: oldrev, newrev: newrev, ref: ref, push_options: push_options) end context 'new branch' do @@ -113,6 +114,20 @@ describe Git::BranchPushService, services: true do expect { subject }.not_to change { Ci::Pipeline.count } end + + context 'with push options' do + let(:push_options) { ['mr.create'] } + + it 'sanitizes push options' do + allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true) + expect(Sidekiq.logger).to receive(:warn) do |args| + pipeline_params = args[:pipeline_params] + expect(pipeline_params.keys).to match_array(%i(before after ref variables_attributes checkout_sha)) + end + + expect { subject }.not_to change { Ci::Pipeline.count } + end + end end end @@ -637,8 +652,8 @@ describe Git::BranchPushService, services: true do end end - def execute_service(project, user, change) - service = described_class.new(project, user, change: change) + def execute_service(project, user, change, push_options = {}) + service = described_class.new(project, user, change: change, push_options: push_options) service.execute service end diff --git a/spec/services/labels/available_labels_service_spec.rb b/spec/services/labels/available_labels_service_spec.rb index 4d5c87ecc53..ce120344f16 100644 --- a/spec/services/labels/available_labels_service_spec.rb +++ b/spec/services/labels/available_labels_service_spec.rb @@ -12,7 +12,7 @@ describe Labels::AvailableLabelsService do let(:other_group_label) { create(:group_label) } let(:labels) { [project_label, other_project_label, group_label, other_group_label] } - context '#find_or_create_by_titles' do + describe '#find_or_create_by_titles' do let(:label_titles) { labels.map(&:title).push('non existing title') } context 'when parent is a project' do @@ -64,7 +64,7 @@ describe Labels::AvailableLabelsService do end end - context '#filter_labels_ids_in_param' do + describe '#filter_labels_ids_in_param' do let(:label_ids) { labels.map(&:id).push(99999) } context 'when parent is a project' do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 1f1ccff2ba8..e7d0e91bced 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2264,7 +2264,7 @@ describe NotificationService, :mailer do end it 'filters out guests when new note is created' do - expect(SentNotification).to receive(:record).with(merge_request, any_args).exactly(1).times + expect(SentNotification).to receive(:record).with(merge_request, any_args).once notification.new_note(note) diff --git a/spec/services/projects/batch_open_issues_count_service_spec.rb b/spec/services/projects/batch_open_issues_count_service_spec.rb index e978334d68b..8cb0ce03fba 100644 --- a/spec/services/projects/batch_open_issues_count_service_spec.rb +++ b/spec/services/projects/batch_open_issues_count_service_spec.rb @@ -8,7 +8,7 @@ describe Projects::BatchOpenIssuesCountService do let(:subject) { described_class.new([project_1, project_2]) } - context '#refresh_cache', :use_clean_rails_memory_store_caching do + describe '#refresh_cache', :use_clean_rails_memory_store_caching do before do create(:issue, project: project_1) create(:issue, project: project_1, confidential: true) diff --git a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb index b0827f6a2ee..7c7e188a12d 100644 --- a/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb @@ -14,7 +14,7 @@ describe Projects::HashedStorage::MigrateAttachmentsService do let(:old_disk_path) { File.join(base_path(legacy_storage), upload.path) } let(:new_disk_path) { File.join(base_path(hashed_storage), upload.path) } - context '#execute' do + describe '#execute' do context 'when succeeds' do it 'moves attachments to hashed storage layout' do expect(File.file?(old_disk_path)).to be_truthy @@ -102,13 +102,13 @@ describe Projects::HashedStorage::MigrateAttachmentsService do end end - context '#old_disk_path' do + describe '#old_disk_path' do it 'returns old disk_path for project' do expect(service.old_disk_path).to eq(project.full_path) end end - context '#new_disk_path' do + describe '#new_disk_path' do it 'returns new disk_path for project' do service.execute @@ -116,7 +116,7 @@ describe Projects::HashedStorage::MigrateAttachmentsService do end end - context '#target_path_discardable?' do + describe '#target_path_discardable?' do it 'returns true when it include only items on the discardable list' do hashed_attachments_path = File.join(base_path(hashed_storage)) Projects::HashedStorage::MigrateAttachmentsService::DISCARDABLE_PATHS.each do |path_fragment| diff --git a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb index 98b343371df..54695e6e48f 100644 --- a/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb +++ b/spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb @@ -14,7 +14,7 @@ describe Projects::HashedStorage::RollbackAttachmentsService do let(:old_disk_path) { File.join(base_path(hashed_storage), upload.path) } let(:new_disk_path) { File.join(base_path(legacy_storage), upload.path) } - context '#execute' do + describe '#execute' do context 'when succeeds' do it 'moves attachments to legacy storage layout' do expect(File.file?(old_disk_path)).to be_truthy @@ -86,13 +86,13 @@ describe Projects::HashedStorage::RollbackAttachmentsService do end end - context '#old_disk_path' do + describe '#old_disk_path' do it 'returns old disk_path for project' do expect(service.old_disk_path).to eq(project.disk_path) end end - context '#new_disk_path' do + describe '#new_disk_path' do it 'returns new disk_path for project' do service.execute diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 60804a8dba6..98a27a71c26 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -75,7 +75,7 @@ describe Projects::HousekeepingService do # At push 200 expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :the_lease_key, :the_uuid) - .exactly(1).times + .once # At push 50, 100, 150 expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :full_repack, :the_lease_key, :the_uuid) .exactly(3).times diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb index 04f1353c499..c1d49befeb9 100644 --- a/spec/services/projects/open_issues_count_service_spec.rb +++ b/spec/services/projects/open_issues_count_service_spec.rb @@ -57,7 +57,7 @@ describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_caching end end - context '#refresh_cache' do + describe '#refresh_cache' do before do create(:issue, :opened, project: project) create(:issue, :opened, project: project) diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 81012eba06d..7db94d4a4ac 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -1884,7 +1884,7 @@ describe QuickActions::InterpretService do end end - context "#commands_executed_count" do + describe "#commands_executed_count" do it 'counts commands executed' do content = "/close and \n/assign me and \n/title new title" diff --git a/spec/support/shared_examples/services/boards/issues_list_service_shared_examples.rb b/spec/support/shared_examples/services/boards/issues_list_service_shared_examples.rb index 56f9e811799..ec1c58e5b67 100644 --- a/spec/support/shared_examples/services/boards/issues_list_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/issues_list_service_shared_examples.rb @@ -9,7 +9,7 @@ RSpec.shared_examples 'issues list service' do described_class.new(parent, user, params).execute end - context '#metadata' do + describe '#metadata' do it 'returns issues count for list' do params = { board_id: board.id, id: list1.id } diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb index 3c3e5eea838..92ccc195a9a 100644 --- a/spec/tasks/gitlab/cleanup_rake_spec.rb +++ b/spec/tasks/gitlab/cleanup_rake_spec.rb @@ -16,10 +16,10 @@ describe 'gitlab:cleanup rake tasks' do let!(:logger) { double(:logger) } before do - expect(main_object).to receive(:logger).and_return(logger).at_least(1).times + expect(main_object).to receive(:logger).and_return(logger).at_least(:once) - allow(logger).to receive(:info).at_least(1).times - allow(logger).to receive(:debug).at_least(1).times + allow(logger).to receive(:info).at_least(:once) + allow(logger).to receive(:debug).at_least(:once) end context 'with a fixable orphaned project upload file' do diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb index 4329171f0be..80efdb88585 100644 --- a/spec/uploaders/gitlab_uploader_spec.rb +++ b/spec/uploaders/gitlab_uploader_spec.rb @@ -59,7 +59,7 @@ describe GitlabUploader do describe '#cache!' do it 'moves the file from the working directory to the cache directory' do # One to get the work dir, the other to remove it - expect(subject).to receive(:workfile_path).exactly(2).times.and_call_original + expect(subject).to receive(:workfile_path).twice.and_call_original # Test https://github.com/carrierwavesubject/carrierwave/blob/v1.0.0/lib/carrierwave/sanitized_file.rb#L200 expect(FileUtils).to receive(:mv).with(anything, /^#{subject.work_dir}/).and_call_original expect(FileUtils).to receive(:mv).with(/^#{subject.work_dir}/, /#{subject.cache_dir}/).and_call_original diff --git a/spec/uploaders/namespace_file_uploader_spec.rb b/spec/uploaders/namespace_file_uploader_spec.rb index aa98b3e2828..bc8d6a33e85 100644 --- a/spec/uploaders/namespace_file_uploader_spec.rb +++ b/spec/uploaders/namespace_file_uploader_spec.rb @@ -37,7 +37,7 @@ describe NamespaceFileUploader do end end - context '.base_dir' do + describe '.base_dir' do it 'returns local storage base_dir without store param' do expect(described_class.base_dir(group)).to eq("uploads/-/system/namespace/#{group.id}") end diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_gpg_signature_worker_spec.rb index ae09b4b77f1..2504a6474db 100644 --- a/spec/workers/create_gpg_signature_worker_spec.rb +++ b/spec/workers/create_gpg_signature_worker_spec.rb @@ -31,7 +31,7 @@ describe CreateGpgSignatureWorker do allow(Gitlab::Gpg::Commit).to receive(:new).and_return(gpg_commit) allow(Gitlab::Gpg::Commit).to receive(:new).with(commits.first).and_raise(StandardError) - expect(gpg_commit).to receive(:signature).exactly(2).times + expect(gpg_commit).to receive(:signature).twice subject end |