summaryrefslogtreecommitdiff
path: root/spec/lib
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-20 23:50:22 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-20 23:50:22 +0000
commit9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch)
tree70467ae3692a0e35e5ea56bcb803eb512a10bedb /spec/lib
parent4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff)
downloadgitlab-ce-9dc93a4519d9d5d7be48ff274127136236a3adb3.tar.gz
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'spec/lib')
-rw-r--r--spec/lib/api/entities/clusters/agent_spec.rb16
-rw-r--r--spec/lib/api/entities/design_management/design_spec.rb1
-rw-r--r--spec/lib/api/entities/merge_request_changes_spec.rb1
-rw-r--r--spec/lib/api/entities/project_import_failed_relation_spec.rb2
-rw-r--r--spec/lib/api/entities/release_spec.rb1
-rw-r--r--spec/lib/api/helpers/authentication_spec.rb15
-rw-r--r--spec/lib/api/helpers/caching_spec.rb139
-rw-r--r--spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb10
-rw-r--r--spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb3
-rw-r--r--spec/lib/api/helpers/variables_helpers_spec.rb43
-rw-r--r--spec/lib/api/helpers_spec.rb109
-rw-r--r--spec/lib/atlassian/jira_connect/serializers/pull_request_entity_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_trailers_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/gollum_tags_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb3
-rw-r--r--spec/lib/banzai/filter/math_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/abstract_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/alert_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/alert_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/commit_range_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/commit_range_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/commit_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/commit_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/design_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/design_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/external_issue_reference_filter_spec.rb)3
-rw-r--r--spec/lib/banzai/filter/references/feature_flag_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/feature_flag_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/issue_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/issue_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/label_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/label_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/merge_request_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/milestone_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/project_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/project_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/reference_filter_spec.rb (renamed from spec/lib/banzai/filter/reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/snippet_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/references/user_reference_filter_spec.rb (renamed from spec/lib/banzai/filter/user_reference_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/filter/suggestion_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/upload_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb9
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb3
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb25
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_redactor_spec.rb2
-rw-r--r--spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb31
-rw-r--r--spec/lib/bulk_imports/common/transformers/user_reference_transformer_spec.rb28
-rw-r--r--spec/lib/bulk_imports/groups/extractors/subgroups_extractor_spec.rb3
-rw-r--r--spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb6
-rw-r--r--spec/lib/bulk_imports/groups/graphql/get_labels_query_spec.rb4
-rw-r--r--spec/lib/bulk_imports/groups/graphql/get_members_query_spec.rb4
-rw-r--r--spec/lib/bulk_imports/groups/graphql/get_milestones_query_spec.rb4
-rw-r--r--spec/lib/bulk_imports/groups/loaders/group_loader_spec.rb11
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/badges_pipeline_spec.rb116
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/entity_finisher_spec.rb37
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb14
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/labels_pipeline_spec.rb87
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/members_pipeline_spec.rb14
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/milestones_pipeline_spec.rb90
-rw-r--r--spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb30
-rw-r--r--spec/lib/bulk_imports/groups/rest/get_badges_query_spec.rb22
-rw-r--r--spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb23
-rw-r--r--spec/lib/bulk_imports/groups/transformers/member_attributes_transformer_spec.rb3
-rw-r--r--spec/lib/bulk_imports/importers/group_importer_spec.rb57
-rw-r--r--spec/lib/bulk_imports/pipeline/context_spec.rb45
-rw-r--r--spec/lib/bulk_imports/pipeline/extracted_data_spec.rb2
-rw-r--r--spec/lib/bulk_imports/pipeline/runner_spec.rb65
-rw-r--r--spec/lib/bulk_imports/pipeline_spec.rb10
-rw-r--r--spec/lib/constraints/admin_constrainer_spec.rb6
-rw-r--r--spec/lib/feature_spec.rb92
-rw-r--r--spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb30
-rw-r--r--spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb70
-rw-r--r--spec/lib/gitlab/alert_management/alert_status_counts_spec.rb1
-rw-r--r--spec/lib/gitlab/alert_management/payload/base_spec.rb1
-rw-r--r--spec/lib/gitlab/alert_management/payload/generic_spec.rb1
-rw-r--r--spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb2
-rw-r--r--spec/lib/gitlab/alert_management/payload/prometheus_spec.rb1
-rw-r--r--spec/lib/gitlab/alert_management/payload_spec.rb1
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb1
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb44
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb2
-rw-r--r--spec/lib/gitlab/analytics/unique_visits_spec.rb22
-rw-r--r--spec/lib/gitlab/application_context_spec.rb16
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb6
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/otp/strategies/devise_spec.rb1
-rw-r--r--spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb1
-rw-r--r--spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb1
-rw-r--r--spec/lib/gitlab/auth_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/migrate_pages_to_zip_storage_spec.rb43
-rw-r--r--spec/lib/gitlab/bullet/exclusions_spec.rb155
-rw-r--r--spec/lib/gitlab/bullet_spec.rb51
-rw-r--r--spec/lib/gitlab/changelog/config_spec.rb4
-rw-r--r--spec/lib/gitlab/checks/project_created_spec.rb1
-rw-r--r--spec/lib/gitlab/checks/project_moved_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/ansi2json/style_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/lint_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb61
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb13
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/pipeline/process_spec.rb31
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb159
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb201
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb139
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb109
-rw-r--r--spec/lib/gitlab/ci/reports/codequality_reports_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/reports/test_failure_history_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/runner_instructions_spec.rb116
-rw-r--r--spec/lib/gitlab/ci/status/build/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/composite_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/variables/helpers_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/yaml_processor/result_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb139
-rw-r--r--spec/lib/gitlab/composer/version_index_spec.rb5
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb6
-rw-r--r--spec/lib/gitlab/crypto_helper_spec.rb58
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb3
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb1
-rw-r--r--spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb31
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb198
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_spec.rb51
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb94
-rw-r--r--spec/lib/gitlab/database/background_migration/scheduler_spec.rb182
-rw-r--r--spec/lib/gitlab/database/batch_count_spec.rb76
-rw-r--r--spec/lib/gitlab/database/loose_index_scan_distinct_count_spec.rb71
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb241
-rw-r--r--spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb28
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb66
-rw-r--r--spec/lib/gitlab/database/pg_class_spec.rb37
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb3
-rw-r--r--spec/lib/gitlab/database/unidirectional_copy_trigger_spec.rb191
-rw-r--r--spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb2
-rw-r--r--spec/lib/gitlab/database_spec.rb4
-rw-r--r--spec/lib/gitlab/diff/char_diff_spec.rb6
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb26
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb20
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb74
-rw-r--r--spec/lib/gitlab/diff/line_spec.rb35
-rw-r--r--spec/lib/gitlab/diff/lines_unfolder_spec.rb18
-rw-r--r--spec/lib/gitlab/diff/suggestions_parser_spec.rb3
-rw-r--r--spec/lib/gitlab/downtime_check/message_spec.rb41
-rw-r--r--spec/lib/gitlab/downtime_check_spec.rb116
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb8
-rw-r--r--spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb93
-rw-r--r--spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb90
-rw-r--r--spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb96
-rw-r--r--spec/lib/gitlab/error_tracking_spec.rb108
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb1
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb86
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb40
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb13
-rw-r--r--spec/lib/gitlab/gitaly_client/blob_service_spec.rb43
-rw-r--r--spec/lib/gitlab/gitaly_client/call_spec.rb13
-rw-r--r--spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb4
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb26
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb17
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/github_import/milestone_finder_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb253
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb80
-rw-r--r--spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb64
-rw-r--r--spec/lib/gitlab/graphql/batch_key_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/deprecation_spec.rb213
-rw-r--r--spec/lib/gitlab/graphql/docs/renderer_spec.rb80
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/markdown_field_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/negatable_arguments_spec.rb45
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb1
-rw-r--r--spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb47
-rw-r--r--spec/lib/gitlab/highlight_spec.rb15
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/hook_data/release_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/hook_data/user_builder_spec.rb90
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb125
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml5
-rw-r--r--spec/lib/gitlab/import_export/design_repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/project/export_task_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project/tree_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb1
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb47
-rw-r--r--spec/lib/gitlab/jira_import/base_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/jira_import/handle_labels_service_spec.rb1
-rw-r--r--spec/lib/gitlab/jira_import_spec.rb1
-rw-r--r--spec/lib/gitlab/json_spec.rb60
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb26
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/extension_spec.rb3
-rw-r--r--spec/lib/gitlab/markdown_cache/redis/store_spec.rb4
-rw-r--r--spec/lib/gitlab/marker_range_spec.rb33
-rw-r--r--spec/lib/gitlab/metrics/background_transaction_spec.rb53
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb171
-rw-r--r--spec/lib/gitlab/metrics/subscribers/external_http_spec.rb57
-rw-r--r--spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb94
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb97
-rw-r--r--spec/lib/gitlab/pages/settings_spec.rb42
-rw-r--r--spec/lib/gitlab/pages/stores/local_store_spec.rb25
-rw-r--r--spec/lib/gitlab/pages_transfer_spec.rb4
-rw-r--r--spec/lib/gitlab/pagination/keyset/order_spec.rb55
-rw-r--r--spec/lib/gitlab/pagination/offset_header_builder_with_controller_spec.rb54
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb1
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb1
-rw-r--r--spec/lib/gitlab/profiler_spec.rb9
-rw-r--r--spec/lib/gitlab/prometheus/adapter_spec.rb16
-rw-r--r--spec/lib/gitlab/query_limiting/transaction_spec.rb10
-rw-r--r--spec/lib/gitlab/query_limiting_spec.rb82
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb8
-rw-r--r--spec/lib/gitlab/regex_spec.rb9
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb31
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb23
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb2
-rw-r--r--spec/lib/gitlab/search_context/builder_spec.rb29
-rw-r--r--spec/lib/gitlab/sidekiq_cluster/cli_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb84
-rw-r--r--spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb129
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb97
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb4
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb4
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb153
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb101
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb318
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware_spec.rb6
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb1
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb1
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb13
-rw-r--r--spec/lib/gitlab/slash_commands/run_spec.rb20
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb1
-rw-r--r--spec/lib/gitlab/sourcegraph_spec.rb1
-rw-r--r--spec/lib/gitlab/sql/cte_spec.rb20
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb13
-rw-r--r--spec/lib/gitlab/subscription_portal_spec.rb56
-rw-r--r--spec/lib/gitlab/template/finders/repo_template_finders_spec.rb1
-rw-r--r--spec/lib/gitlab/tracking/destinations/snowplow_spec.rb23
-rw-r--r--spec/lib/gitlab/tracking/standard_context_spec.rb12
-rw-r--r--spec/lib/gitlab/tracking_spec.rb32
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb1
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb18
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb85
-rw-r--r--spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb1
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb51
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb40
-rw-r--r--spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb95
-rw-r--r--spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb36
-rw-r--r--spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb18
-rw-r--r--spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb20
-rw-r--r--spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb55
-rw-r--r--spec/lib/gitlab/usage_data_queries_spec.rb32
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb42
-rw-r--r--spec/lib/gitlab/utils/lazy_attributes_spec.rb6
-rw-r--r--spec/lib/gitlab/utils/usage_data_spec.rb19
-rw-r--r--spec/lib/gitlab/utils_spec.rb8
-rw-r--r--spec/lib/gitlab/web_ide/config/entry/global_spec.rb1
-rw-r--r--spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb5
-rw-r--r--spec/lib/gitlab/word_diff/chunk_collection_spec.rb23
-rw-r--r--spec/lib/gitlab/word_diff/parser_spec.rb40
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb1
-rw-r--r--spec/lib/gitlab_spec.rb118
-rw-r--r--spec/lib/kramdown/kramdown_spec.rb38
-rw-r--r--spec/lib/marginalia_spec.rb52
-rw-r--r--spec/lib/mattermost/command_spec.rb2
-rw-r--r--spec/lib/mattermost/session_spec.rb2
-rw-r--r--spec/lib/mattermost/team_spec.rb2
-rw-r--r--spec/lib/peek/views/active_record_spec.rb27
-rw-r--r--spec/lib/peek/views/external_http_spec.rb33
-rw-r--r--spec/lib/quality/test_level_spec.rb225
-rw-r--r--spec/lib/rouge/formatters/html_gitlab_spec.rb40
293 files changed, 6490 insertions, 3193 deletions
diff --git a/spec/lib/api/entities/clusters/agent_spec.rb b/spec/lib/api/entities/clusters/agent_spec.rb
new file mode 100644
index 00000000000..04f7ec28407
--- /dev/null
+++ b/spec/lib/api/entities/clusters/agent_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Entities::Clusters::Agent do
+ let_it_be(:cluster_agent) { create(:cluster_agent) }
+
+ subject { described_class.new(cluster_agent).as_json }
+
+ it 'includes basic fields' do
+ expect(subject).to include(
+ id: cluster_agent.id,
+ config_project: a_hash_including(id: cluster_agent.project_id)
+ )
+ end
+end
diff --git a/spec/lib/api/entities/design_management/design_spec.rb b/spec/lib/api/entities/design_management/design_spec.rb
index fe449e3e9bc..fe2b1dadfa7 100644
--- a/spec/lib/api/entities/design_management/design_spec.rb
+++ b/spec/lib/api/entities/design_management/design_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe API::Entities::DesignManagement::Design do
let_it_be(:design) { create(:design) }
+
let(:entity) { described_class.new(design, request: double) }
subject { entity.as_json }
diff --git a/spec/lib/api/entities/merge_request_changes_spec.rb b/spec/lib/api/entities/merge_request_changes_spec.rb
index f46d8981328..29bfd1da6cc 100644
--- a/spec/lib/api/entities/merge_request_changes_spec.rb
+++ b/spec/lib/api/entities/merge_request_changes_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe ::API::Entities::MergeRequestChanges do
let_it_be(:user) { create(:user) }
let_it_be(:merge_request) { create(:merge_request) }
+
let(:entity) { described_class.new(merge_request, current_user: user) }
subject(:basic_entity) { entity.as_json }
diff --git a/spec/lib/api/entities/project_import_failed_relation_spec.rb b/spec/lib/api/entities/project_import_failed_relation_spec.rb
index 51a684c4564..d3c24f6fce3 100644
--- a/spec/lib/api/entities/project_import_failed_relation_spec.rb
+++ b/spec/lib/api/entities/project_import_failed_relation_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe API::Entities::ProjectImportFailedRelation do
id: import_failure.id,
created_at: import_failure.created_at,
exception_class: import_failure.exception_class,
- exception_message: import_failure.exception_message,
+ exception_message: nil,
relation_name: import_failure.relation_key,
source: import_failure.source
)
diff --git a/spec/lib/api/entities/release_spec.rb b/spec/lib/api/entities/release_spec.rb
index d57c283c1f4..06062634015 100644
--- a/spec/lib/api/entities/release_spec.rb
+++ b/spec/lib/api/entities/release_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe API::Entities::Release do
let_it_be(:project) { create(:project) }
+
let(:release) { create(:release, project: project) }
let(:evidence) { release.evidences.first }
let(:user) { create(:user) }
diff --git a/spec/lib/api/helpers/authentication_spec.rb b/spec/lib/api/helpers/authentication_spec.rb
index 461b0d2f6f9..eea5c10d4f8 100644
--- a/spec/lib/api/helpers/authentication_spec.rb
+++ b/spec/lib/api/helpers/authentication_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe API::Helpers::Authentication do
let_it_be(:project, reload: true) { create(:project, :public) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:ci_build) { create(:ci_build, :running, user: user) }
describe 'class methods' do
subject { Class.new.include(described_class::ClassMethods).new }
@@ -176,6 +177,20 @@ RSpec.describe API::Helpers::Authentication do
end
end
+ describe '#ci_build_from_namespace_inheritable' do
+ subject { object.ci_build_from_namespace_inheritable }
+
+ it 'returns #token_from_namespace_inheritable if it is a ci build' do
+ expect(object).to receive(:token_from_namespace_inheritable).and_return(ci_build)
+ expect(subject).to be(ci_build)
+ end
+
+ it 'returns nil if #token_from_namespace_inheritable is not a ci build' do
+ expect(object).to receive(:token_from_namespace_inheritable).and_return(personal_access_token)
+ expect(subject).to eq(nil)
+ end
+ end
+
describe '#user_from_namespace_inheritable' do
subject { object.user_from_namespace_inheritable }
diff --git a/spec/lib/api/helpers/caching_spec.rb b/spec/lib/api/helpers/caching_spec.rb
new file mode 100644
index 00000000000..a8cd061e123
--- /dev/null
+++ b/spec/lib/api/helpers/caching_spec.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe API::Helpers::Caching do
+ subject(:instance) { Class.new.include(described_class).new }
+
+ describe "#present_cached" do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:presenter) { API::Entities::Todo }
+
+ let(:kwargs) do
+ {
+ with: presenter,
+ project: project
+ }
+ end
+
+ subject do
+ instance.present_cached(presentable, **kwargs)
+ end
+
+ before do
+ # We have to stub #body as it's a Grape method
+ # unavailable in the module by itself
+ expect(instance).to receive(:body) do |data|
+ data
+ end
+
+ allow(instance).to receive(:current_user) { user }
+ end
+
+ context "single object" do
+ let_it_be(:presentable) { create(:todo, project: project) }
+
+ it { is_expected.to be_a(Gitlab::Json::PrecompiledJson) }
+
+ it "uses the presenter" do
+ expect(presenter).to receive(:represent).with(presentable, project: project)
+
+ subject
+ end
+
+ it "is valid JSON" do
+ parsed = Gitlab::Json.parse(subject.to_s)
+
+ expect(parsed).to be_a(Hash)
+ expect(parsed["id"]).to eq(presentable.id)
+ end
+
+ it "fetches from the cache" do
+ expect(instance.cache).to receive(:fetch).with("#{presentable.cache_key}:#{user.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once
+
+ subject
+ end
+
+ context "when a cache context is supplied" do
+ before do
+ kwargs[:cache_context] = -> (todo) { todo.project.cache_key }
+ end
+
+ it "uses the context to augment the cache key" do
+ expect(instance.cache).to receive(:fetch).with("#{presentable.cache_key}:#{project.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once
+
+ subject
+ end
+ end
+
+ context "when expires_in is supplied" do
+ it "sets the expiry when accessing the cache" do
+ kwargs[:expires_in] = 7.days
+
+ expect(instance.cache).to receive(:fetch).with("#{presentable.cache_key}:#{user.cache_key}", expires_in: 7.days).once
+
+ subject
+ end
+ end
+ end
+
+ context "for a collection of objects" do
+ let_it_be(:presentable) { Array.new(5).map { create(:todo, project: project) } }
+
+ it { is_expected.to be_an(Gitlab::Json::PrecompiledJson) }
+
+ it "uses the presenter" do
+ presentable.each do |todo|
+ expect(presenter).to receive(:represent).with(todo, project: project)
+ end
+
+ subject
+ end
+
+ it "is valid JSON" do
+ parsed = Gitlab::Json.parse(subject.to_s)
+
+ expect(parsed).to be_an(Array)
+
+ presentable.each_with_index do |todo, i|
+ expect(parsed[i]["id"]).to eq(todo.id)
+ end
+ end
+
+ it "fetches from the cache" do
+ keys = presentable.map { |todo| "#{todo.cache_key}:#{user.cache_key}" }
+
+ expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: described_class::DEFAULT_EXPIRY).once.and_call_original
+
+ subject
+ end
+
+ context "when a cache context is supplied" do
+ before do
+ kwargs[:cache_context] = -> (todo) { todo.project.cache_key }
+ end
+
+ it "uses the context to augment the cache key" do
+ keys = presentable.map { |todo| "#{todo.cache_key}:#{project.cache_key}" }
+
+ expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: described_class::DEFAULT_EXPIRY).once.and_call_original
+
+ subject
+ end
+ end
+
+ context "expires_in is supplied" do
+ it "sets the expiry when accessing the cache" do
+ keys = presentable.map { |todo| "#{todo.cache_key}:#{user.cache_key}" }
+ kwargs[:expires_in] = 7.days
+
+ expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: 7.days).once.and_call_original
+
+ subject
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
index 6d06fc3618d..99b52236771 100644
--- a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
+++ b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb
@@ -12,6 +12,10 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
subject { helper.redirect_registry_request(forward_to_registry, package_type, options) { helper.fallback } }
+ before do
+ allow(helper).to receive(:options).and_return(for: API::NpmInstancePackages)
+ end
+
shared_examples 'executing fallback' do
it 'redirects to package registry' do
expect(helper).to receive(:registry_url).never
@@ -23,13 +27,14 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
end
shared_examples 'executing redirect' do
- it 'redirects to package registry' do
- expect(helper).to receive(:track_event).with('npm_request_forward').once
+ it 'redirects to package registry', :snowplow do
expect(helper).to receive(:registry_url).once
expect(helper).to receive(:redirect).once
expect(helper).to receive(:fallback).never
subject
+
+ expect_snowplow_event(category: 'API::NpmInstancePackages', action: 'npm_request_forward')
end
end
@@ -64,7 +69,6 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do
let(:package_type) { pkg_type }
it 'raises an error' do
- allow(helper).to receive(:track_event)
expect { subject }.to raise_error(ArgumentError, "Can't build registry_url for package_type #{package_type}")
end
end
diff --git a/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
index 3c40859da21..e4c5002aa68 100644
--- a/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
+++ b/spec/lib/api/helpers/packages_manager_clients_helpers_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
let_it_be(:personal_access_token) { create(:personal_access_token) }
let_it_be(:username) { personal_access_token.user.username }
let_it_be(:helper) { Class.new.include(described_class).new }
+
let(:password) { personal_access_token.token }
let(:env) do
@@ -50,6 +51,7 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
describe '#find_job_from_http_basic_auth' do
let_it_be(:user) { personal_access_token.user }
+
let(:job) { create(:ci_build, user: user, status: :running) }
let(:password) { job.token }
@@ -74,6 +76,7 @@ RSpec.describe API::Helpers::PackagesManagerClientsHelpers do
describe '#find_deploy_token_from_http_basic_auth' do
let_it_be(:deploy_token) { create(:deploy_token) }
+
let(:token) { deploy_token.token }
let(:username) { deploy_token.username }
let(:password) { token }
diff --git a/spec/lib/api/helpers/variables_helpers_spec.rb b/spec/lib/api/helpers/variables_helpers_spec.rb
new file mode 100644
index 00000000000..de6bebaa827
--- /dev/null
+++ b/spec/lib/api/helpers/variables_helpers_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Helpers::VariablesHelpers do
+ let(:helper) { Class.new.include(described_class).new }
+
+ describe '#filter_variable_parameters' do
+ let(:project) { double }
+ let(:params) { double }
+
+ subject { helper.filter_variable_parameters(project, params) }
+
+ it 'returns unmodified params (overridden in EE)' do
+ expect(subject).to eq(params)
+ end
+ end
+
+ describe '#find_variable' do
+ let(:owner) { double }
+ let(:params) { double }
+ let(:variables) { [double] }
+
+ subject { helper.find_variable(owner, params) }
+
+ before do
+ expect(Ci::VariablesFinder).to receive(:new).with(owner, params)
+ .and_return(double(execute: variables))
+ end
+
+ it { is_expected.to eq(variables.first) }
+
+ context 'there are multiple variables with the supplied key' do
+ let(:variables) { [double, double] }
+
+ it 'raises a conflict!' do
+ expect(helper).to receive(:conflict!).with(/There are multiple variables with provided parameters/)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index bdf04fafaae..15b22fcf25e 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -47,6 +47,58 @@ RSpec.describe API::Helpers do
end
end
+ describe '#find_project!' do
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:user) { create(:user) }
+
+ shared_examples 'private project without access' do
+ before do
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value('private'))
+ allow(subject).to receive(:authenticate_non_public?).and_return(false)
+ end
+
+ it 'returns not found' do
+ expect(subject).to receive(:not_found!)
+
+ subject.find_project!(project.id)
+ end
+ end
+
+ context 'when user is authenticated' do
+ before do
+ subject.instance_variable_set(:@current_user, user)
+ subject.instance_variable_set(:@initial_current_user, user)
+ end
+
+ context 'public project' do
+ it 'returns requested project' do
+ expect(subject.find_project!(project.id)).to eq(project)
+ end
+ end
+
+ context 'private project' do
+ it_behaves_like 'private project without access'
+ end
+ end
+
+ context 'when user is not authenticated' do
+ before do
+ subject.instance_variable_set(:@current_user, nil)
+ subject.instance_variable_set(:@initial_current_user, nil)
+ end
+
+ context 'public project' do
+ it 'returns requested project' do
+ expect(subject.find_project!(project.id)).to eq(project)
+ end
+ end
+
+ context 'private project' do
+ it_behaves_like 'private project without access'
+ end
+ end
+ end
+
describe '#find_namespace' do
let(:namespace) { create(:namespace) }
@@ -175,64 +227,27 @@ RSpec.describe API::Helpers do
end
end
- describe '#track_event' do
- it "creates a gitlab tracking event", :snowplow do
- subject.track_event('my_event', category: 'foo')
-
- expect_snowplow_event(category: 'foo', action: 'my_event')
- end
-
- it "logs an exception" do
- expect(Gitlab::AppLogger).to receive(:warn).with(/Tracking event failed/)
-
- subject.track_event('my_event', category: nil)
- end
- end
-
describe '#increment_unique_values' do
let(:value) { '9f302fea-f828-4ca9-aef4-e10bd723c0b3' }
let(:event_name) { 'g_compliance_dashboard' }
let(:unknown_event) { 'unknown' }
- let(:feature) { "usage_data_#{event_name}" }
-
- before do
- skip_feature_flags_yaml_validation
- end
- context 'with feature enabled' do
- before do
- stub_feature_flags(feature => true)
- end
+ it 'tracks redis hll event' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(event_name, values: value)
- it 'tracks redis hll event' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(event_name, values: value)
-
- subject.increment_unique_values(event_name, value)
- end
-
- it 'logs an exception for unknown event' do
- expect(Gitlab::AppLogger).to receive(:warn).with("Redis tracking event failed for event: #{unknown_event}, message: Unknown event #{unknown_event}")
-
- subject.increment_unique_values(unknown_event, value)
- end
+ subject.increment_unique_values(event_name, value)
+ end
- it 'does not track event for nil values' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+ it 'logs an exception for unknown event' do
+ expect(Gitlab::AppLogger).to receive(:warn).with("Redis tracking event failed for event: #{unknown_event}, message: Unknown event #{unknown_event}")
- subject.increment_unique_values(unknown_event, nil)
- end
+ subject.increment_unique_values(unknown_event, value)
end
- context 'with feature disabled' do
- before do
- stub_feature_flags(feature => false)
- end
-
- it 'does not track event' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+ it 'does not track event for nil values' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
- subject.increment_unique_values(event_name, value)
- end
+ subject.increment_unique_values(unknown_event, nil)
end
end
diff --git a/spec/lib/atlassian/jira_connect/serializers/pull_request_entity_spec.rb b/spec/lib/atlassian/jira_connect/serializers/pull_request_entity_spec.rb
index 872ba1ab43d..6399fc9053b 100644
--- a/spec/lib/atlassian/jira_connect/serializers/pull_request_entity_spec.rb
+++ b/spec/lib/atlassian/jira_connect/serializers/pull_request_entity_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Atlassian::JiraConnect::Serializers::PullRequestEntity do
end
context 'with user_notes_count option' do
- let(:user_notes_count) { merge_requests.map { |merge_request| [merge_request.id, 1] }.to_h }
+ let(:user_notes_count) { merge_requests.to_h { |merge_request| [merge_request.id, 1] } }
subject { described_class.represent(merge_requests, user_notes_count: user_notes_count).as_json }
diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
index 03a6cc34962..f7cb6b92b48 100644
--- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
@@ -139,6 +139,12 @@ RSpec.describe Banzai::Filter::CommitTrailersFilter do
end
context "structure" do
+ it 'starts with two newlines to separate with actual commit message' do
+ doc = filter(commit_message_html)
+
+ expect(doc.xpath('pre').text).to start_with("\n\n")
+ end
+
it 'preserves the commit trailer structure' do
doc = filter(commit_message_html)
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
index ec17bb26346..23626576c0c 100644
--- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -16,18 +16,14 @@ RSpec.describe Banzai::Filter::GollumTagsFilter do
context 'linking internal images' do
it 'creates img tag if image exists' do
- gollum_file_double = double('Gollum::File',
- mime_type: 'image/jpeg',
- name: 'images/image.jpg',
- path: 'images/image.jpg',
- raw_data: '')
- wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double)
+ blob = double(mime_type: 'image/jpeg', name: 'images/image.jpg', path: 'images/image.jpg', data: '')
+ wiki_file = Gitlab::Git::WikiFile.new(blob)
expect(wiki).to receive(:find_file).with('images/image.jpg', load_content: false).and_return(wiki_file)
tag = '[[images/image.jpg]]'
doc = filter("See #{tag}", wiki: wiki)
- expect(doc.at_css('img')['data-src']).to eq "#{wiki.wiki_base_path}/images/image.jpg"
+ expect(doc.at_css('img')['src']).to eq 'images/image.jpg'
end
it 'does not creates img tag if image does not exist' do
@@ -45,7 +41,7 @@ RSpec.describe Banzai::Filter::GollumTagsFilter do
tag = '[[http://example.com/image.jpg]]'
doc = filter("See #{tag}", wiki: wiki)
- expect(doc.at_css('img')['data-src']).to eq "http://example.com/image.jpg"
+ expect(doc.at_css('img')['src']).to eq "http://example.com/image.jpg"
end
it 'does not creates img tag for invalid URL' do
diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
index 3c736b46131..9ccea1cc3e9 100644
--- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter do
include FilterSpecHelper
let_it_be(:project) { create(:project) }
+
let(:url) { urls.metrics_dashboard_project_environment_url(project, 1, embedded: true) }
let(:input) { %(<a href="#{url}">example</a>) }
let(:doc) { filter(input) }
@@ -38,6 +39,7 @@ RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter do
context 'for a cluster metric embed' do
let_it_be(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [project]) }
+
let(:params) { [project.namespace.path, project.path, cluster.id] }
let(:query_params) { { group: 'Cluster Health', title: 'CPU Usage', y_label: 'CPU (cores)' } }
let(:url) { urls.metrics_dashboard_namespace_project_cluster_url(*params, **query_params, format: :json) }
@@ -84,6 +86,7 @@ RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter do
context 'for an alert embed' do
let_it_be(:alert) { create(:prometheus_alert, project: project) }
+
let(:url) do
urls.metrics_dashboard_project_prometheus_alert_url(
project,
diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb
index 9f6688f4f7d..6d22fa3a001 100644
--- a/spec/lib/banzai/filter/math_filter_spec.rb
+++ b/spec/lib/banzai/filter/math_filter_spec.rb
@@ -91,35 +91,35 @@ RSpec.describe Banzai::Filter::MathFilter do
# Display math
it 'adds data-math-style display attribute to display math' do
- doc = filter('<pre class="code highlight js-syntax-highlight math" v-pre="true"><code>2+2</code></pre>')
+ doc = filter('<pre class="code highlight js-syntax-highlight language-math" v-pre="true"><code>2+2</code></pre>')
pre = doc.xpath('descendant-or-self::pre').first
expect(pre['data-math-style']).to eq 'display'
end
it 'adds js-render-math class to display math' do
- doc = filter('<pre class="code highlight js-syntax-highlight math" v-pre="true"><code>2+2</code></pre>')
+ doc = filter('<pre class="code highlight js-syntax-highlight language-math" v-pre="true"><code>2+2</code></pre>')
pre = doc.xpath('descendant-or-self::pre').first
expect(pre[:class]).to include("js-render-math")
end
it 'ignores code blocks that are not math' do
- input = '<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>2+2</code></pre>'
+ input = '<pre class="code highlight js-syntax-highlight language-plaintext" v-pre="true"><code>2+2</code></pre>'
doc = filter(input)
expect(doc.to_s).to eq input
end
it 'requires the pre to contain both code and math' do
- input = '<pre class="highlight js-syntax-highlight plaintext math" v-pre="true"><code>2+2</code></pre>'
+ input = '<pre class="highlight js-syntax-highlight language-plaintext language-math" v-pre="true"><code>2+2</code></pre>'
doc = filter(input)
expect(doc.to_s).to eq input
end
it 'dollar signs around to display math' do
- doc = filter('$<pre class="code highlight js-syntax-highlight math" v-pre="true"><code>2+2</code></pre>$')
+ doc = filter('$<pre class="code highlight js-syntax-highlight language-math" v-pre="true"><code>2+2</code></pre>$')
before = doc.xpath('descendant-or-self::text()[1]').first
after = doc.xpath('descendant-or-self::text()[3]').first
diff --git a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb
index 797f1c8d52f..076c112ac87 100644
--- a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::AbstractReferenceFilter do
+RSpec.describe Banzai::Filter::References::AbstractReferenceFilter do
let_it_be(:project) { create(:project) }
let(:doc) { Nokogiri::HTML.fragment('') }
diff --git a/spec/lib/banzai/filter/alert_reference_filter_spec.rb b/spec/lib/banzai/filter/references/alert_reference_filter_spec.rb
index c57a8a7321c..7c6b0cac24b 100644
--- a/spec/lib/banzai/filter/alert_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/alert_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::AlertReferenceFilter do
+RSpec.describe Banzai::Filter::References::AlertReferenceFilter do
include FilterSpecHelper
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/references/commit_range_reference_filter_spec.rb
index f04d3212437..b235de06b30 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/commit_range_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::CommitRangeReferenceFilter do
+RSpec.describe Banzai::Filter::References::CommitRangeReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/references/commit_reference_filter_spec.rb
index 925fd031d95..bee8e42d12e 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/commit_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::CommitReferenceFilter do
+RSpec.describe Banzai::Filter::References::CommitReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/lib/banzai/filter/design_reference_filter_spec.rb b/spec/lib/banzai/filter/references/design_reference_filter_spec.rb
index 847c398964a..52514ad17fc 100644
--- a/spec/lib/banzai/filter/design_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/design_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::DesignReferenceFilter do
+RSpec.describe Banzai::Filter::References::DesignReferenceFilter do
include FilterSpecHelper
include DesignManagementTestHelpers
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb
index 35ef2abfa63..3b274f98020 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::ExternalIssueReferenceFilter do
+RSpec.describe Banzai::Filter::References::ExternalIssueReferenceFilter do
include FilterSpecHelper
let_it_be_with_refind(:project) { create(:project) }
@@ -184,6 +184,7 @@ RSpec.describe Banzai::Filter::ExternalIssueReferenceFilter do
context "jira project" do
let_it_be(:service) { create(:jira_service, project: project) }
+
let(:reference) { issue.to_reference }
context "with right markdown" do
diff --git a/spec/lib/banzai/filter/feature_flag_reference_filter_spec.rb b/spec/lib/banzai/filter/references/feature_flag_reference_filter_spec.rb
index 2d7089853cf..c64b66f746e 100644
--- a/spec/lib/banzai/filter/feature_flag_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/feature_flag_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::FeatureFlagReferenceFilter do
+RSpec.describe Banzai::Filter::References::FeatureFlagReferenceFilter do
include FilterSpecHelper
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
index 4b8b575c1f0..b849355f6db 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::IssueReferenceFilter do
+RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
include FilterSpecHelper
include DesignManagementTestHelpers
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb
index 726ef8c57ab..db7dda96cad 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'html/pipeline'
-RSpec.describe Banzai::Filter::LabelReferenceFilter do
+RSpec.describe Banzai::Filter::References::LabelReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, name: 'sample-project') }
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb
index 811c2aca342..7a634b0b513 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::MergeRequestReferenceFilter do
+RSpec.describe Banzai::Filter::References::MergeRequestReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb
index 276fa7952be..dafdc71ce64 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::MilestoneReferenceFilter do
+RSpec.describe Banzai::Filter::References::MilestoneReferenceFilter do
include FilterSpecHelper
let_it_be(:parent_group) { create(:group, :public) }
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/references/project_reference_filter_spec.rb
index ac7a90a5893..7a77d57cd42 100644
--- a/spec/lib/banzai/filter/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/project_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::ProjectReferenceFilter do
+RSpec.describe Banzai::Filter::References::ProjectReferenceFilter do
include FilterSpecHelper
def invalidate_reference(reference)
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/references/reference_filter_spec.rb
index 2888965dbc4..4bcb41ef2a9 100644
--- a/spec/lib/banzai/filter/reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::ReferenceFilter do
+RSpec.describe Banzai::Filter::References::ReferenceFilter do
let(:project) { build_stubbed(:project) }
describe '#each_node' do
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb
index f23fbc5be88..32a706925ba 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::SnippetReferenceFilter do
+RSpec.describe Banzai::Filter::References::SnippetReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public) }
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/references/user_reference_filter_spec.rb
index b8baccf6658..e4703606b47 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/user_reference_filter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Banzai::Filter::UserReferenceFilter do
+RSpec.describe Banzai::Filter::References::UserReferenceFilter do
include FilterSpecHelper
def get_reference(user)
diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb
index 7d6092e21e9..d74bac4898e 100644
--- a/spec/lib/banzai/filter/suggestion_filter_spec.rb
+++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Banzai::Filter::SuggestionFilter do
include FilterSpecHelper
- let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion"><code>foo\n</code></pre>) }
+ let(:input) { %(<pre class="code highlight js-syntax-highlight language-suggestion"><code>foo\n</code></pre>) }
let(:default_context) do
{ suggestions_filter_enabled: true }
end
@@ -26,7 +26,7 @@ RSpec.describe Banzai::Filter::SuggestionFilter do
context 'multi-line suggestions' do
let(:data_attr) { Banzai::Filter::SyntaxHighlightFilter::LANG_PARAMS_ATTR }
- let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion" #{data_attr}="-3+2"><code>foo\n</code></pre>) }
+ let(:input) { %(<pre class="code highlight js-syntax-highlight language-suggestion" #{data_attr}="-3+2"><code>foo\n</code></pre>) }
it 'element has correct data-lang-params' do
doc = filter(input, default_context)
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 78f84ee44f7..16e30604c99 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "highlights as plaintext" do
result = filter('<pre><code>def fun end</code></pre>')
- expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre>')
end
include_examples "XSS prevention", ""
@@ -38,7 +38,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "highlights as that language" do
result = filter('<pre><code lang="ruby">def fun end</code></pre>')
- expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight language-ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>')
end
include_examples "XSS prevention", "ruby"
@@ -48,7 +48,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "highlights as plaintext" do
result = filter('<pre><code lang="gnuplot">This is a test</code></pre>')
- expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>')
end
include_examples "XSS prevention", "gnuplot"
@@ -63,7 +63,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "highlights as plaintext but with the correct language attribute and class" do
result = filter(%{<pre><code lang="#{lang}">This is a test</code></pre>})
- expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
+ expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
end
include_examples "XSS prevention", lang
@@ -75,7 +75,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "includes data-lang-params tag with extra information" do
result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}">This is a test</code></pre>})
- expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
+ expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
end
include_examples "XSS prevention", lang
@@ -93,7 +93,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do
it "delimits on the first appearance" do
result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}#{delimiter}more-things">This is a test</code></pre>})
- expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}#{delimiter}more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
+ expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}#{delimiter}more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
end
end
end
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index 0f8c773c68d..9ca499be665 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -35,6 +35,7 @@ RSpec.describe Banzai::Filter::UploadLinkFilter do
let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
+
let(:group) { nil }
let(:project_path) { project.full_path }
let(:only_path) { true }
@@ -114,6 +115,7 @@ RSpec.describe Banzai::Filter::UploadLinkFilter do
context 'to a group upload' do
let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
let_it_be(:group) { create(:group) }
+
let(:project) { nil }
let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index d1f6ee49260..b5b5349946b 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -22,6 +22,15 @@ RSpec.describe Banzai::Filter::WikiLinkFilter do
expect(filtered_link.attribute('href').value).to eq('/uploads/a.test')
end
+ describe 'when links point to the relative wiki path' do
+ it 'does not rewrite links' do
+ path = "#{wiki.wiki_base_path}/#{repository_upload_folder}/a.jpg"
+ filtered_link = filter("<a href='#{path}'>Link</a>", wiki: wiki, page_slug: 'home').children[0]
+
+ expect(filtered_link.attribute('href').value).to eq(path)
+ end
+ end
+
describe "when links point to the #{Wikis::CreateAttachmentService::ATTACHMENT_PATH} folder" do
context 'with an "a" html tag' do
it 'rewrites links' do
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 31047b9494a..e24177a7043 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Banzai::Pipeline::GfmPipeline do
issue = create(:issue, project: project)
markdown = "text #{issue.to_reference(project, full: true)}"
- expect_any_instance_of(Banzai::Filter::ReferenceFilter).to receive(:each_node).once
+ expect_any_instance_of(Banzai::Filter::References::ReferenceFilter).to receive(:each_node).once
described_class.call(markdown, project: project)
end
@@ -145,6 +145,7 @@ RSpec.describe Banzai::Pipeline::GfmPipeline do
describe 'emoji in references' do
let_it_be(:project) { create(:project, :public) }
+
let(:emoji) { '💯' }
it 'renders a label reference with emoji inside' do
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index b102de24041..007d310247b 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -289,4 +289,29 @@ RSpec.describe Banzai::Pipeline::WikiPipeline do
expect(output).to include('<audio src="/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/audio%20file%20name.wav"')
end
end
+
+ describe 'gollum tag filters' do
+ context 'when local image file exists' do
+ it 'sets the proper attributes for the image' do
+ gollum_file_double = double('Gollum::File',
+ mime_type: 'image/jpeg',
+ name: 'images/image.jpg',
+ path: 'images/image.jpg',
+ data: '')
+
+ wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double)
+ markdown = "[[#{wiki_file.path}]]"
+
+ expect(wiki).to receive(:find_file).with(wiki_file.path, load_content: false).and_return(wiki_file)
+
+ output = described_class.to_html(markdown, project: project, wiki: wiki, page_slug: page.slug)
+ doc = Nokogiri::HTML::DocumentFragment.parse(output)
+
+ full_path = "/wiki_link_ns/wiki_link_project/-/wikis/nested/twice/#{wiki_file.path}"
+ expect(doc.css('a')[0].attr('href')).to eq(full_path)
+ expect(doc.css('img')[0].attr('class')).to eq('gfm lazy')
+ expect(doc.css('img')[0].attr('data-src')).to eq(full_path)
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index 5f92eb42e74..0c1b98e5ec3 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe Banzai::ReferenceParser::ExternalIssueParser do
levels.each do |level|
it "creates reference when the feature is #{level}" do
- project.project_feature.update(issues_access_level: level)
+ project.project_feature.update!(issues_access_level: level)
visible_nodes = subject.nodes_visible_to_user(user, [link])
diff --git a/spec/lib/banzai/reference_redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb
index 668e427cfa2..78cceedd0e5 100644
--- a/spec/lib/banzai/reference_redactor_spec.rb
+++ b/spec/lib/banzai/reference_redactor_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe Banzai::ReferenceRedactor do
let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
before do
- project.update(pending_delete: true)
+ project.update!(pending_delete: true)
end
it 'redacts an issue attached' do
diff --git a/spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb b/spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb
new file mode 100644
index 00000000000..721dacbe3f4
--- /dev/null
+++ b/spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Common::Extractors::RestExtractor do
+ let(:http_client) { instance_double(BulkImports::Clients::Http) }
+ let(:options) { { query: double(to_h: { resource: nil, query: nil }) } }
+ let(:response) { double(parsed_response: { 'data' => { 'foo' => 'bar' } }, headers: { 'x-next-page' => '2' }) }
+
+ subject { described_class.new(options) }
+
+ describe '#extract' do
+ before do
+ allow(subject).to receive(:http_client).and_return(http_client)
+ allow(http_client).to receive(:get).and_return(response)
+ end
+
+ it 'returns instance of ExtractedData' do
+ entity = create(:bulk_import_entity)
+ tracker = create(:bulk_import_tracker, entity: entity)
+ context = BulkImports::Pipeline::Context.new(tracker)
+
+ extracted_data = subject.extract(context)
+
+ expect(extracted_data).to be_instance_of(BulkImports::Pipeline::ExtractedData)
+ expect(extracted_data.data).to contain_exactly(response.parsed_response)
+ expect(extracted_data.next_page).to eq(response.headers['x-next-page'])
+ expect(extracted_data.has_next_page?).to eq(true)
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/common/transformers/user_reference_transformer_spec.rb b/spec/lib/bulk_imports/common/transformers/user_reference_transformer_spec.rb
index ff11a10bfe9..ba74c173794 100644
--- a/spec/lib/bulk_imports/common/transformers/user_reference_transformer_spec.rb
+++ b/spec/lib/bulk_imports/common/transformers/user_reference_transformer_spec.rb
@@ -8,7 +8,8 @@ RSpec.describe BulkImports::Common::Transformers::UserReferenceTransformer do
let_it_be(:group) { create(:group) }
let_it_be(:bulk_import) { create(:bulk_import) }
let_it_be(:entity) { create(:bulk_import_entity, bulk_import: bulk_import, group: group) }
- let_it_be(:context) { BulkImports::Pipeline::Context.new(entity) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:hash) do
{
@@ -51,19 +52,26 @@ RSpec.describe BulkImports::Common::Transformers::UserReferenceTransformer do
end
context 'when custom reference is provided' do
- it 'updates provided reference' do
- hash = {
- 'author' => {
- 'public_email' => user.email
+ shared_examples 'updates provided reference' do |reference|
+ let(:hash) do
+ {
+ 'author' => {
+ 'public_email' => user.email
+ }
}
- }
+ end
- transformer = described_class.new(reference: 'author')
- result = transformer.transform(context, hash)
+ it 'updates provided reference' do
+ transformer = described_class.new(reference: reference)
+ result = transformer.transform(context, hash)
- expect(result['author']).to be_nil
- expect(result['author_id']).to eq(user.id)
+ expect(result['author']).to be_nil
+ expect(result['author_id']).to eq(user.id)
+ end
end
+
+ include_examples 'updates provided reference', 'author'
+ include_examples 'updates provided reference', :author
end
end
end
diff --git a/spec/lib/bulk_imports/groups/extractors/subgroups_extractor_spec.rb b/spec/lib/bulk_imports/groups/extractors/subgroups_extractor_spec.rb
index 627247c04ab..ac8786440e9 100644
--- a/spec/lib/bulk_imports/groups/extractors/subgroups_extractor_spec.rb
+++ b/spec/lib/bulk_imports/groups/extractors/subgroups_extractor_spec.rb
@@ -8,8 +8,9 @@ RSpec.describe BulkImports::Groups::Extractors::SubgroupsExtractor do
bulk_import = create(:bulk_import)
create(:bulk_import_configuration, bulk_import: bulk_import)
entity = create(:bulk_import_entity, bulk_import: bulk_import)
+ tracker = create(:bulk_import_tracker, entity: entity)
response = [{ 'test' => 'group' }]
- context = BulkImports::Pipeline::Context.new(entity)
+ context = BulkImports::Pipeline::Context.new(tracker)
allow_next_instance_of(BulkImports::Clients::Http) do |client|
allow(client).to receive(:each_page).and_return(response)
diff --git a/spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb b/spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb
index ef46da7062b..b0f8f74783b 100644
--- a/spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb
+++ b/spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb
@@ -4,10 +4,10 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Graphql::GetGroupQuery do
describe '#variables' do
- let(:entity) { double(source_full_path: 'test', bulk_import: nil) }
- let(:context) { BulkImports::Pipeline::Context.new(entity) }
-
it 'returns query variables based on entity information' do
+ entity = double(source_full_path: 'test', bulk_import: nil)
+ tracker = double(entity: entity)
+ context = BulkImports::Pipeline::Context.new(tracker)
expected = { full_path: entity.source_full_path }
expect(described_class.variables(context)).to eq(expected)
diff --git a/spec/lib/bulk_imports/groups/graphql/get_labels_query_spec.rb b/spec/lib/bulk_imports/groups/graphql/get_labels_query_spec.rb
index 85f82be7d18..61db644a372 100644
--- a/spec/lib/bulk_imports/groups/graphql/get_labels_query_spec.rb
+++ b/spec/lib/bulk_imports/groups/graphql/get_labels_query_spec.rb
@@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Graphql::GetLabelsQuery do
it 'has a valid query' do
- entity = create(:bulk_import_entity)
- context = BulkImports::Pipeline::Context.new(entity)
+ tracker = create(:bulk_import_tracker)
+ context = BulkImports::Pipeline::Context.new(tracker)
query = GraphQL::Query.new(
GitlabSchema,
diff --git a/spec/lib/bulk_imports/groups/graphql/get_members_query_spec.rb b/spec/lib/bulk_imports/groups/graphql/get_members_query_spec.rb
index 5d05f5a2d30..d0c4bb817b2 100644
--- a/spec/lib/bulk_imports/groups/graphql/get_members_query_spec.rb
+++ b/spec/lib/bulk_imports/groups/graphql/get_members_query_spec.rb
@@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Graphql::GetMembersQuery do
it 'has a valid query' do
- entity = create(:bulk_import_entity)
- context = BulkImports::Pipeline::Context.new(entity)
+ tracker = create(:bulk_import_tracker)
+ context = BulkImports::Pipeline::Context.new(tracker)
query = GraphQL::Query.new(
GitlabSchema,
diff --git a/spec/lib/bulk_imports/groups/graphql/get_milestones_query_spec.rb b/spec/lib/bulk_imports/groups/graphql/get_milestones_query_spec.rb
index a38505fbf85..7a0f964c5f3 100644
--- a/spec/lib/bulk_imports/groups/graphql/get_milestones_query_spec.rb
+++ b/spec/lib/bulk_imports/groups/graphql/get_milestones_query_spec.rb
@@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Graphql::GetMilestonesQuery do
it 'has a valid query' do
- entity = create(:bulk_import_entity)
- context = BulkImports::Pipeline::Context.new(entity)
+ tracker = create(:bulk_import_tracker)
+ context = BulkImports::Pipeline::Context.new(tracker)
query = GraphQL::Query.new(
GitlabSchema,
diff --git a/spec/lib/bulk_imports/groups/loaders/group_loader_spec.rb b/spec/lib/bulk_imports/groups/loaders/group_loader_spec.rb
index 183292722d2..533955b057c 100644
--- a/spec/lib/bulk_imports/groups/loaders/group_loader_spec.rb
+++ b/spec/lib/bulk_imports/groups/loaders/group_loader_spec.rb
@@ -4,12 +4,13 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Loaders::GroupLoader do
describe '#load' do
- let(:user) { create(:user) }
- let(:data) { { foo: :bar } }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:bulk_import) { create(:bulk_import, user: user) }
+ let_it_be(:entity) { create(:bulk_import_entity, bulk_import: bulk_import) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:service_double) { instance_double(::Groups::CreateService) }
- let(:bulk_import) { create(:bulk_import, user: user) }
- let(:entity) { create(:bulk_import_entity, bulk_import: bulk_import) }
- let(:context) { BulkImports::Pipeline::Context.new(entity) }
+ let(:data) { { foo: :bar } }
subject { described_class.new }
diff --git a/spec/lib/bulk_imports/groups/pipelines/badges_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/badges_pipeline_spec.rb
new file mode 100644
index 00000000000..9fa35c4707d
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/pipelines/badges_pipeline_spec.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Pipelines::BadgesPipeline do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+
+ let_it_be(:entity) do
+ create(
+ :bulk_import_entity,
+ source_full_path: 'source/full/path',
+ destination_name: 'My Destination Group',
+ destination_namespace: group.full_path,
+ group: group
+ )
+ end
+
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
+
+ subject { described_class.new(context) }
+
+ describe '#run' do
+ it 'imports a group badge' do
+ first_page = extracted_data(has_next_page: true)
+ last_page = extracted_data(name: 'badge2')
+
+ allow_next_instance_of(BulkImports::Common::Extractors::RestExtractor) do |extractor|
+ allow(extractor)
+ .to receive(:extract)
+ .and_return(first_page, last_page)
+ end
+
+ expect { subject.run }.to change(Badge, :count).by(2)
+
+ badge = group.badges.last
+
+ expect(badge.name).to eq('badge2')
+ expect(badge.link_url).to eq(badge_data['link_url'])
+ expect(badge.image_url).to eq(badge_data['image_url'])
+ end
+
+ describe '#load' do
+ it 'creates a badge' do
+ expect { subject.load(context, badge_data) }.to change(Badge, :count).by(1)
+
+ badge = group.badges.first
+
+ badge_data.each do |key, value|
+ expect(badge[key]).to eq(value)
+ end
+ end
+
+ it 'does nothing when the data is blank' do
+ expect { subject.load(context, nil) }.not_to change(Badge, :count)
+ end
+ end
+
+ describe '#transform' do
+ it 'return transformed badge hash' do
+ badge = subject.transform(context, badge_data)
+
+ expect(badge[:name]).to eq('badge')
+ expect(badge[:link_url]).to eq(badge_data['link_url'])
+ expect(badge[:image_url]).to eq(badge_data['image_url'])
+ expect(badge.keys).to contain_exactly(:name, :link_url, :image_url)
+ end
+
+ context 'when data is blank' do
+ it 'does nothing when the data is blank' do
+ expect(subject.transform(context, nil)).to be_nil
+ end
+ end
+ end
+
+ describe 'pipeline parts' do
+ it { expect(described_class).to include_module(BulkImports::Pipeline) }
+ it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
+
+ it 'has extractors' do
+ expect(described_class.get_extractor)
+ .to eq(
+ klass: BulkImports::Common::Extractors::RestExtractor,
+ options: {
+ query: BulkImports::Groups::Rest::GetBadgesQuery
+ }
+ )
+ end
+
+ it 'has transformers' do
+ expect(described_class.transformers)
+ .to contain_exactly(
+ { klass: BulkImports::Common::Transformers::ProhibitedAttributesTransformer, options: nil }
+ )
+ end
+ end
+
+ def badge_data(name = 'badge')
+ {
+ 'name' => name,
+ 'link_url' => 'https://gitlab.example.com',
+ 'image_url' => 'https://gitlab.example.com/image.png'
+ }
+ end
+
+ def extracted_data(name: 'badge', has_next_page: false)
+ page_info = {
+ 'has_next_page' => has_next_page,
+ 'next_page' => has_next_page ? '2' : nil
+ }
+
+ BulkImports::Pipeline::ExtractedData.new(data: [badge_data(name)], page_info: page_info)
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/pipelines/entity_finisher_spec.rb b/spec/lib/bulk_imports/groups/pipelines/entity_finisher_spec.rb
new file mode 100644
index 00000000000..8276349c5f4
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/pipelines/entity_finisher_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Pipelines::EntityFinisher do
+ it 'updates the entity status to finished' do
+ entity = create(:bulk_import_entity, :started)
+ pipeline_tracker = create(:bulk_import_tracker, entity: entity)
+ context = BulkImports::Pipeline::Context.new(pipeline_tracker)
+ subject = described_class.new(context)
+
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ bulk_import_id: entity.bulk_import.id,
+ bulk_import_entity_id: entity.id,
+ bulk_import_entity_type: entity.source_type,
+ pipeline_class: described_class.name,
+ message: 'Entity finished'
+ )
+ end
+
+ expect { subject.run }
+ .to change(entity, :status_name).to(:finished)
+ end
+
+ it 'does nothing when the entity is already finished' do
+ entity = create(:bulk_import_entity, :finished)
+ pipeline_tracker = create(:bulk_import_tracker, entity: entity)
+ context = BulkImports::Pipeline::Context.new(pipeline_tracker)
+ subject = described_class.new(context)
+
+ expect { subject.run }
+ .not_to change(entity, :status_name)
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb
index 61950cdd9b0..39e782dc093 100644
--- a/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb
@@ -4,10 +4,11 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Pipelines::GroupPipeline do
describe '#run' do
- let(:user) { create(:user) }
- let(:parent) { create(:group) }
- let(:bulk_import) { create(:bulk_import, user: user) }
- let(:entity) do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:parent) { create(:group) }
+ let_it_be(:bulk_import) { create(:bulk_import, user: user) }
+
+ let_it_be(:entity) do
create(
:bulk_import_entity,
bulk_import: bulk_import,
@@ -17,7 +18,8 @@ RSpec.describe BulkImports::Groups::Pipelines::GroupPipeline do
)
end
- let(:context) { BulkImports::Pipeline::Context.new(entity) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:group_data) do
{
@@ -37,7 +39,7 @@ RSpec.describe BulkImports::Groups::Pipelines::GroupPipeline do
before do
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
- allow(extractor).to receive(:extract).and_return([group_data])
+ allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: group_data))
end
parent.add_owner(user)
diff --git a/spec/lib/bulk_imports/groups/pipelines/labels_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/labels_pipeline_spec.rb
index 3327a30f1d5..8af646d1101 100644
--- a/spec/lib/bulk_imports/groups/pipelines/labels_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/groups/pipelines/labels_pipeline_spec.rb
@@ -3,11 +3,11 @@
require 'spec_helper'
RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:cursor) { 'cursor' }
- let(:timestamp) { Time.new(2020, 01, 01).utc }
- let(:entity) do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:timestamp) { Time.new(2020, 01, 01).utc }
+
+ let_it_be(:entity) do
create(
:bulk_import_entity,
source_full_path: 'source/full/path',
@@ -17,33 +17,15 @@ RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
)
end
- let(:context) { BulkImports::Pipeline::Context.new(entity) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject { described_class.new(context) }
- def label_data(title)
- {
- 'title' => title,
- 'description' => 'desc',
- 'color' => '#428BCA',
- 'created_at' => timestamp.to_s,
- 'updated_at' => timestamp.to_s
- }
- end
-
- def extractor_data(title:, has_next_page:, cursor: nil)
- page_info = {
- 'end_cursor' => cursor,
- 'has_next_page' => has_next_page
- }
-
- BulkImports::Pipeline::ExtractedData.new(data: [label_data(title)], page_info: page_info)
- end
-
describe '#run' do
it 'imports a group labels' do
- first_page = extractor_data(title: 'label1', has_next_page: true, cursor: cursor)
- last_page = extractor_data(title: 'label2', has_next_page: false)
+ first_page = extracted_data(title: 'label1', has_next_page: true)
+ last_page = extracted_data(title: 'label2')
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
allow(extractor)
@@ -63,38 +45,6 @@ RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
end
end
- describe '#after_run' do
- context 'when extracted data has next page' do
- it 'updates tracker information and runs pipeline again' do
- data = extractor_data(title: 'label', has_next_page: true, cursor: cursor)
-
- expect(subject).to receive(:run)
-
- subject.after_run(data)
-
- tracker = entity.trackers.find_by(relation: :labels)
-
- expect(tracker.has_next_page).to eq(true)
- expect(tracker.next_page).to eq(cursor)
- end
- end
-
- context 'when extracted data has no next page' do
- it 'updates tracker information and does not run pipeline' do
- data = extractor_data(title: 'label', has_next_page: false)
-
- expect(subject).not_to receive(:run)
-
- subject.after_run(data)
-
- tracker = entity.trackers.find_by(relation: :labels)
-
- expect(tracker.has_next_page).to eq(false)
- expect(tracker.next_page).to be_nil
- end
- end
- end
-
describe '#load' do
it 'creates the label' do
data = label_data('label')
@@ -130,4 +80,23 @@ RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
)
end
end
+
+ def label_data(title)
+ {
+ 'title' => title,
+ 'description' => 'desc',
+ 'color' => '#428BCA',
+ 'created_at' => timestamp.to_s,
+ 'updated_at' => timestamp.to_s
+ }
+ end
+
+ def extracted_data(title:, has_next_page: false)
+ page_info = {
+ 'has_next_page' => has_next_page,
+ 'next_page' => has_next_page ? 'cursor' : nil
+ }
+
+ BulkImports::Pipeline::ExtractedData.new(data: [label_data(title)], page_info: page_info)
+ end
end
diff --git a/spec/lib/bulk_imports/groups/pipelines/members_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/members_pipeline_spec.rb
index 74d3e09d263..d8a667ec92a 100644
--- a/spec/lib/bulk_imports/groups/pipelines/members_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/groups/pipelines/members_pipeline_spec.rb
@@ -8,17 +8,17 @@ RSpec.describe BulkImports::Groups::Pipelines::MembersPipeline do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
- let_it_be(:cursor) { 'cursor' }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:entity) { create(:bulk_import_entity, bulk_import: bulk_import, group: group) }
- let_it_be(:context) { BulkImports::Pipeline::Context.new(entity) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject { described_class.new(context) }
describe '#run' do
it 'maps existing users to the imported group' do
- first_page = member_data(email: member_user1.email, has_next_page: true, cursor: cursor)
- last_page = member_data(email: member_user2.email, has_next_page: false)
+ first_page = extracted_data(email: member_user1.email, has_next_page: true)
+ last_page = extracted_data(email: member_user2.email)
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
allow(extractor)
@@ -88,7 +88,7 @@ RSpec.describe BulkImports::Groups::Pipelines::MembersPipeline do
end
end
- def member_data(email:, has_next_page:, cursor: nil)
+ def extracted_data(email:, has_next_page: false)
data = {
'created_at' => '2020-01-01T00:00:00Z',
'updated_at' => '2020-01-01T00:00:00Z',
@@ -102,8 +102,8 @@ RSpec.describe BulkImports::Groups::Pipelines::MembersPipeline do
}
page_info = {
- 'end_cursor' => cursor,
- 'has_next_page' => has_next_page
+ 'has_next_page' => has_next_page,
+ 'next_page' => has_next_page ? 'cursor' : nil
}
BulkImports::Pipeline::ExtractedData.new(data: data, page_info: page_info)
diff --git a/spec/lib/bulk_imports/groups/pipelines/milestones_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/milestones_pipeline_spec.rb
index f0c34c65257..e5cf75c566b 100644
--- a/spec/lib/bulk_imports/groups/pipelines/milestones_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/groups/pipelines/milestones_pipeline_spec.rb
@@ -5,11 +5,10 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Pipelines::MilestonesPipeline do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
- let_it_be(:cursor) { 'cursor' }
let_it_be(:timestamp) { Time.new(2020, 01, 01).utc }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
- let(:entity) do
+ let_it_be(:entity) do
create(
:bulk_import_entity,
bulk_import: bulk_import,
@@ -20,39 +19,19 @@ RSpec.describe BulkImports::Groups::Pipelines::MilestonesPipeline do
)
end
- let(:context) { BulkImports::Pipeline::Context.new(entity) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject { described_class.new(context) }
- def milestone_data(title)
- {
- 'title' => title,
- 'description' => 'desc',
- 'state' => 'closed',
- 'start_date' => '2020-10-21',
- 'due_date' => '2020-10-22',
- 'created_at' => timestamp.to_s,
- 'updated_at' => timestamp.to_s
- }
- end
-
- def extracted_data(title:, has_next_page:, cursor: nil)
- page_info = {
- 'end_cursor' => cursor,
- 'has_next_page' => has_next_page
- }
-
- BulkImports::Pipeline::ExtractedData.new(data: [milestone_data(title)], page_info: page_info)
- end
-
before do
group.add_owner(user)
end
describe '#run' do
it 'imports group milestones' do
- first_page = extracted_data(title: 'milestone1', has_next_page: true, cursor: cursor)
- last_page = extracted_data(title: 'milestone2', has_next_page: false)
+ first_page = extracted_data(title: 'milestone1', iid: 1, has_next_page: true)
+ last_page = extracted_data(title: 'milestone2', iid: 2)
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
allow(extractor)
@@ -75,38 +54,6 @@ RSpec.describe BulkImports::Groups::Pipelines::MilestonesPipeline do
end
end
- describe '#after_run' do
- context 'when extracted data has next page' do
- it 'updates tracker information and runs pipeline again' do
- data = extracted_data(title: 'milestone', has_next_page: true, cursor: cursor)
-
- expect(subject).to receive(:run)
-
- subject.after_run(data)
-
- tracker = entity.trackers.find_by(relation: :milestones)
-
- expect(tracker.has_next_page).to eq(true)
- expect(tracker.next_page).to eq(cursor)
- end
- end
-
- context 'when extracted data has no next page' do
- it 'updates tracker information and does not run pipeline' do
- data = extracted_data(title: 'milestone', has_next_page: false)
-
- expect(subject).not_to receive(:run)
-
- subject.after_run(data)
-
- tracker = entity.trackers.find_by(relation: :milestones)
-
- expect(tracker.has_next_page).to eq(false)
- expect(tracker.next_page).to be_nil
- end
- end
- end
-
describe '#load' do
it 'creates the milestone' do
data = milestone_data('milestone')
@@ -120,7 +67,7 @@ RSpec.describe BulkImports::Groups::Pipelines::MilestonesPipeline do
end
it 'raises NotAllowedError' do
- data = extracted_data(title: 'milestone', has_next_page: false)
+ data = extracted_data(title: 'milestone')
expect { subject.load(context, data) }.to raise_error(::BulkImports::Pipeline::NotAllowedError)
end
@@ -148,4 +95,29 @@ RSpec.describe BulkImports::Groups::Pipelines::MilestonesPipeline do
)
end
end
+
+ def milestone_data(title, iid: 1)
+ {
+ 'title' => title,
+ 'description' => 'desc',
+ 'iid' => iid,
+ 'state' => 'closed',
+ 'start_date' => '2020-10-21',
+ 'due_date' => '2020-10-22',
+ 'created_at' => timestamp.to_s,
+ 'updated_at' => timestamp.to_s
+ }
+ end
+
+ def extracted_data(title:, iid: 1, has_next_page: false)
+ page_info = {
+ 'has_next_page' => has_next_page,
+ 'next_page' => has_next_page ? 'cursor' : nil
+ }
+
+ BulkImports::Pipeline::ExtractedData.new(
+ data: milestone_data(title, iid: iid),
+ page_info: page_info
+ )
+ end
end
diff --git a/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb b/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb
index 2a99646bb4a..e4a41428dd2 100644
--- a/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb
@@ -6,31 +6,23 @@ RSpec.describe BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, path: 'group') }
let_it_be(:parent) { create(:group, name: 'imported-group', path: 'imported-group') }
- let(:context) { BulkImports::Pipeline::Context.new(parent_entity) }
+ let_it_be(:parent_entity) { create(:bulk_import_entity, destination_namespace: parent.full_path, group: parent) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: parent_entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject { described_class.new(context) }
- describe '#run' do
- let!(:parent_entity) do
- create(
- :bulk_import_entity,
- destination_namespace: parent.full_path,
- group: parent
- )
- end
-
- let(:subgroup_data) do
- [
- {
- "name" => "subgroup",
- "full_path" => "parent/subgroup"
- }
- ]
- end
+ let(:extracted_data) do
+ BulkImports::Pipeline::ExtractedData.new(data: {
+ 'name' => 'subgroup',
+ 'full_path' => 'parent/subgroup'
+ })
+ end
+ describe '#run' do
before do
allow_next_instance_of(BulkImports::Groups::Extractors::SubgroupsExtractor) do |extractor|
- allow(extractor).to receive(:extract).and_return(subgroup_data)
+ allow(extractor).to receive(:extract).and_return(extracted_data)
end
parent.add_owner(user)
diff --git a/spec/lib/bulk_imports/groups/rest/get_badges_query_spec.rb b/spec/lib/bulk_imports/groups/rest/get_badges_query_spec.rb
new file mode 100644
index 00000000000..eef6848e118
--- /dev/null
+++ b/spec/lib/bulk_imports/groups/rest/get_badges_query_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Groups::Rest::GetBadgesQuery do
+ describe '.to_h' do
+ it 'returns query resource and page info' do
+ entity = create(:bulk_import_entity)
+ tracker = create(:bulk_import_tracker, entity: entity)
+ context = BulkImports::Pipeline::Context.new(tracker)
+ encoded_full_path = ERB::Util.url_encode(entity.source_full_path)
+ expected = {
+ resource: ['groups', encoded_full_path, 'badges'].join('/'),
+ query: {
+ page: context.tracker.next_page
+ }
+ }
+
+ expect(described_class.to_h(context)).to eq(expected)
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb
index b3fe8a2ba25..75d8c15088a 100644
--- a/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb
+++ b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb
@@ -4,11 +4,12 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
describe '#transform' do
- let(:user) { create(:user) }
- let(:parent) { create(:group) }
- let(:group) { create(:group, name: 'My Source Group', parent: parent) }
- let(:bulk_import) { create(:bulk_import, user: user) }
- let(:entity) do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:parent) { create(:group) }
+ let_it_be(:group) { create(:group, name: 'My Source Group', parent: parent) }
+ let_it_be(:bulk_import) { create(:bulk_import, user: user) }
+
+ let_it_be(:entity) do
create(
:bulk_import_entity,
bulk_import: bulk_import,
@@ -18,7 +19,8 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
)
end
- let(:context) { BulkImports::Pipeline::Context.new(entity) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:data) do
{
@@ -82,14 +84,7 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
context 'when destination namespace is empty' do
it 'does not set parent id' do
- entity = create(
- :bulk_import_entity,
- bulk_import: bulk_import,
- source_full_path: 'source/full/path',
- destination_name: group.name,
- destination_namespace: ''
- )
- context = BulkImports::Pipeline::Context.new(entity)
+ entity.update!(destination_namespace: '')
transformed_data = subject.transform(context, data)
diff --git a/spec/lib/bulk_imports/groups/transformers/member_attributes_transformer_spec.rb b/spec/lib/bulk_imports/groups/transformers/member_attributes_transformer_spec.rb
index f66c67fc6a2..f3905a4b6e4 100644
--- a/spec/lib/bulk_imports/groups/transformers/member_attributes_transformer_spec.rb
+++ b/spec/lib/bulk_imports/groups/transformers/member_attributes_transformer_spec.rb
@@ -8,7 +8,8 @@ RSpec.describe BulkImports::Groups::Transformers::MemberAttributesTransformer do
let_it_be(:group) { create(:group) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:entity) { create(:bulk_import_entity, bulk_import: bulk_import, group: group) }
- let_it_be(:context) { BulkImports::Pipeline::Context.new(entity) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
it 'returns nil when receives no data' do
expect(subject.transform(context, nil)).to eq(nil)
diff --git a/spec/lib/bulk_imports/importers/group_importer_spec.rb b/spec/lib/bulk_imports/importers/group_importer_spec.rb
deleted file mode 100644
index 5d501b49e41..00000000000
--- a/spec/lib/bulk_imports/importers/group_importer_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe BulkImports::Importers::GroupImporter do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:bulk_import) { create(:bulk_import) }
- let(:bulk_import_entity) { create(:bulk_import_entity, :started, bulk_import: bulk_import, group: group) }
- let(:bulk_import_configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) }
- let(:context) { BulkImports::Pipeline::Context.new(bulk_import_entity) }
-
- before do
- allow(BulkImports::Pipeline::Context).to receive(:new).and_return(context)
- end
-
- subject { described_class.new(bulk_import_entity) }
-
- describe '#execute' do
- it 'starts the entity and run its pipelines' do
- expect_to_run_pipeline BulkImports::Groups::Pipelines::GroupPipeline, context: context
- expect_to_run_pipeline BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline, context: context
- expect_to_run_pipeline BulkImports::Groups::Pipelines::MembersPipeline, context: context
- expect_to_run_pipeline BulkImports::Groups::Pipelines::LabelsPipeline, context: context
- expect_to_run_pipeline BulkImports::Groups::Pipelines::MilestonesPipeline, context: context
-
- if Gitlab.ee?
- expect_to_run_pipeline('EE::BulkImports::Groups::Pipelines::EpicsPipeline'.constantize, context: context)
- expect_to_run_pipeline('EE::BulkImports::Groups::Pipelines::EpicAwardEmojiPipeline'.constantize, context: context)
- expect_to_run_pipeline('EE::BulkImports::Groups::Pipelines::EpicEventsPipeline'.constantize, context: context)
- expect_to_run_pipeline('EE::BulkImports::Groups::Pipelines::IterationsPipeline'.constantize, context: context)
- end
-
- subject.execute
-
- expect(bulk_import_entity.reload).to be_finished
- end
-
- context 'when failed' do
- let(:bulk_import_entity) { create(:bulk_import_entity, :failed, bulk_import: bulk_import, group: group) }
-
- it 'does not transition entity to finished state' do
- allow(bulk_import_entity).to receive(:start!)
-
- subject.execute
-
- expect(bulk_import_entity.reload).to be_failed
- end
- end
- end
-
- def expect_to_run_pipeline(klass, context:)
- expect_next_instance_of(klass, context) do |pipeline|
- expect(pipeline).to receive(:run)
- end
- end
-end
diff --git a/spec/lib/bulk_imports/pipeline/context_spec.rb b/spec/lib/bulk_imports/pipeline/context_spec.rb
index c8c3fe3a861..5b7711ad5d7 100644
--- a/spec/lib/bulk_imports/pipeline/context_spec.rb
+++ b/spec/lib/bulk_imports/pipeline/context_spec.rb
@@ -3,29 +3,52 @@
require 'spec_helper'
RSpec.describe BulkImports::Pipeline::Context do
- let(:group) { instance_double(Group) }
- let(:user) { instance_double(User) }
- let(:bulk_import) { instance_double(BulkImport, user: user, configuration: :config) }
-
- let(:entity) do
- instance_double(
- BulkImports::Entity,
- bulk_import: bulk_import,
- group: group
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:bulk_import) { create(:bulk_import, user: user) }
+
+ let_it_be(:entity) do
+ create(
+ :bulk_import_entity,
+ source_full_path: 'source/full/path',
+ destination_name: 'My Destination Group',
+ destination_namespace: group.full_path,
+ group: group,
+ bulk_import: bulk_import
+ )
+ end
+
+ let_it_be(:tracker) do
+ create(
+ :bulk_import_tracker,
+ entity: entity,
+ pipeline_name: described_class.name
)
end
- subject { described_class.new(entity) }
+ subject { described_class.new(tracker, extra: :data) }
+
+ describe '#entity' do
+ it { expect(subject.entity).to eq(entity) }
+ end
describe '#group' do
it { expect(subject.group).to eq(group) }
end
+ describe '#bulk_import' do
+ it { expect(subject.bulk_import).to eq(bulk_import) }
+ end
+
describe '#current_user' do
it { expect(subject.current_user).to eq(user) }
end
- describe '#current_user' do
+ describe '#configuration' do
it { expect(subject.configuration).to eq(bulk_import.configuration) }
end
+
+ describe '#extra' do
+ it { expect(subject.extra).to eq(extra: :data) }
+ end
end
diff --git a/spec/lib/bulk_imports/pipeline/extracted_data_spec.rb b/spec/lib/bulk_imports/pipeline/extracted_data_spec.rb
index 25c5178227a..9c79b3f4c9e 100644
--- a/spec/lib/bulk_imports/pipeline/extracted_data_spec.rb
+++ b/spec/lib/bulk_imports/pipeline/extracted_data_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe BulkImports::Pipeline::ExtractedData do
let(:page_info) do
{
'has_next_page' => has_next_page,
- 'end_cursor' => cursor
+ 'next_page' => cursor
}
end
diff --git a/spec/lib/bulk_imports/pipeline/runner_spec.rb b/spec/lib/bulk_imports/pipeline/runner_spec.rb
index 59f01c9caaa..7235b7c95cd 100644
--- a/spec/lib/bulk_imports/pipeline/runner_spec.rb
+++ b/spec/lib/bulk_imports/pipeline/runner_spec.rb
@@ -38,23 +38,20 @@ RSpec.describe BulkImports::Pipeline::Runner do
extractor BulkImports::Extractor
transformer BulkImports::Transformer
loader BulkImports::Loader
-
- def after_run(_); end
end
stub_const('BulkImports::MyPipeline', pipeline)
end
- let_it_be_with_refind(:entity) { create(:bulk_import_entity) }
- let(:context) { BulkImports::Pipeline::Context.new(entity, extra: :data) }
+ let_it_be_with_reload(:entity) { create(:bulk_import_entity) }
+ let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
+ let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker, extra: :data) }
subject { BulkImports::MyPipeline.new(context) }
describe 'pipeline runner' do
context 'when entity is not marked as failed' do
it 'runs pipeline extractor, transformer, loader' do
- extracted_data = BulkImports::Pipeline::ExtractedData.new(data: { foo: :bar })
-
expect_next_instance_of(BulkImports::Extractor) do |extractor|
expect(extractor)
.to receive(:extract)
@@ -132,6 +129,22 @@ RSpec.describe BulkImports::Pipeline::Runner do
subject.run
end
+ context 'when extracted data has multiple pages' do
+ it 'updates tracker information and runs pipeline again' do
+ first_page = extracted_data(has_next_page: true)
+ last_page = extracted_data
+
+ expect_next_instance_of(BulkImports::Extractor) do |extractor|
+ expect(extractor)
+ .to receive(:extract)
+ .with(context)
+ .and_return(first_page, last_page)
+ end
+
+ subject.run
+ end
+ end
+
context 'when exception is raised' do
before do
allow_next_instance_of(BulkImports::Extractor) do |extractor|
@@ -170,12 +183,7 @@ RSpec.describe BulkImports::Pipeline::Runner do
BulkImports::MyPipeline.abort_on_failure!
end
- it 'marks entity as failed' do
- expect { subject.run }
- .to change(entity, :status_name).to(:failed)
- end
-
- it 'logs warn message' do
+ it 'logs a warn message and marks entity as failed' do
expect_next_instance_of(Gitlab::Import::Logger) do |logger|
expect(logger).to receive(:warn)
.with(
@@ -188,6 +196,9 @@ RSpec.describe BulkImports::Pipeline::Runner do
end
subject.run
+
+ expect(entity.status_name).to eq(:failed)
+ expect(tracker.status_name).to eq(:failed)
end
end
@@ -206,11 +217,11 @@ RSpec.describe BulkImports::Pipeline::Runner do
entity.fail_op!
expect_next_instance_of(Gitlab::Import::Logger) do |logger|
- expect(logger).to receive(:info)
+ expect(logger).to receive(:warn)
.with(
log_params(
context,
- message: 'Skipping due to failed pipeline status',
+ message: 'Skipping pipeline due to failed entity',
pipeline_class: 'BulkImports::MyPipeline'
)
)
@@ -219,14 +230,24 @@ RSpec.describe BulkImports::Pipeline::Runner do
subject.run
end
end
- end
- def log_params(context, extra = {})
- {
- bulk_import_id: context.bulk_import.id,
- bulk_import_entity_id: context.entity.id,
- bulk_import_entity_type: context.entity.source_type,
- context_extra: context.extra
- }.merge(extra)
+ def log_params(context, extra = {})
+ {
+ bulk_import_id: context.bulk_import.id,
+ bulk_import_entity_id: context.entity.id,
+ bulk_import_entity_type: context.entity.source_type,
+ context_extra: context.extra
+ }.merge(extra)
+ end
+
+ def extracted_data(has_next_page: false)
+ BulkImports::Pipeline::ExtractedData.new(
+ data: { foo: :bar },
+ page_info: {
+ 'has_next_page' => has_next_page,
+ 'next_page' => has_next_page ? 'cursor' : nil
+ }
+ )
+ end
end
end
diff --git a/spec/lib/bulk_imports/pipeline_spec.rb b/spec/lib/bulk_imports/pipeline_spec.rb
index c882e3d26ea..dda2e41f06c 100644
--- a/spec/lib/bulk_imports/pipeline_spec.rb
+++ b/spec/lib/bulk_imports/pipeline_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe BulkImports::Pipeline do
+ let(:context) { instance_double(BulkImports::Pipeline::Context, tracker: nil) }
+
before do
stub_const('BulkImports::Extractor', Class.new)
stub_const('BulkImports::Transformer', Class.new)
@@ -44,7 +46,7 @@ RSpec.describe BulkImports::Pipeline do
end
it 'returns itself when retrieving extractor & loader' do
- pipeline = BulkImports::AnotherPipeline.new(nil)
+ pipeline = BulkImports::AnotherPipeline.new(context)
expect(pipeline.send(:extractor)).to eq(pipeline)
expect(pipeline.send(:loader)).to eq(pipeline)
@@ -83,7 +85,7 @@ RSpec.describe BulkImports::Pipeline do
expect(BulkImports::Transformer).to receive(:new).with(foo: :bar)
expect(BulkImports::Loader).to receive(:new).with(foo: :bar)
- pipeline = BulkImports::MyPipeline.new(nil)
+ pipeline = BulkImports::MyPipeline.new(context)
pipeline.send(:extractor)
pipeline.send(:transformers)
@@ -109,7 +111,7 @@ RSpec.describe BulkImports::Pipeline do
expect(BulkImports::Transformer).to receive(:new).with(no_args)
expect(BulkImports::Loader).to receive(:new).with(no_args)
- pipeline = BulkImports::NoOptionsPipeline.new(nil)
+ pipeline = BulkImports::NoOptionsPipeline.new(context)
pipeline.send(:extractor)
pipeline.send(:transformers)
@@ -135,7 +137,7 @@ RSpec.describe BulkImports::Pipeline do
transformer = double
allow(BulkImports::Transformer).to receive(:new).and_return(transformer)
- pipeline = BulkImports::TransformersPipeline.new(nil)
+ pipeline = BulkImports::TransformersPipeline.new(context)
expect(pipeline.send(:transformers)).to eq([pipeline, transformer])
end
diff --git a/spec/lib/constraints/admin_constrainer_spec.rb b/spec/lib/constraints/admin_constrainer_spec.rb
index ac6ad31120e..6e8909ca129 100644
--- a/spec/lib/constraints/admin_constrainer_spec.rb
+++ b/spec/lib/constraints/admin_constrainer_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Constraints::AdminConstrainer do
end
describe '#matches' do
- context 'feature flag :user_mode_in_session is enabled' do
+ context 'application setting :admin_mode is enabled' do
context 'when user is a regular user' do
it 'forbids access' do
expect(subject.matches?(request)).to be(false)
@@ -46,9 +46,9 @@ RSpec.describe Constraints::AdminConstrainer do
end
end
- context 'feature flag :user_mode_in_session is disabled' do
+ context 'application setting :admin_mode is disabled' do
before do
- stub_feature_flags(user_mode_in_session: false)
+ stub_application_setting(admin_mode: false)
end
context 'when user is a regular user' do
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 3e158391d7f..dc8fd0de313 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -487,6 +487,98 @@ RSpec.describe Feature, stub_feature_flags: false do
end
end
+ context 'caching with stale reads from the database', :use_clean_rails_redis_caching, :request_store, :aggregate_failures do
+ let(:actor) { stub_feature_flag_gate('CustomActor:5') }
+ let(:another_actor) { stub_feature_flag_gate('CustomActor:10') }
+
+ # This is a bit unpleasant. For these tests we want to simulate stale reads
+ # from the database (due to database load balancing). A simple way to do
+ # that is to stub the response on the adapter Flipper uses for reading from
+ # the database. However, there isn't a convenient API for this. We know that
+ # the ActiveRecord adapter is always at the 'bottom' of the chain, so we can
+ # find it that way.
+ let(:active_record_adapter) do
+ adapter = described_class.flipper
+
+ loop do
+ break adapter unless adapter.instance_variable_get(:@adapter)
+
+ adapter = adapter.instance_variable_get(:@adapter)
+ end
+ end
+
+ it 'gives the correct value when enabling for an additional actor' do
+ described_class.enable(:enabled_feature_flag, actor)
+ initial_gate_values = active_record_adapter.get(described_class.get(:enabled_feature_flag))
+
+ # This should only be enabled for `actor`
+ expect(described_class.enabled?(:enabled_feature_flag, actor)).to be(true)
+ expect(described_class.enabled?(:enabled_feature_flag, another_actor)).to be(false)
+ expect(described_class.enabled?(:enabled_feature_flag)).to be(false)
+
+ # Enable for `another_actor` and simulate a stale read
+ described_class.enable(:enabled_feature_flag, another_actor)
+ allow(active_record_adapter).to receive(:get).once.and_return(initial_gate_values)
+
+ # Should read from the cache and be enabled for both of these actors
+ expect(described_class.enabled?(:enabled_feature_flag, actor)).to be(true)
+ expect(described_class.enabled?(:enabled_feature_flag, another_actor)).to be(true)
+ expect(described_class.enabled?(:enabled_feature_flag)).to be(false)
+ end
+
+ it 'gives the correct value when enabling for percentage of time' do
+ described_class.enable_percentage_of_time(:enabled_feature_flag, 10)
+ initial_gate_values = active_record_adapter.get(described_class.get(:enabled_feature_flag))
+
+ # Test against `gate_values` directly as otherwise it would be non-determistic
+ expect(described_class.get(:enabled_feature_flag).gate_values.percentage_of_time).to eq(10)
+
+ # Enable 50% of time and simulate a stale read
+ described_class.enable_percentage_of_time(:enabled_feature_flag, 50)
+ allow(active_record_adapter).to receive(:get).once.and_return(initial_gate_values)
+
+ # Should read from the cache and be enabled 50% of the time
+ expect(described_class.get(:enabled_feature_flag).gate_values.percentage_of_time).to eq(50)
+ end
+
+ it 'gives the correct value when disabling the flag' do
+ described_class.enable(:enabled_feature_flag, actor)
+ described_class.enable(:enabled_feature_flag, another_actor)
+ initial_gate_values = active_record_adapter.get(described_class.get(:enabled_feature_flag))
+
+ # This be enabled for `actor` and `another_actor`
+ expect(described_class.enabled?(:enabled_feature_flag, actor)).to be(true)
+ expect(described_class.enabled?(:enabled_feature_flag, another_actor)).to be(true)
+ expect(described_class.enabled?(:enabled_feature_flag)).to be(false)
+
+ # Disable for `another_actor` and simulate a stale read
+ described_class.disable(:enabled_feature_flag, another_actor)
+ allow(active_record_adapter).to receive(:get).once.and_return(initial_gate_values)
+
+ # Should read from the cache and be enabled only for `actor`
+ expect(described_class.enabled?(:enabled_feature_flag, actor)).to be(true)
+ expect(described_class.enabled?(:enabled_feature_flag, another_actor)).to be(false)
+ expect(described_class.enabled?(:enabled_feature_flag)).to be(false)
+ end
+
+ it 'gives the correct value when deleting the flag' do
+ described_class.enable(:enabled_feature_flag, actor)
+ initial_gate_values = active_record_adapter.get(described_class.get(:enabled_feature_flag))
+
+ # This should only be enabled for `actor`
+ expect(described_class.enabled?(:enabled_feature_flag, actor)).to be(true)
+ expect(described_class.enabled?(:enabled_feature_flag)).to be(false)
+
+ # Remove and simulate a stale read
+ described_class.remove(:enabled_feature_flag)
+ allow(active_record_adapter).to receive(:get).once.and_return(initial_gate_values)
+
+ # Should read from the cache and be disabled everywhere
+ expect(described_class.enabled?(:enabled_feature_flag, actor)).to be(false)
+ expect(described_class.enabled?(:enabled_feature_flag)).to be(false)
+ end
+ end
+
describe Feature::Target do
describe '#targets' do
let(:project) { create(:project) }
diff --git a/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb b/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb
new file mode 100644
index 00000000000..021fb8f5f58
--- /dev/null
+++ b/spec/lib/generators/gitlab/usage_metric_definition/redis_hll_generator_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'generator_helper'
+
+RSpec.describe Gitlab::UsageMetricDefinition::RedisHllGenerator do
+ include UsageDataHelpers
+
+ let(:category) { 'test_category' }
+ let(:event) { 'i_test_event' }
+ let(:args) { [category, event] }
+ let(:temp_dir) { Dir.mktmpdir }
+
+ # Interpolating to preload the class
+ # See https://github.com/rspec/rspec-mocks/issues/1079
+ before do
+ stub_const("#{Gitlab::UsageMetricDefinitionGenerator}::TOP_LEVEL_DIR", temp_dir)
+ # Stub Prometheus requests from Gitlab::Utils::UsageData
+ stub_prometheus_queries
+ end
+
+ it 'creates metric definition files' do
+ described_class.new(args).invoke_all
+
+ weekly_metric_definition_path = Dir.glob(File.join(temp_dir, 'metrics/counts_7d/*i_test_event_weekly.yml')).first
+ monthly_metric_definition_path = Dir.glob(File.join(temp_dir, 'metrics/counts_28d/*i_test_event_monthly.yml')).first
+
+ expect(YAML.safe_load(File.read(weekly_metric_definition_path))).to include("key_path" => "redis_hll_counters.test_category.i_test_event_weekly")
+ expect(YAML.safe_load(File.read(monthly_metric_definition_path))).to include("key_path" => "redis_hll_counters.test_category.i_test_event_monthly")
+ end
+end
diff --git a/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb b/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb
index b62eac14e3e..f8c055ae111 100644
--- a/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb
+++ b/spec/lib/generators/gitlab/usage_metric_definition_generator_spec.rb
@@ -3,10 +3,42 @@
require 'generator_helper'
RSpec.describe Gitlab::UsageMetricDefinitionGenerator do
+ include UsageDataHelpers
+
+ let(:key_path) { 'counts_weekly.test_metric' }
+ let(:dir) { '7d' }
+ let(:temp_dir) { Dir.mktmpdir }
+
+ before do
+ stub_const("#{described_class}::TOP_LEVEL_DIR", temp_dir)
+ # Stub Prometheus requests from Gitlab::Utils::UsageData
+ stub_prometheus_queries
+ end
+
+ after do
+ FileUtils.rm_rf(temp_dir)
+ end
+
+ describe 'Creating metric definition file' do
+ # Stub version so that `milestone` key remains constant between releases to prevent flakiness.
+ before do
+ stub_const('Gitlab::VERSION', '13.9.0')
+ allow(::Gitlab::Usage::Metrics::NamesSuggestions::Generator).to receive(:generate).and_return('test metric name')
+ end
+
+ let(:sample_metric) { load_sample_metric_definition(filename: 'sample_metric_with_name_suggestions.yml') }
+
+ it 'creates a metric definition file using the template' do
+ described_class.new([key_path], { 'dir' => dir }).invoke_all
+
+ metric_definition_path = Dir.glob(File.join(temp_dir, 'metrics/counts_7d/*_test_metric.yml')).first
+
+ expect(YAML.safe_load(File.read(metric_definition_path))).to eq(sample_metric)
+ end
+ end
+
describe 'Validation' do
- let(:key_path) { 'counter.category.event' }
- let(:dir) { '7d' }
- let(:options) { [key_path, '--dir', dir, '--pretend'] }
+ let(:options) { [key_path, '--dir', dir] }
subject { described_class.start(options) }
@@ -42,34 +74,12 @@ RSpec.describe Gitlab::UsageMetricDefinitionGenerator do
end
describe 'Name suggestions' do
- let(:temp_dir) { Dir.mktmpdir }
-
- before do
- stub_const("#{described_class}::TOP_LEVEL_DIR", temp_dir)
- end
-
- context 'with product_intelligence_metrics_names_suggestions feature ON' do
- it 'adds name key to metric definition' do
- stub_feature_flags(product_intelligence_metrics_names_suggestions: true)
-
- expect(::Gitlab::Usage::Metrics::NamesSuggestions::Generator).to receive(:generate).and_return('some name')
- described_class.new(['counts_weekly.test_metric'], { 'dir' => '7d' }).invoke_all
- metric_definition_path = Dir.glob(File.join(temp_dir, 'metrics/counts_7d/*_test_metric.yml')).first
+ it 'adds name key to metric definition' do
+ expect(::Gitlab::Usage::Metrics::NamesSuggestions::Generator).to receive(:generate).and_return('some name')
+ described_class.new([key_path], { 'dir' => dir }).invoke_all
+ metric_definition_path = Dir.glob(File.join(temp_dir, 'metrics/counts_7d/*_test_metric.yml')).first
- expect(YAML.safe_load(File.read(metric_definition_path))).to include("name" => "some name")
- end
- end
-
- context 'with product_intelligence_metrics_names_suggestions feature OFF' do
- it 'adds name key to metric definition' do
- stub_feature_flags(product_intelligence_metrics_names_suggestions: false)
-
- expect(::Gitlab::Usage::Metrics::NamesSuggestions::Generator).not_to receive(:generate)
- described_class.new(['counts_weekly.test_metric'], { 'dir' => '7d' }).invoke_all
- metric_definition_path = Dir.glob(File.join(temp_dir, 'metrics/counts_7d/*_test_metric.yml')).first
-
- expect(YAML.safe_load(File.read(metric_definition_path)).keys).not_to include(:name)
- end
+ expect(YAML.safe_load(File.read(metric_definition_path))).to include("name" => "some name")
end
end
end
diff --git a/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb b/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
index fceda763717..1ed43145aa6 100644
--- a/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
+++ b/spec/lib/gitlab/alert_management/alert_status_counts_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe Gitlab::AlertManagement::AlertStatusCounts do
let_it_be(:alert_resolved) { create(:alert_management_alert, :resolved, project: project) }
let_it_be(:alert_ignored) { create(:alert_management_alert, :ignored, project: project) }
let_it_be(:alert_triggered) { create(:alert_management_alert) }
+
let(:params) { {} }
describe '#execute' do
diff --git a/spec/lib/gitlab/alert_management/payload/base_spec.rb b/spec/lib/gitlab/alert_management/payload/base_spec.rb
index 0c26e94e596..e093b3587c2 100644
--- a/spec/lib/gitlab/alert_management/payload/base_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/base_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload::Base do
let_it_be(:project) { create(:project) }
+
let(:raw_payload) { {} }
let(:payload_class) { described_class }
diff --git a/spec/lib/gitlab/alert_management/payload/generic_spec.rb b/spec/lib/gitlab/alert_management/payload/generic_spec.rb
index b0c238c62c8..59933f7459d 100644
--- a/spec/lib/gitlab/alert_management/payload/generic_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/generic_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload::Generic do
let_it_be(:project) { build_stubbed(:project) }
+
let(:raw_payload) { {} }
let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
diff --git a/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb b/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
index 862b5b2bdc3..fa8afd47c53 100644
--- a/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload::ManagedPrometheus do
let_it_be(:project) { create(:project) }
+
let(:raw_payload) { {} }
let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
@@ -136,6 +137,7 @@ RSpec.describe Gitlab::AlertManagement::Payload::ManagedPrometheus do
context 'with sufficient fallback info' do
let_it_be(:environment) { create(:environment, project: project, name: 'production') }
+
let(:raw_payload) do
{
'labels' => {
diff --git a/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb b/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb
index f574f5ba6a3..6a4f35c01e3 100644
--- a/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload::Prometheus do
let_it_be(:project) { create(:project) }
+
let(:raw_payload) { {} }
let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
diff --git a/spec/lib/gitlab/alert_management/payload_spec.rb b/spec/lib/gitlab/alert_management/payload_spec.rb
index 7c129a8a48e..efde7ed3772 100644
--- a/spec/lib/gitlab/alert_management/payload_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::AlertManagement::Payload do
describe '#parse' do
let_it_be(:project) { build_stubbed(:project) }
+
let(:payload) { {} }
context 'without a monitoring_tool specified by caller' do
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
index 80d3f82b404..0a333965f68 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
let_it_be(:mr1) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 3.months.ago) }
let_it_be(:mr2) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 1.month.ago) }
let_it_be(:user) { create(:user) }
+
let(:params) { { current_user: user } }
let(:records) do
stage = build(:cycle_analytics_project_stage, {
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
index c1ea000eb7b..14768025932 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Analytics::CycleAnalytics::Median do
let_it_be(:project) { create(:project, :repository) }
+
let(:query) { Project.joins(merge_requests: :metrics) }
let(:stage) do
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
index b8f9dde4291..ebc5ae2a632 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
@@ -7,16 +7,15 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
Timecop.freeze { example.run }
end
+ let(:params) { { from: 1.year.ago, current_user: user } }
+
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:user) { create(:user) }
subject do
Gitlab::Analytics::CycleAnalytics::DataCollector.new(
stage: stage,
- params: {
- from: 1.year.ago,
- current_user: user
- }
+ params: params
).records_fetcher.serialized_records
end
@@ -34,6 +33,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
describe 'for issue based stage' do
let_it_be(:issue1) { create(:issue, project: project) }
let_it_be(:issue2) { create(:issue, project: project, confidential: true) }
+
let(:stage) do
build(:cycle_analytics_project_stage, {
start_event_identifier: :plan_stage_start,
@@ -130,4 +130,40 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
end
end
end
+
+ describe 'pagination' do
+ let_it_be(:issue1) { create(:issue, project: project) }
+ let_it_be(:issue2) { create(:issue, project: project) }
+ let_it_be(:issue3) { create(:issue, project: project) }
+
+ let(:stage) do
+ build(:cycle_analytics_project_stage, {
+ start_event_identifier: :plan_stage_start,
+ end_event_identifier: :issue_first_mentioned_in_commit,
+ project: project
+ })
+ end
+
+ before(:all) do
+ issue1.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
+ issue2.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
+ issue3.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
+ end
+
+ before do
+ project.add_user(user, Gitlab::Access::DEVELOPER)
+
+ stub_const('Gitlab::Analytics::CycleAnalytics::RecordsFetcher::MAX_RECORDS', 2)
+ end
+
+ it 'limits the results' do
+ expect(subject.size).to eq(2)
+ end
+
+ it 'loads the record for the next page' do
+ params[:page] = 2
+
+ expect(subject.size).to eq(1)
+ end
+ end
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
index 52e9f2d9846..b6f9c8106c9 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::StageEvents::CodeStageStart do
other_merge_request = create(:merge_request, source_project: project, source_branch: 'a', target_branch: 'master')
- records = subject.apply_query_customization(MergeRequest.all).where('merge_requests_closing_issues.issue_id IS NOT NULL')
+ records = subject.apply_query_customization(MergeRequest.all).where.not('merge_requests_closing_issues.issue_id' => nil)
expect(records).to eq([merge_request])
expect(records).not_to include(other_merge_request)
end
diff --git a/spec/lib/gitlab/analytics/unique_visits_spec.rb b/spec/lib/gitlab/analytics/unique_visits_spec.rb
index 6ac58e13f4c..f4d5c0b1eca 100644
--- a/spec/lib/gitlab/analytics/unique_visits_spec.rb
+++ b/spec/lib/gitlab/analytics/unique_visits_spec.rb
@@ -24,18 +24,18 @@ RSpec.describe Gitlab::Analytics::UniqueVisits, :clean_gitlab_redis_shared_state
describe '#track_visit' do
it 'tracks the unique weekly visits for targets' do
- unique_visits.track_visit(visitor1_id, target1_id, 7.days.ago)
- unique_visits.track_visit(visitor1_id, target1_id, 7.days.ago)
- unique_visits.track_visit(visitor2_id, target1_id, 7.days.ago)
+ unique_visits.track_visit(target1_id, values: visitor1_id, time: 7.days.ago)
+ unique_visits.track_visit(target1_id, values: visitor1_id, time: 7.days.ago)
+ unique_visits.track_visit(target1_id, values: visitor2_id, time: 7.days.ago)
- unique_visits.track_visit(visitor2_id, target2_id, 7.days.ago)
- unique_visits.track_visit(visitor1_id, target2_id, 8.days.ago)
- unique_visits.track_visit(visitor1_id, target2_id, 15.days.ago)
+ unique_visits.track_visit(target2_id, values: visitor2_id, time: 7.days.ago)
+ unique_visits.track_visit(target2_id, values: visitor1_id, time: 8.days.ago)
+ unique_visits.track_visit(target2_id, values: visitor1_id, time: 15.days.ago)
- unique_visits.track_visit(visitor3_id, target4_id, 7.days.ago)
+ unique_visits.track_visit(target4_id, values: visitor3_id, time: 7.days.ago)
- unique_visits.track_visit(visitor3_id, target5_id, 15.days.ago)
- unique_visits.track_visit(visitor2_id, target5_id, 15.days.ago)
+ unique_visits.track_visit(target5_id, values: visitor3_id, time: 15.days.ago)
+ unique_visits.track_visit(target5_id, values: visitor2_id, time: 15.days.ago)
expect(unique_visits.unique_visits_for(targets: target1_id)).to eq(2)
expect(unique_visits.unique_visits_for(targets: target2_id)).to eq(1)
@@ -61,7 +61,7 @@ RSpec.describe Gitlab::Analytics::UniqueVisits, :clean_gitlab_redis_shared_state
end
it 'sets the keys in Redis to expire automatically after 12 weeks' do
- unique_visits.track_visit(visitor1_id, target1_id)
+ unique_visits.track_visit(target1_id, values: visitor1_id)
Gitlab::Redis::SharedState.with do |redis|
redis.scan_each(match: "{#{target1_id}}-*").each do |key|
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::Analytics::UniqueVisits, :clean_gitlab_redis_shared_state
invalid_target_id = "x_invalid"
expect do
- unique_visits.track_visit(visitor1_id, invalid_target_id)
+ unique_visits.track_visit(invalid_target_id, values: visitor1_id)
end.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent)
end
end
diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb
index 0fbbc67ef6a..c4fe2ebaba9 100644
--- a/spec/lib/gitlab/application_context_spec.rb
+++ b/spec/lib/gitlab/application_context_spec.rb
@@ -27,6 +27,20 @@ RSpec.describe Gitlab::ApplicationContext do
end
end
+ describe '.with_raw_context' do
+ it 'yields the block' do
+ expect { |b| described_class.with_raw_context({}, &b) }.to yield_control
+ end
+
+ it 'passes the attributes unaltered on to labkit' do
+ attrs = { foo: :bar }
+
+ expect(Labkit::Context).to receive(:with_context).with(attrs)
+
+ described_class.with_raw_context(attrs) {}
+ end
+ end
+
describe '.push' do
it 'passes the expected context on to labkit' do
fake_proc = duck_type(:call)
@@ -138,7 +152,7 @@ RSpec.describe Gitlab::ApplicationContext do
it 'does not cause queries' do
context = described_class.new(project: create(:project), namespace: create(:group, :nested), user: create(:user))
- expect { context.use { Labkit::Context.current.to_h } }.not_to exceed_query_limit(0)
+ expect { context.use { Gitlab::ApplicationContext.current } }.not_to exceed_query_limit(0)
end
end
end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 3eb015a5a22..f3799c58fed 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -83,7 +83,7 @@ module Gitlab
},
'fenced code with inline script' => {
input: '```mypre"><script>alert(3)</script>',
- output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"&gt;</span></code></pre>\n</div>\n</div>"
+ output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight language-plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"&gt;</span></code></pre>\n</div>\n</div>"
}
}
@@ -353,7 +353,7 @@ module Gitlab
output = <<~HTML
<div>
<div>
- <pre class="code highlight js-syntax-highlight javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
+ <pre class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre>
</div>
</div>
HTML
@@ -380,7 +380,7 @@ module Gitlab
<div>
<div>class.cpp</div>
<div>
- <pre class="code highlight js-syntax-highlight cpp" lang="cpp" v-pre="true"><code><span id="LC1" class="line" lang="cpp"><span class="cp">#include &lt;stdio.h&gt;</span></span>
+ <pre class="code highlight js-syntax-highlight language-cpp" lang="cpp" v-pre="true"><code><span id="LC1" class="line" lang="cpp"><span class="cp">#include &lt;stdio.h&gt;</span></span>
<span id="LC2" class="line" lang="cpp"></span>
<span id="LC3" class="line" lang="cpp"><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span></span>
<span id="LC4" class="line" lang="cpp"> <span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="o">&lt;&lt;</span><span class="s">"*"</span><span class="o">&lt;&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span></span>
diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
index 67ffdee0c4a..69068883096 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Auth::OAuth::AuthHash do
- let(:provider) { 'ldap'.freeze }
+ let(:provider) { 'ldap' }
let(:auth_hash) do
described_class.new(
OmniAuth::AuthHash.new(
diff --git a/spec/lib/gitlab/auth/otp/strategies/devise_spec.rb b/spec/lib/gitlab/auth/otp/strategies/devise_spec.rb
index 0c88421d456..e51705bdb9c 100644
--- a/spec/lib/gitlab/auth/otp/strategies/devise_spec.rb
+++ b/spec/lib/gitlab/auth/otp/strategies/devise_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth::Otp::Strategies::Devise do
let_it_be(:user) { create(:user) }
+
let(:otp_code) { 42 }
subject(:validate) { described_class.new(user).validate(otp_code) }
diff --git a/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb b/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb
index 88a245b6b10..dc20df98185 100644
--- a/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/otp/strategies/forti_authenticator_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth::Otp::Strategies::FortiAuthenticator do
let_it_be(:user) { create(:user) }
+
let(:otp_code) { 42 }
let(:host) { 'forti_authenticator.example.com' }
diff --git a/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb b/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb
index 368cf98dfec..57ee53a452e 100644
--- a/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb
+++ b/spec/lib/gitlab/auth/otp/strategies/forti_token_cloud_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth::Otp::Strategies::FortiTokenCloud do
let_it_be(:user) { create(:user) }
+
let(:otp_code) { 42 }
let(:url) { 'https://ftc.example.com:9696/api/v1' }
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 4e4bbd1bb60..7a578ad3c90 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
let_it_be(:project) { create(:project) }
+
let(:gl_auth) { described_class }
describe 'constants' do
@@ -543,6 +544,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'and belong to different projects' do
let_it_be(:other_project) { create(:project) }
+
let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) }
let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [other_project]) }
let(:auth_success) { Gitlab::Auth::Result.new(read_repository, other_project, :deploy_token, [:download_code]) }
diff --git a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
index 50e799908c6..dbf74bd9333 100644
--- a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2020_04_20_094444 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2021_03_13_045845 do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:users) { table(:users) }
let(:snippets) { table(:snippets) }
diff --git a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
index 7ad93c3124a..c4c0247ad3e 100644
--- a/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
+++ b/spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb
@@ -64,5 +64,13 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
expect(test_table.where('name is NULL and name_convert_to_text is NULL').pluck(:id)).to contain_exactly(15)
expect(test_table.where("name_convert_to_text = 'no name'").count).to eq(0)
end
+
+ it 'tracks timings of queries' do
+ expect(subject.batch_metrics.timings).to be_empty
+
+ subject.perform(10, 20, table_name, 'id', sub_batch_size, 'name', 'name_convert_to_text')
+
+ expect(subject.batch_metrics.timings[:update_all]).not_to be_empty
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/migrate_pages_to_zip_storage_spec.rb b/spec/lib/gitlab/background_migration/migrate_pages_to_zip_storage_spec.rb
new file mode 100644
index 00000000000..557dd8ddee6
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_pages_to_zip_storage_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::MigratePagesToZipStorage do
+ let(:namespace) { create(:group) } # rubocop: disable RSpec/FactoriesInMigrationSpecs
+ let(:migration) { described_class.new }
+
+ describe '#perform' do
+ context 'when there is project to migrate' do
+ let!(:project) { create_project('project') }
+
+ after do
+ FileUtils.rm_rf(project.pages_path)
+ end
+
+ it 'migrates project to zip storage' do
+ expect_next_instance_of(::Pages::MigrateFromLegacyStorageService,
+ anything,
+ ignore_invalid_entries: false,
+ mark_projects_as_not_deployed: false) do |service|
+ expect(service).to receive(:execute_for_batch).with(project.id..project.id).and_call_original
+ end
+
+ migration.perform(project.id, project.id)
+
+ expect(project.reload.pages_metadatum.pages_deployment.file.filename).to eq("_migrated.zip")
+ end
+ end
+ end
+
+ def create_project(path)
+ project = create(:project) # rubocop: disable RSpec/FactoriesInMigrationSpecs
+ project.mark_pages_as_deployed
+
+ FileUtils.mkdir_p File.join(project.pages_path, "public")
+ File.open(File.join(project.pages_path, "public/index.html"), "w") do |f|
+ f.write("Hello!")
+ end
+
+ project
+ end
+end
diff --git a/spec/lib/gitlab/bullet/exclusions_spec.rb b/spec/lib/gitlab/bullet/exclusions_spec.rb
new file mode 100644
index 00000000000..ba42156b0c4
--- /dev/null
+++ b/spec/lib/gitlab/bullet/exclusions_spec.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Bullet::Exclusions do
+ let(:config_file) do
+ file = Tempfile.new('bullet.yml')
+ File.basename(file)
+ end
+
+ let(:exclude) { [] }
+ let(:config) do
+ {
+ exclusions: {
+ abc: {
+ merge_request: '_mr_',
+ path_with_method: true,
+ exclude: exclude
+ }
+ }
+ }
+ end
+
+ before do
+ File.write(config_file, config.deep_stringify_keys.to_yaml)
+ end
+
+ after do
+ FileUtils.rm_f(config_file)
+ end
+
+ describe '#execute' do
+ subject(:executor) { described_class.new(config_file).execute }
+
+ shared_examples_for 'loads exclusion results' do
+ let(:config) { { exclusions: { abc: { exclude: exclude } } } }
+ let(:results) { [exclude] }
+
+ specify do
+ expect(executor).to match(results)
+ end
+ end
+
+ context 'with preferred method of path and method name' do
+ it_behaves_like 'loads exclusion results' do
+ let(:exclude) { %w[_path_ _method_] }
+ end
+ end
+
+ context 'with file pattern' do
+ it_behaves_like 'loads exclusion results' do
+ let(:exclude) { ['_file_pattern_'] }
+ end
+ end
+
+ context 'with file name and line range' do
+ it_behaves_like 'loads exclusion results' do
+ let(:exclude) { ['file_name.rb', 5..10] }
+ end
+ end
+
+ context 'without exclusions' do
+ it_behaves_like 'loads exclusion results' do
+ let(:exclude) { [] }
+ end
+ end
+
+ context 'without exclusions key in config' do
+ it_behaves_like 'loads exclusion results' do
+ let(:config) { {} }
+ let(:results) { [] }
+ end
+ end
+
+ context 'when config file does not exist' do
+ it 'provides an empty array for exclusions' do
+ expect(described_class.new('_some_bogus_file_').execute).to match([])
+ end
+ end
+ end
+
+ describe '#validate_paths!' do
+ context 'when validating scenarios' do
+ let(:source_file) do
+ file = Tempfile.new('bullet_test_source_file.rb')
+ File.basename(file)
+ end
+
+ subject { described_class.new(config_file).validate_paths! }
+
+ before do
+ FileUtils.touch(source_file)
+ end
+
+ after do
+ FileUtils.rm_f(source_file)
+ end
+
+ context 'when using paths with method name' do
+ let(:exclude) { [source_file, '_method_'] }
+
+ context 'when source file for exclusion exists' do
+ specify do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when source file for exclusion does not exist' do
+ let(:exclude) { %w[_bogus_file_ _method_] }
+
+ specify do
+ expect { subject }.to raise_error(RuntimeError)
+ end
+ end
+ end
+
+ context 'when using path only' do
+ let(:exclude) { [source_file] }
+
+ context 'when source file for exclusion exists' do
+ specify do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'when source file for exclusion does not exist' do
+ let(:exclude) { '_bogus_file_' }
+
+ specify do
+ expect { subject }.to raise_error(RuntimeError)
+ end
+ end
+ end
+
+ context 'when path_with_method is false for a file pattern' do
+ let(:exclude) { ['_file_pattern_'] }
+ let(:config) do
+ {
+ exclusions: {
+ abc: {
+ merge_request: '_mr_',
+ path_with_method: false,
+ exclude: exclude
+ }
+ }
+ }
+ end
+
+ specify do
+ expect { subject }.not_to raise_error
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bullet_spec.rb b/spec/lib/gitlab/bullet_spec.rb
new file mode 100644
index 00000000000..1262a0b8bde
--- /dev/null
+++ b/spec/lib/gitlab/bullet_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Bullet do
+ describe '#enabled?' do
+ it 'is enabled' do
+ stub_env('ENABLE_BULLET', true)
+
+ expect(described_class.enabled?).to be(true)
+ end
+
+ it 'is not enabled' do
+ stub_env('ENABLE_BULLET', nil)
+
+ expect(described_class.enabled?).to be(false)
+ end
+
+ it 'is correctly aliased for #extra_logging_enabled?' do
+ expect(described_class.method(:extra_logging_enabled?).original_name).to eq(:enabled?)
+ end
+ end
+
+ describe '#configure_bullet?' do
+ context 'with ENABLE_BULLET true' do
+ before do
+ stub_env('ENABLE_BULLET', true)
+ end
+
+ it 'is configurable' do
+ expect(described_class.configure_bullet?).to be(true)
+ end
+ end
+
+ context 'with ENABLE_BULLET falsey' do
+ before do
+ stub_env('ENABLE_BULLET', nil)
+ end
+
+ it 'is not configurable' do
+ expect(described_class.configure_bullet?).to be(false)
+ end
+
+ it 'is configurable in development' do
+ allow(Rails).to receive_message_chain(:env, :development?).and_return(true)
+
+ expect(described_class.configure_bullet?).to be(true)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/changelog/config_spec.rb b/spec/lib/gitlab/changelog/config_spec.rb
index 51988acf3d1..2809843b832 100644
--- a/spec/lib/gitlab/changelog/config_spec.rb
+++ b/spec/lib/gitlab/changelog/config_spec.rb
@@ -37,7 +37,8 @@ RSpec.describe Gitlab::Changelog::Config do
project,
'date_format' => 'foo',
'template' => 'bar',
- 'categories' => { 'foo' => 'bar' }
+ 'categories' => { 'foo' => 'bar' },
+ 'tag_regex' => 'foo'
)
expect(config.date_format).to eq('foo')
@@ -45,6 +46,7 @@ RSpec.describe Gitlab::Changelog::Config do
.to be_instance_of(Gitlab::Changelog::AST::Expressions)
expect(config.categories).to eq({ 'foo' => 'bar' })
+ expect(config.tag_regex).to eq('foo')
end
it 'raises Error when the categories are not a Hash' do
diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb
index f099f19b061..74e43b04b6b 100644
--- a/spec/lib/gitlab/checks/project_created_spec.rb
+++ b/spec/lib/gitlab/checks/project_created_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
+
let(:protocol) { 'http' }
let(:git_user) { user }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index c7dad0a91d4..469aea8d093 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
+
let(:repository) { project.repository }
let(:protocol) { 'http' }
let(:git_user) { user }
@@ -101,6 +102,7 @@ RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
context 'with project snippet' do
let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
+
let(:repository) { snippet.repository }
it_behaves_like 'errors per protocol' do
@@ -111,6 +113,7 @@ RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
context 'with personal snippet' do
let_it_be(:snippet) { create(:personal_snippet, :repository, author: user) }
+
let(:repository) { snippet.repository }
it 'returns nil' do
diff --git a/spec/lib/gitlab/ci/ansi2json/style_spec.rb b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
index ff70ff69aaa..87085950a9f 100644
--- a/spec/lib/gitlab/ci/ansi2json/style_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json/style_spec.rb
@@ -160,9 +160,9 @@ RSpec.describe Gitlab::Ci::Ansi2json::Style do
with_them do
it 'change the style' do
style = described_class.new
- style.update(initial_state)
+ style.update(initial_state) # rubocop:disable Rails/SaveBang
- style.update(ansi_commands)
+ style.update(ansi_commands) # rubocop:disable Rails/SaveBang
expect(style.to_s).to eq(result)
end
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index 179578fe0a8..d294eca7f15 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -107,6 +107,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
stage: 'test',
only: { refs: %w[branches tags] },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage)
end
end
@@ -130,6 +132,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
stage: 'test',
only: { refs: %w[branches tags] },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage)
end
end
@@ -284,6 +288,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Bridge do
parallel: { matrix: [{ 'PROVIDER' => ['aws'], 'STACK' => %w(monitoring app1) },
{ 'PROVIDER' => ['gcp'], 'STACK' => %w(data) }] },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage
)
end
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 064990667d5..cec1c97085b 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -13,6 +13,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Cache do
end
describe '#valid?' do
+ context 'with an empty hash as cache' do
+ let(:config) { {} }
+
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
context 'when configuration is valid with a single cache' do
let(:config) { { key: 'key', paths: ["logs/"], untracked: true } }
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index a4167003987..ffcd029172a 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -663,6 +663,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job do
after_script: %w[cleanup],
only: { refs: %w[branches tags] },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index ac6b589ec6b..cb73044b62b 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -100,6 +100,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do
stage: 'test',
trigger: { project: 'my/project' },
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage
},
regular_job: {
@@ -109,6 +111,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Jobs do
script: ['something'],
stage: 'test',
variables: {},
+ job_variables: {},
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index 04e80450263..016d59e98b9 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -382,7 +382,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
context 'with only job variables' do
it 'does return defined variables' do
expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job' }
+ variables: { 'A' => 'job', 'B' => 'job' },
+ job_variables: { 'A' => 'job', 'B' => 'job' },
+ root_variables_inheritance: true
)
end
end
@@ -394,9 +396,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
).value
end
- it 'does return all variables and overwrite them' do
+ it 'does return job and root variables' do
expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root', 'D' => 'root' }
+ variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root', 'D' => 'root' },
+ job_variables: { 'A' => 'job', 'B' => 'job' },
+ root_variables_inheritance: true
)
end
@@ -408,9 +412,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
}
end
- it 'does return only job variables' do
+ it 'does return job and root variables' do
expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job' }
+ variables: { 'A' => 'job', 'B' => 'job' },
+ job_variables: { 'A' => 'job', 'B' => 'job' },
+ root_variables_inheritance: false
)
end
end
@@ -423,9 +429,11 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
}
end
- it 'does return only job variables' do
+ it 'does return job and root variables' do
expect(entry.value).to include(
- variables: { 'A' => 'job', 'B' => 'job', 'D' => 'root' }
+ variables: { 'A' => 'job', 'B' => 'job', 'D' => 'root' },
+ job_variables: { 'A' => 'job', 'B' => 'job' },
+ root_variables_inheritance: ['D']
)
end
end
@@ -493,7 +501,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
name: :rspec,
stage: 'test',
only: { refs: %w[branches tags] },
- variables: {}
+ variables: {},
+ job_variables: {},
+ root_variables_inheritance: true
)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 7b38c21788f..041eb748fc9 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -133,6 +133,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -147,6 +149,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -163,6 +167,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }],
only: { refs: %w(branches tags) },
variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
+ job_variables: { 'VAR' => 'job' },
+ root_variables_inheritance: true,
after_script: [],
ignore: false,
scheduling_type: :stage }
@@ -188,6 +194,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -202,6 +210,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root', 'VAR2' => 'val 2' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -218,6 +228,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' },
only: { refs: %w(branches tags) },
variables: { 'VAR' => 'job', 'VAR2' => 'val 2' },
+ job_variables: { 'VAR' => 'job' },
+ root_variables_inheritance: true,
after_script: [],
ignore: false,
scheduling_type: :stage }
@@ -267,6 +279,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'root' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -279,6 +293,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' },
variables: { 'VAR' => 'job' },
+ job_variables: { 'VAR' => 'job' },
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -311,6 +327,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'root' },
+ job_variables: {},
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -323,6 +341,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
variables: { 'VAR' => 'job' },
+ job_variables: { 'VAR' => 'job' },
+ root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 99f546ceb37..e5b008a482e 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -324,5 +324,39 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
end
end
end
+
+ context 'when local file path has wildcard' do
+ let(:project) { create(:project, :repository) }
+
+ let(:values) do
+ { include: 'myfolder/*.yml' }
+ end
+
+ before do
+ allow_next_instance_of(Repository) do |repository|
+ allow(repository).to receive(:search_files_by_wildcard_path).with('myfolder/*.yml', '123456') do
+ ['myfolder/file1.yml', 'myfolder/file2.yml']
+ end
+ end
+ end
+
+ it 'includes the matched local files' do
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Local),
+ an_instance_of(Gitlab::Ci::Config::External::File::Local))
+
+ expect(subject.map(&:location)).to contain_exactly('myfolder/file1.yml', 'myfolder/file2.yml')
+ end
+
+ context 'when the FF ci_wildcard_file_paths is disabled' do
+ before do
+ stub_feature_flags(ci_wildcard_file_paths: false)
+ end
+
+ it 'cannot find any file returns an error message' do
+ expect(subject).to contain_exactly(an_instance_of(Gitlab::Ci::Config::External::File::Local))
+ expect(subject[0].errors).to eq(['Local file `myfolder/*.yml` does not exist!'])
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index d2d7116bb12..d657c3e943f 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -366,5 +366,40 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
expect(output.keys).to match_array([:image, :my_build, :my_test])
end
end
+
+ context 'when local file path has wildcard' do
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:values) do
+ { include: 'myfolder/*.yml', image: 'ruby:2.7' }
+ end
+
+ before do
+ allow_next_instance_of(Repository) do |repository|
+ allow(repository).to receive(:search_files_by_wildcard_path).with('myfolder/*.yml', sha) do
+ ['myfolder/file1.yml', 'myfolder/file2.yml']
+ end
+
+ allow(repository).to receive(:blob_data_at).with(sha, 'myfolder/file1.yml') do
+ <<~HEREDOC
+ my_build:
+ script: echo Hello World
+ HEREDOC
+ end
+
+ allow(repository).to receive(:blob_data_at).with(sha, 'myfolder/file2.yml') do
+ <<~HEREDOC
+ my_test:
+ script: echo Hello World
+ HEREDOC
+ end
+ end
+ end
+
+ it 'fetches the matched files' do
+ output = processor.perform
+ expect(output.keys).to match_array([:image, :my_build, :my_test])
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb
index fbf86927bd9..e5f0341c5fe 100644
--- a/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer/matrix_strategy_spec.rb
@@ -1,8 +1,12 @@
# frozen_string_literal: true
require 'fast_spec_helper'
+require 'support/helpers/stubbed_feature'
+require 'support/helpers/stub_feature_flags'
RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
+ include StubFeatureFlags
+
describe '.applies_to?' do
subject { described_class.applies_to?(config) }
@@ -49,6 +53,10 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app1'
+ },
+ job_variables: {
+ 'PROVIDER' => 'aws',
+ 'STACK' => 'app1'
}
},
{
@@ -58,6 +66,10 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app2'
+ },
+ job_variables: {
+ 'PROVIDER' => 'aws',
+ 'STACK' => 'app2'
}
},
{
@@ -67,6 +79,10 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
variables: {
'PROVIDER' => 'ovh',
'STACK' => 'app'
+ },
+ job_variables: {
+ 'PROVIDER' => 'ovh',
+ 'STACK' => 'app'
}
},
{
@@ -76,6 +92,10 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
variables: {
'PROVIDER' => 'gcp',
'STACK' => 'app'
+ },
+ job_variables: {
+ 'PROVIDER' => 'gcp',
+ 'STACK' => 'app'
}
}
]
diff --git a/spec/lib/gitlab/ci/lint_spec.rb b/spec/lib/gitlab/ci/lint_spec.rb
index 67324c09d86..aaa3a7a8b9d 100644
--- a/spec/lib/gitlab/ci/lint_spec.rb
+++ b/spec/lib/gitlab/ci/lint_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::Ci::Lint do
it 'sets merged_config' do
root_config = YAML.safe_load(content, [Symbol])
included_config = YAML.safe_load(included_content, [Symbol])
- expected_config = included_config.merge(root_config).except(:include)
+ expected_config = included_config.merge(root_config).except(:include).deep_stringify_keys
expect(subject.merged_yaml).to eq(expected_config.to_yaml)
end
diff --git a/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
index c6b8cf2a985..6a08e8f0b7f 100644
--- a/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/codequality/code_climate_spec.rb
@@ -131,7 +131,6 @@ RSpec.describe Gitlab::Ci::Parsers::Codequality::CodeClimate do
expect { parse }.not_to raise_error
expect(codequality_report.degradations_count).to eq(0)
- expect(codequality_report.error_message).to eq("Invalid degradation format: The property '#/' did not contain a required property of 'location'")
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 9ca5aeeea58..900dfec38e2 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -321,4 +321,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected.to be_falsey }
end
end
+
+ describe '#increment_pipeline_failure_reason_counter' do
+ let(:command) { described_class.new }
+ let(:reason) { :size_limit_exceeded }
+
+ subject { command.increment_pipeline_failure_reason_counter(reason) }
+
+ it 'increments the error metric' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { subject }.to change { counter.get(reason: reason.to_s) }.by(1)
+ end
+
+ context 'when the reason is nil' do
+ let(:reason) { nil }
+
+ it 'increments the error metric with unknown_failure' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { subject }.to change { counter.get(reason: 'unknown_failure') }.by(1)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
index 4ae51ac8bf9..e30a78546af 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
@@ -16,8 +16,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
describe '#perform!' do
context 'when pipeline has been skipped by workflow configuration' do
before do
- allow(step).to receive(:workflow_passed?)
- .and_return(false)
+ allow(step).to receive(:workflow_rules_result)
+ .and_return(
+ double(pass?: false, variables: {})
+ )
step.perform!
end
@@ -33,12 +35,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
it 'attaches an error to the pipeline' do
expect(pipeline.errors[:base]).to include('Pipeline filtered out by workflow rules.')
end
+
+ it 'saves workflow_rules_result' do
+ expect(command.workflow_rules_result.variables).to eq({})
+ end
end
context 'when pipeline has not been skipped by workflow configuration' do
before do
- allow(step).to receive(:workflow_passed?)
- .and_return(true)
+ allow(step).to receive(:workflow_rules_result)
+ .and_return(
+ double(pass?: true, variables: { 'VAR1' => 'val2' })
+ )
step.perform!
end
@@ -55,6 +63,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
it 'attaches no errors' do
expect(pipeline.errors).to be_empty
end
+
+ it 'saves workflow_rules_result' do
+ expect(command.workflow_rules_result.variables).to eq({ 'VAR1' => 'val2' })
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb
new file mode 100644
index 00000000000..bcea6462790
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers do
+ let(:helper_class) do
+ Class.new do
+ include Gitlab::Ci::Pipeline::Chain::Helpers
+
+ attr_accessor :pipeline, :command
+
+ def initialize(pipeline, command)
+ self.pipeline = pipeline
+ self.command = command
+ end
+ end
+ end
+
+ subject(:helper) { helper_class.new(pipeline, command) }
+
+ let(:pipeline) { build(:ci_empty_pipeline) }
+ let(:command) { double(save_incompleted: true) }
+ let(:message) { 'message' }
+
+ describe '.error' do
+ shared_examples 'error function' do
+ specify do
+ expect(pipeline).to receive(:drop!).with(drop_reason).and_call_original
+ expect(pipeline).to receive(:add_error_message).with(message).and_call_original
+ expect(pipeline).to receive(:ensure_project_iid!).twice.and_call_original
+
+ subject.error(message, config_error: config_error, drop_reason: drop_reason)
+
+ expect(pipeline.yaml_errors).to eq(yaml_error)
+ expect(pipeline.errors[:base]).to include(message)
+ end
+ end
+
+ context 'when given a drop reason' do
+ context 'when config error is true' do
+ context 'sets the yaml error and overrides the drop reason' do
+ let(:drop_reason) { :config_error }
+ let(:config_error) { true }
+ let(:yaml_error) { message }
+
+ it_behaves_like "error function"
+ end
+ end
+
+ context 'when config error is false' do
+ context 'does not set the yaml error or override the drop reason' do
+ let(:drop_reason) { :size_limit_exceeded }
+ let(:config_error) { false }
+ let(:yaml_error) { nil }
+
+ it_behaves_like "error function"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
index 78363be7f36..23cdec61bb3 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
let(:save_incompleted) { false }
let(:command) do
- double(:command,
+ Gitlab::Ci::Pipeline::Chain::Command.new(
project: project,
pipeline_seed: pipeline_seed,
save_incompleted: save_incompleted
@@ -49,6 +49,11 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
expect(pipeline.deployments_limit_exceeded?).to be true
end
+
+ it 'calls increment_pipeline_failure_reason_counter' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { perform }.to change { counter.get(reason: 'deployments_limit_exceeded') }.by(1)
+ end
end
context 'when not saving incomplete pipelines' do
@@ -71,6 +76,12 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
expect(pipeline.errors.messages).to include(base: ['Pipeline has too many deployments! Requested 2, but the limit is 1.'])
end
+
+ it 'increments the error metric' do
+ expect(command).to receive(:increment_pipeline_failure_reason_counter).with(:deployments_limit_exceeded)
+
+ perform
+ end
end
it 'logs the error' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/pipeline/process_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/pipeline/process_spec.rb
new file mode 100644
index 00000000000..3885cea2d1b
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/pipeline/process_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Pipeline::Chain::Pipeline::Process do
+ let_it_be(:project) { build(:project) }
+ let_it_be(:user) { build(:user) }
+ let_it_be(:pipeline) { build(:ci_pipeline, project: project, id: 42) }
+
+ let_it_be(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
+ end
+
+ let(:step) { described_class.new(pipeline, command) }
+
+ describe '#perform!' do
+ subject(:perform) { step.perform! }
+
+ it 'schedules a job to process the pipeline' do
+ expect(Ci::InitialPipelineProcessWorker)
+ .to receive(:perform_async)
+ .with(42)
+
+ perform
+ end
+ end
+
+ describe '#break?' do
+ it { expect(step.break?).to be_falsey }
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 5506b079d0f..62de4d2e96d 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -22,6 +22,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
[
Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command),
+ Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::SeedBlock.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::Seed.new(pipeline, command)
]
@@ -95,6 +96,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
it 'wastes pipeline iid' do
expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0
end
+
+ it 'increments the error metric' do
+ counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
+ expect { run_chain }.to change { counter.get(reason: 'unknown_failure') }.by(1)
+ end
end
describe 'pipeline protect' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
index 80013cab6ee..264076859cb 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -3,24 +3,16 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
- let(:project) { create(:project, :repository) }
- let(:user) { create(:user, developer_projects: [project]) }
- let(:seeds_block) { }
-
- let(:command) do
- Gitlab::Ci::Pipeline::Chain::Command.new(
- project: project,
- current_user: user,
- origin_ref: 'master',
- seeds_block: seeds_block)
- end
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user, developer_projects: [project]) }
+ let(:seeds_block) { }
+ let(:command) { initialize_command }
let(:pipeline) { build(:ci_pipeline, project: project) }
describe '#perform!' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
- run_chain
end
let(:config) do
@@ -28,23 +20,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
subject(:run_chain) do
- [
- Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
- Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command)
- ].map(&:perform!)
-
- described_class.new(pipeline, command).perform!
+ run_previous_chain(pipeline, command)
+ perform_seed(pipeline, command)
end
it 'allocates next IID' do
+ run_chain
+
expect(pipeline.iid).to be_present
end
it 'ensures ci_ref' do
+ run_chain
+
expect(pipeline.ci_ref).to be_present
end
it 'sets the seeds in the command object' do
+ run_chain
+
expect(command.pipeline_seed).to be_a(Gitlab::Ci::Pipeline::Seed::Pipeline)
expect(command.pipeline_seed.size).to eq 1
end
@@ -59,6 +53,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'correctly fabricates stages and builds' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.stages.size).to eq 2
@@ -84,6 +80,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'returns pipeline seed with jobs only assigned to master' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 1
@@ -103,6 +101,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'returns pipeline seed with jobs only assigned to schedules' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 1
@@ -130,6 +130,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
let(:pipeline) { build(:ci_pipeline, project: project) }
it 'returns seeds for kubernetes dependent job' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 2
@@ -141,6 +143,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
context 'when kubernetes is not active' do
it 'does not return seeds for kubernetes dependent job' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 1
@@ -158,6 +162,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'returns stage seeds only when variables expression is truthy' do
+ run_chain
+
seed = command.pipeline_seed
expect(seed.size).to eq 1
@@ -171,8 +177,125 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'does not execute the block' do
+ run_chain
+
expect(pipeline.variables.size).to eq(0)
end
end
+
+ describe '#root_variables' do
+ let(:config) do
+ {
+ variables: { VAR1: 'var 1' },
+ workflow: {
+ rules: [{ if: '$CI_PIPELINE_SOURCE',
+ variables: { VAR1: 'overridden var 1' } },
+ { when: 'always' }]
+ },
+ rspec: { script: 'rake' }
+ }
+ end
+
+ let(:rspec_variables) { command.pipeline_seed.stages[0].statuses[0].variables.to_hash }
+
+ it 'sends root variable with overridden by rules' do
+ run_chain
+
+ expect(rspec_variables['VAR1']).to eq('overridden var 1')
+ end
+
+ context 'when the FF ci_workflow_rules_variables is disabled' do
+ before do
+ stub_feature_flags(ci_workflow_rules_variables: false)
+ end
+
+ it 'sends root variable' do
+ run_chain
+
+ expect(rspec_variables['VAR1']).to eq('var 1')
+ end
+ end
+ end
+
+ context 'N+1 queries' do
+ it 'avoids N+1 queries when calculating variables of jobs' do
+ pipeline1, command1 = prepare_pipeline1
+ pipeline2, command2 = prepare_pipeline2
+
+ control = ActiveRecord::QueryRecorder.new do
+ perform_seed(pipeline1, command1)
+ end
+
+ expect { perform_seed(pipeline2, command2) }.not_to exceed_query_limit(
+ control.count + expected_extra_queries
+ )
+ end
+
+ private
+
+ def prepare_pipeline1
+ config1 = { build: { stage: 'build', script: 'build' } }
+ stub_ci_pipeline_yaml_file(YAML.dump(config1))
+ pipeline1 = build(:ci_pipeline, project: project)
+ command1 = initialize_command
+
+ run_previous_chain(pipeline1, command1)
+
+ [pipeline1, command1]
+ end
+
+ def prepare_pipeline2
+ config2 = { build1: { stage: 'build', script: 'build1' },
+ build2: { stage: 'build', script: 'build2' },
+ test: { stage: 'build', script: 'test' } }
+ stub_ci_pipeline_yaml_file(YAML.dump(config2))
+ pipeline2 = build(:ci_pipeline, project: project)
+ command2 = initialize_command
+
+ run_previous_chain(pipeline2, command2)
+
+ [pipeline2, command2]
+ end
+
+ def expected_extra_queries
+ extra_jobs = 2
+ non_handled_sql_queries = 3
+
+ # 1. Ci::Build Load () SELECT "ci_builds".* FROM "ci_builds"
+ # WHERE "ci_builds"."type" = 'Ci::Build'
+ # AND "ci_builds"."commit_id" IS NULL
+ # AND ("ci_builds"."retried" = FALSE OR "ci_builds"."retried" IS NULL)
+ # AND (stage_idx < 1)
+ # 2. Ci::InstanceVariable Load => `Ci::InstanceVariable#cached_data` => already cached with `fetch_memory_cache`
+ # 3. Ci::Variable Load => `Project#ci_variables_for` => already cached with `Gitlab::SafeRequestStore`
+
+ extra_jobs * non_handled_sql_queries
+ end
+ end
+
+ private
+
+ def run_previous_chain(pipeline, command)
+ [
+ Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
+ Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command),
+ Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules.new(pipeline, command)
+ ].map(&:perform!)
+ end
+
+ def perform_seed(pipeline, command)
+ described_class.new(pipeline, command).perform!
+ end
+ end
+
+ private
+
+ def initialize_command
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ project: project,
+ current_user: user,
+ origin_ref: 'master',
+ seeds_block: seeds_block
+ )
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
index e55281f9705..caf3a053c4e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
let(:pipeline) { build(:ci_empty_pipeline, user: user, project: project) }
let!(:step) { described_class.new(pipeline, command) }
@@ -42,6 +42,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
let(:save_incompleted) { true }
+ let(:dot_com) { true }
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project, current_user: user, yaml_processor_result: yaml_processor_result, save_incompleted: save_incompleted
@@ -51,11 +52,79 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
describe '#perform!' do
subject(:perform!) { step.perform! }
- context 'when validation returns true' do
+ let(:validation_service_url) { 'https://validation-service.external/' }
+
+ before do
+ stub_env('EXTERNAL_VALIDATION_SERVICE_URL', validation_service_url)
+ allow(Gitlab).to receive(:com?).and_return(dot_com)
+ allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('correlation-id')
+ end
+
+ context 'with configuration values in ApplicationSetting' do
+ let(:alternate_validation_service_url) { 'https://alternate-validation-service.external/' }
+ let(:validation_service_token) { 'SECURE_TOKEN' }
+ let(:shorter_timeout) { described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT - 1 }
+
before do
- allow(step).to receive(:validate_external).and_return(true)
+ stub_env('EXTERNAL_VALIDATION_SERVICE_TOKEN', 'TOKEN_IN_ENV')
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:external_pipeline_validation_service_timeout).and_return(shorter_timeout)
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:external_pipeline_validation_service_token).and_return(validation_service_token)
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:external_pipeline_validation_service_url).and_return(alternate_validation_service_url)
+ end
+
+ it 'uses those values rather than env vars or defaults' do
+ expect(::Gitlab::HTTP).to receive(:post) do |url, params|
+ expect(url).to eq(alternate_validation_service_url)
+ expect(params[:timeout]).to eq(shorter_timeout)
+ expect(params[:headers]).to include('X-Gitlab-Token' => validation_service_token)
+ expect(params[:timeout]).to eq(shorter_timeout)
+ end
+
+ perform!
+ end
+ end
+
+ it 'respects the defined payload schema' do
+ expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
+ expect(params[:body]).to match_schema('/external_validation')
+ expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)
+ expect(params[:headers]).to eq({ 'X-Gitlab-Correlation-id' => 'correlation-id' })
+ end
+
+ perform!
+ end
+
+ context 'with EXTERNAL_VALIDATION_SERVICE_TIMEOUT defined' do
+ before do
+ stub_env('EXTERNAL_VALIDATION_SERVICE_TIMEOUT', validation_service_timeout)
+ end
+
+ context 'with valid value' do
+ let(:validation_service_timeout) { '1' }
+
+ it 'uses defined timeout' do
+ expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
+ expect(params[:timeout]).to eq(1)
+ end
+
+ perform!
+ end
+ end
+
+ context 'with invalid value' do
+ let(:validation_service_timeout) { '??' }
+
+ it 'uses default timeout' do
+ expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
+ expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)
+ end
+
+ perform!
+ end
end
+ end
+ shared_examples 'successful external authorization' do
it 'does not drop the pipeline' do
perform!
@@ -76,9 +145,117 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
end
- context 'when validation return false' do
+ context 'when EXTERNAL_VALIDATION_SERVICE_TOKEN is set' do
+ before do
+ stub_env('EXTERNAL_VALIDATION_SERVICE_TOKEN', '123')
+ end
+
+ it 'passes token in X-Gitlab-Token header' do
+ expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
+ expect(params[:headers]).to include({ 'X-Gitlab-Token' => '123' })
+ end
+
+ perform!
+ end
+ end
+
+ context 'when validation returns 200 OK' do
+ before do
+ stub_request(:post, validation_service_url).to_return(status: 200, body: "{}")
+ end
+
+ it_behaves_like 'successful external authorization'
+ end
+
+ context 'when validation returns 404 Not Found' do
before do
- allow(step).to receive(:validate_external).and_return(false)
+ stub_request(:post, validation_service_url).to_return(status: 404, body: "{}")
+ end
+
+ it_behaves_like 'successful external authorization'
+ end
+
+ context 'when validation returns 500 Internal Server Error' do
+ before do
+ stub_request(:post, validation_service_url).to_return(status: 500, body: "{}")
+ end
+
+ it_behaves_like 'successful external authorization'
+ end
+
+ context 'when validation raises exceptions' do
+ before do
+ stub_request(:post, validation_service_url).to_raise(Net::OpenTimeout)
+ end
+
+ it_behaves_like 'successful external authorization'
+
+ it 'logs exceptions' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(instance_of(Net::OpenTimeout), { project_id: project.id })
+
+ perform!
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_external_validation_service: false)
+ stub_request(:post, validation_service_url)
+ end
+
+ it 'does not drop the pipeline' do
+ perform!
+
+ expect(pipeline.status).not_to eq('failed')
+ expect(pipeline.errors).to be_empty
+ end
+
+ it 'does not break the chain' do
+ perform!
+
+ expect(step.break?).to be false
+ end
+
+ it 'does not make requests' do
+ perform!
+
+ expect(WebMock).not_to have_requested(:post, validation_service_url)
+ end
+ end
+
+ context 'when not on .com' do
+ let(:dot_com) { false }
+
+ before do
+ stub_feature_flags(ci_external_validation_service: false)
+ stub_request(:post, validation_service_url).to_return(status: 404, body: "{}")
+ end
+
+ it 'drops the pipeline' do
+ perform!
+
+ expect(pipeline.status).to eq('failed')
+ expect(pipeline).to be_persisted
+ expect(pipeline.errors.to_a).to include('External validation failed')
+ end
+
+ it 'breaks the chain' do
+ perform!
+
+ expect(step.break?).to be true
+ end
+
+ it 'logs the authorization' do
+ expect(Gitlab::AppLogger).to receive(:info).with(message: 'Pipeline not authorized', project_id: project.id, user_id: user.id)
+
+ perform!
+ end
+ end
+
+ context 'when validation returns 406 Not Acceptable' do
+ before do
+ stub_request(:post, validation_service_url).to_return(status: 406, body: "{}")
end
it 'drops the pipeline' do
@@ -126,16 +303,4 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
end
end
-
- describe '#validation_service_payload' do
- subject(:validation_service_payload) { step.send(:validation_service_payload, pipeline, command.yaml_processor_result.stages_attributes) }
-
- it 'respects the defined schema' do
- expect(validation_service_payload).to match_schema('/external_validation')
- end
-
- it 'does not fire sql queries' do
- expect { validation_service_payload }.not_to exceed_query_limit(1)
- end
- end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 7ec6949f852..f97935feb86 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -6,10 +6,12 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:head_sha) { project.repository.head_commit.id }
let(:pipeline) { build(:ci_empty_pipeline, project: project, sha: head_sha) }
+ let(:root_variables) { [] }
+ let(:seed_context) { double(pipeline: pipeline, root_variables: root_variables) }
let(:attributes) { { name: 'rspec', ref: 'master', scheduling_type: :stage } }
let(:previous_stages) { [] }
- let(:seed_build) { described_class.new(pipeline, attributes, previous_stages) }
+ let(:seed_build) { described_class.new(seed_context, attributes, previous_stages) }
describe '#attributes' do
subject { seed_build.attributes }
@@ -75,8 +77,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let(:attributes) do
{ name: 'rspec',
ref: 'master',
- yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true },
- { key: 'VAR2', value: 'var 2', public: true }],
+ job_variables: [{ key: 'VAR1', value: 'var 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true }],
rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
end
@@ -301,6 +303,133 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.to match a_hash_including(options: { allow_failure_criteria: nil }) }
end
end
+
+ context 'with workflow:rules:[variables:]' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }],
+ job_variables: [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }],
+ root_variables_inheritance: root_variables_inheritance }
+ end
+
+ context 'when the pipeline has variables' do
+ let(:root_variables) do
+ [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true },
+ { key: 'VAR2', value: 'var pipeline 2', public: true },
+ { key: 'VAR3', value: 'var pipeline 3', public: true },
+ { key: 'VAR4', value: 'new var pipeline 4', public: true }]
+ end
+
+ context 'when root_variables_inheritance is true' do
+ let(:root_variables_inheritance) { true }
+
+ it 'returns calculated yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true },
+ { key: 'VAR4', value: 'new var pipeline 4', public: true }]
+ )
+ end
+
+ context 'when FF ci_workflow_rules_variables is disabled' do
+ before do
+ stub_feature_flags(ci_workflow_rules_variables: false)
+ end
+
+ it 'returns existing yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }]
+ )
+ end
+ end
+ end
+
+ context 'when root_variables_inheritance is false' do
+ let(:root_variables_inheritance) { false }
+
+ it 'returns job variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }]
+ )
+ end
+ end
+
+ context 'when root_variables_inheritance is an array' do
+ let(:root_variables_inheritance) { %w(VAR1 VAR2 VAR3) }
+
+ it 'returns calculated yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR1', value: 'var overridden pipeline 1', public: true },
+ { key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }]
+ )
+ end
+ end
+ end
+
+ context 'when the pipeline has not a variable' do
+ let(:root_variables_inheritance) { true }
+
+ it 'returns seed yaml variables' do
+ expect(subject[:yaml_variables]).to match_array(
+ [{ key: 'VAR2', value: 'var 2', public: true },
+ { key: 'VAR3', value: 'var 3', public: true }])
+ end
+ end
+ end
+
+ context 'when the job rule depends on variables' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ yaml_variables: [{ key: 'VAR1', value: 'var 1', public: true }],
+ job_variables: [{ key: 'VAR1', value: 'var 1', public: true }],
+ root_variables_inheritance: root_variables_inheritance,
+ rules: rules }
+ end
+
+ let(:root_variables_inheritance) { true }
+
+ context 'when the rules use job variables' do
+ let(:rules) do
+ [{ if: '$VAR1 == "var 1"', variables: { VAR1: 'overridden var 1', VAR2: 'new var 2' } }]
+ end
+
+ it 'recalculates the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true },
+ { key: 'VAR2', value: 'new var 2', public: true })
+ end
+ end
+
+ context 'when the rules use root variables' do
+ let(:root_variables) do
+ [{ key: 'VAR2', value: 'var pipeline 2', public: true }]
+ end
+
+ let(:rules) do
+ [{ if: '$VAR2 == "var pipeline 2"', variables: { VAR1: 'overridden var 1', VAR2: 'overridden var 2' } }]
+ end
+
+ it 'recalculates the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1', public: true },
+ { key: 'VAR2', value: 'overridden var 2', public: true })
+ end
+
+ context 'when the root_variables_inheritance is false' do
+ let(:root_variables_inheritance) { false }
+
+ it 'does not recalculate the variables' do
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1', public: true })
+ end
+ end
+ end
+ end
end
describe '#bridge?' do
@@ -377,7 +506,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it 'does not have environment' do
expect(subject).not_to be_has_environment
expect(subject.environment).to be_nil
- expect(subject.metadata).to be_nil
+ expect(subject.metadata&.expanded_environment_name).to be_nil
expect(Environment.exists?(name: expected_environment_name)).to eq(false)
end
end
@@ -1080,7 +1209,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
end
let(:stage_seed) do
- Gitlab::Ci::Pipeline::Seed::Stage.new(pipeline, stage_attributes, [])
+ Gitlab::Ci::Pipeline::Seed::Stage.new(seed_context, stage_attributes, [])
end
let(:previous_stages) { [stage_seed] }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
index 860b07647bd..21be8660def 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/pipeline_spec.rb
@@ -6,6 +6,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:seed_context) { double(pipeline: pipeline, root_variables: []) }
+
let(:stages_attributes) do
[
{
@@ -29,7 +31,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
end
subject(:seed) do
- described_class.new(pipeline, stages_attributes)
+ described_class.new(seed_context, stages_attributes)
end
describe '#stages' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index 4b9db9fa6c6..5b04d2abd88 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Stage do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:previous_stages) { [] }
+ let(:seed_context) { double(pipeline: pipeline, root_variables: []) }
let(:attributes) do
{ name: 'test',
@@ -16,7 +17,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Stage do
end
subject do
- described_class.new(pipeline, attributes, previous_stages)
+ described_class.new(seed_context, attributes, previous_stages)
end
describe '#size' do
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
index b322e55cb5a..8378d096fcf 100644
--- a/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_comparer_spec.rb
@@ -6,15 +6,17 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
let(:comparer) { described_class.new(base_report, head_report) }
let(:base_report) { Gitlab::Ci::Reports::CodequalityReports.new }
let(:head_report) { Gitlab::Ci::Reports::CodequalityReports.new }
- let(:degradation_1) { build(:codequality_degradation_1) }
- let(:degradation_2) { build(:codequality_degradation_2) }
+ let(:major_degradation) { build(:codequality_degradation, :major) }
+ let(:minor_degradation) { build(:codequality_degradation, :major) }
+ let(:critical_degradation) { build(:codequality_degradation, :critical) }
+ let(:blocker_degradation) { build(:codequality_degradation, :blocker) }
describe '#status' do
subject(:report_status) { comparer.status }
context 'when head report has an error' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns status failed' do
@@ -50,7 +52,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when head report has an error' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns the number of new errors' do
@@ -70,8 +72,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has an error and head has a different error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'counts the base report error as resolved' do
@@ -81,7 +83,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors head has no errors' do
before do
- base_report.add_degradation(degradation_1)
+ base_report.add_degradation(major_degradation)
end
it 'counts the base report errors as resolved' do
@@ -91,8 +93,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has the same error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(major_degradation)
end
it 'returns zero' do
@@ -102,7 +104,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report does not have errors and head has errors' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns zero' do
@@ -124,7 +126,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has an error' do
before do
- base_report.add_degradation(degradation_1)
+ base_report.add_degradation(major_degradation)
end
it 'returns zero' do
@@ -134,7 +136,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when head report has an error' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'includes the head report error in the count' do
@@ -144,8 +146,8 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head report has errors' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'includes errors in the count' do
@@ -155,9 +157,9 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head report has the same error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'includes errors in the count' do
@@ -179,20 +181,28 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has the same error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
- end
-
- it 'includes the base report errors' do
- expect(existing_errors).to contain_exactly(degradation_1)
+ base_report.add_degradation(major_degradation)
+ base_report.add_degradation(critical_degradation)
+ base_report.add_degradation(blocker_degradation)
+ head_report.add_degradation(critical_degradation)
+ head_report.add_degradation(blocker_degradation)
+ head_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
+ end
+
+ it 'includes the base report errors sorted by severity' do
+ expect(existing_errors).to eq([
+ blocker_degradation,
+ critical_degradation,
+ major_degradation
+ ])
end
end
context 'when base report has errors and head has a different error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'returns an empty array' do
@@ -202,7 +212,7 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report does not have errors and head has errors' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns an empty array' do
@@ -224,19 +234,25 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has more errors' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(critical_degradation)
+ head_report.add_degradation(minor_degradation)
+ head_report.add_degradation(blocker_degradation)
+ head_report.add_degradation(major_degradation)
end
- it 'includes errors not found in the base report' do
- expect(new_errors).to eq([degradation_2])
+ it 'includes errors not found in the base report sorted by severity' do
+ expect(new_errors).to eq([
+ blocker_degradation,
+ critical_degradation,
+ minor_degradation
+ ])
end
end
context 'when base report has an error and head has no errors' do
before do
- base_report.add_degradation(degradation_1)
+ base_report.add_degradation(major_degradation)
end
it 'returns an empty array' do
@@ -246,11 +262,11 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report does not have errors and head has errors' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns the head report error' do
- expect(new_errors).to eq([degradation_1])
+ expect(new_errors).to eq([major_degradation])
end
end
@@ -268,9 +284,9 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report errors are still found in the head report' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ head_report.add_degradation(major_degradation)
+ head_report.add_degradation(minor_degradation)
end
it 'returns an empty array' do
@@ -280,18 +296,25 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReportsComparer do
context 'when base report has errors and head has a different error' do
before do
- base_report.add_degradation(degradation_1)
- head_report.add_degradation(degradation_2)
+ base_report.add_degradation(major_degradation)
+ base_report.add_degradation(minor_degradation)
+ base_report.add_degradation(critical_degradation)
+ base_report.add_degradation(blocker_degradation)
+ head_report.add_degradation(major_degradation)
end
- it 'returns the base report error' do
- expect(resolved_errors).to eq([degradation_1])
+ it 'returns the base report errors not found in the head report, sorted by severity' do
+ expect(resolved_errors).to eq([
+ blocker_degradation,
+ critical_degradation,
+ minor_degradation
+ ])
end
end
context 'when base report does not have errors and head has errors' do
before do
- head_report.add_degradation(degradation_1)
+ head_report.add_degradation(major_degradation)
end
it 'returns an empty array' do
diff --git a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
index ae9b2f2c62b..3b0eaffc54e 100644
--- a/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/codequality_reports_spec.rb
@@ -34,8 +34,6 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
it 'sets location as an error' do
codequality_report.add_degradation(invalid_degradation)
-
- expect(codequality_report.error_message).to eq("Invalid degradation format: The property '#/' did not contain a required property of 'location'")
end
end
end
@@ -79,4 +77,36 @@ RSpec.describe Gitlab::Ci::Reports::CodequalityReports do
end
end
end
+
+ describe '#sort_degradations!' do
+ let(:major) { build(:codequality_degradation, :major) }
+ let(:minor) { build(:codequality_degradation, :minor) }
+ let(:blocker) { build(:codequality_degradation, :blocker) }
+ let(:info) { build(:codequality_degradation, :info) }
+ let(:major_2) { build(:codequality_degradation, :major) }
+ let(:critical) { build(:codequality_degradation, :critical) }
+ let(:codequality_report) { described_class.new }
+
+ before do
+ codequality_report.add_degradation(major)
+ codequality_report.add_degradation(minor)
+ codequality_report.add_degradation(blocker)
+ codequality_report.add_degradation(major_2)
+ codequality_report.add_degradation(info)
+ codequality_report.add_degradation(critical)
+
+ codequality_report.sort_degradations!
+ end
+
+ it 'sorts degradations based on severity' do
+ expect(codequality_report.degradations.values).to eq([
+ blocker,
+ critical,
+ major,
+ major_2,
+ minor,
+ info
+ ])
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb b/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb
index 831bc5e9f37..9ee55177ca0 100644
--- a/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_failure_history_spec.rb
@@ -13,9 +13,9 @@ RSpec.describe Gitlab::Ci::Reports::TestFailureHistory, :aggregate_failures do
subject(:load_history) { described_class.new([failed_rspec, failed_java], project).load! }
before do
- allow(Ci::TestCaseFailure)
+ allow(Ci::UnitTestFailure)
.to receive(:recent_failures_count)
- .with(project: project, test_case_keys: [failed_rspec.key, failed_java.key])
+ .with(project: project, unit_test_keys: [failed_rspec.key, failed_java.key])
.and_return(
failed_rspec.key => 2,
failed_java.key => 1
diff --git a/spec/lib/gitlab/ci/runner_instructions_spec.rb b/spec/lib/gitlab/ci/runner_instructions_spec.rb
index d1020026fe6..f872c631a50 100644
--- a/spec/lib/gitlab/ci/runner_instructions_spec.rb
+++ b/spec/lib/gitlab/ci/runner_instructions_spec.rb
@@ -6,7 +6,6 @@ RSpec.describe Gitlab::Ci::RunnerInstructions do
using RSpec::Parameterized::TableSyntax
let(:params) { {} }
- let(:user) { create(:user) }
describe 'OS' do
Gitlab::Ci::RunnerInstructions::OS.each do |name, subject|
@@ -37,7 +36,7 @@ RSpec.describe Gitlab::Ci::RunnerInstructions do
end
describe '#install_script' do
- subject { described_class.new(current_user: user, **params) }
+ subject { described_class.new(**params) }
context 'invalid params' do
where(:current_params, :expected_error_message) do
@@ -106,117 +105,18 @@ RSpec.describe Gitlab::Ci::RunnerInstructions do
end
end
- context 'group' do
- let(:group) { create(:group) }
-
- subject { described_class.new(current_user: user, group: group, **params) }
-
- context 'user is owner' do
- before do
- group.add_owner(user)
- end
-
- with_them do
- let(:params) { { os: commands.each_key.first, arch: 'foo' } }
-
- it 'have correct configurations' do
- result = subject.register_command
-
- expect(result).to include("#{commands[commands.each_key.first]} register")
- expect(result).to include("--registration-token #{group.runners_token}")
- expect(result).to include("--url #{Gitlab::Routing.url_helpers.root_url(only_path: false)}")
- end
- end
- end
-
- context 'user is not owner' do
- where(:user_permission) do
- [:maintainer, :developer, :reporter, :guest]
- end
-
- with_them do
- before do
- create(:group_member, user_permission, group: group, user: user)
- end
-
- it 'raises error' do
- result = subject.register_command
-
- expect(result).to be_nil
- expect(subject.errors).to include("Gitlab::Access::AccessDeniedError")
- end
- end
- end
- end
-
- context 'project' do
- let(:project) { create(:project) }
-
- subject { described_class.new(current_user: user, project: project, **params) }
-
- context 'user is maintainer' do
- before do
- project.add_maintainer(user)
- end
-
- with_them do
- let(:params) { { os: commands.each_key.first, arch: 'foo' } }
-
- it 'have correct configurations' do
- result = subject.register_command
-
- expect(result).to include("#{commands[commands.each_key.first]} register")
- expect(result).to include("--registration-token #{project.runners_token}")
- expect(result).to include("--url #{Gitlab::Routing.url_helpers.root_url(only_path: false)}")
- end
- end
- end
-
- context 'user is not maintainer' do
- where(:user_permission) do
- [:developer, :reporter, :guest]
- end
-
- with_them do
- before do
- create(:project_member, user_permission, project: project, user: user)
- end
-
- it 'raises error' do
- result = subject.register_command
-
- expect(result).to be_nil
- expect(subject.errors).to include("Gitlab::Access::AccessDeniedError")
- end
- end
- end
- end
-
context 'instance' do
- subject { described_class.new(current_user: user, **params) }
-
- context 'user is admin' do
- let(:user) { create(:user, :admin) }
-
- with_them do
- let(:params) { { os: commands.each_key.first, arch: 'foo' } }
+ subject { described_class.new(**params) }
- it 'have correct configurations' do
- result = subject.register_command
-
- expect(result).to include("#{commands[commands.each_key.first]} register")
- expect(result).to include("--registration-token #{Gitlab::CurrentSettings.runners_registration_token}")
- expect(result).to include("--url #{Gitlab::Routing.url_helpers.root_url(only_path: false)}")
- end
- end
- end
+ with_them do
+ let(:params) { { os: commands.each_key.first, arch: 'foo' } }
- context 'user is not admin' do
- it 'raises error' do
+ it 'have correct configurations' do
result = subject.register_command
- expect(result).to be_nil
- expect(subject.errors).to include("Gitlab::Access::AccessDeniedError")
+ expect(result).to include("#{commands[commands.each_key.first]} register")
+ expect(result).to include("--registration-token $REGISTRATION_TOKEN")
+ expect(result).to include("--url #{Gitlab::Routing.url_helpers.root_url(only_path: false)}")
end
end
end
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index 924ee5ee1a4..c4e83c1796d 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Common do
context 'when user does not have access to read build' do
before do
- project.update(public_builds: false)
+ project.update!(public_builds: false)
end
it { is_expected.not_to have_details }
diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb
index 543cfe874ca..2b9523bd83d 100644
--- a/spec/lib/gitlab/ci/status/composite_spec.rb
+++ b/spec/lib/gitlab/ci/status/composite_spec.rb
@@ -6,13 +6,13 @@ RSpec.describe Gitlab::Ci::Status::Composite do
let_it_be(:pipeline) { create(:ci_pipeline) }
before_all do
- @statuses = Ci::HasStatus::STATUSES_ENUM.map do |status, idx|
+ @statuses = Ci::HasStatus::STATUSES_ENUM.to_h do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, importing: true)]
- end.to_h
+ end
- @statuses_with_allow_failure = Ci::HasStatus::STATUSES_ENUM.map do |status, idx|
+ @statuses_with_allow_failure = Ci::HasStatus::STATUSES_ENUM.to_h do |status, idx|
[status, create(:ci_build, pipeline: pipeline, status: status, allow_failure: true, importing: true)]
- end.to_h
+ end
end
describe '#status' do
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 597e4ca9b03..0fe7c731f27 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_default: :keep do
let_it_be(:project) { create_default(:project).freeze }
- let_it_be_with_reload(:build) { create(:ci_build) }
+ let_it_be_with_reload(:build) { create(:ci_build, :success) }
let(:trace) { described_class.new(build) }
describe "associations" do
@@ -63,9 +63,7 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_defa
describe '#update_interval' do
context 'it is not being watched' do
- it 'returns 30 seconds' do
- expect(trace.update_interval).to eq(30.seconds)
- end
+ it { expect(trace.update_interval).to eq(60.seconds) }
end
context 'it is being watched' do
diff --git a/spec/lib/gitlab/ci/variables/helpers_spec.rb b/spec/lib/gitlab/ci/variables/helpers_spec.rb
index b45abf8c0e1..f13b334c10e 100644
--- a/spec/lib/gitlab/ci/variables/helpers_spec.rb
+++ b/spec/lib/gitlab/ci/variables/helpers_spec.rb
@@ -100,4 +100,50 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do
it { is_expected.to eq(result) }
end
end
+
+ describe '.inherit_yaml_variables' do
+ let(:from) do
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value2' }]
+ end
+
+ let(:to) do
+ [{ key: 'key2', value: 'value22' },
+ { key: 'key3', value: 'value3' }]
+ end
+
+ let(:inheritance) { true }
+
+ let(:result) do
+ [{ key: 'key1', value: 'value1', public: true },
+ { key: 'key2', value: 'value22', public: true },
+ { key: 'key3', value: 'value3', public: true }]
+ end
+
+ subject { described_class.inherit_yaml_variables(from: from, to: to, inheritance: inheritance) }
+
+ it { is_expected.to eq(result) }
+
+ context 'when inheritance is false' do
+ let(:inheritance) { false }
+
+ let(:result) do
+ [{ key: 'key2', value: 'value22', public: true },
+ { key: 'key3', value: 'value3', public: true }]
+ end
+
+ it { is_expected.to eq(result) }
+ end
+
+ context 'when inheritance is array' do
+ let(:inheritance) { ['key2'] }
+
+ let(:result) do
+ [{ key: 'key2', value: 'value22', public: true },
+ { key: 'key3', value: 'value3', public: true }]
+ end
+
+ it { is_expected.to eq(result) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
index 7e3cd7ec254..e345cd4de9b 100644
--- a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
@@ -24,7 +24,7 @@ module Gitlab
let(:included_yml) do
YAML.dump(
- another_test: { stage: 'test', script: 'echo 2' }
+ { another_test: { stage: 'test', script: 'echo 2' } }.deep_stringify_keys
)
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 5462a587d16..ad94dfc9160 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -43,6 +43,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -74,6 +76,8 @@ module Gitlab
allow_failure: false,
when: 'on_success',
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -111,7 +115,9 @@ module Gitlab
tag_list: %w[A B],
allow_failure: false,
when: "on_success",
- yaml_variables: []
+ yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true
})
end
end
@@ -158,6 +164,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -347,6 +355,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage,
options: { script: ["rspec"] },
only: { refs: ["branches"] } }] },
@@ -359,6 +369,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage,
options: { script: ["cap prod"] },
only: { refs: ["tags"] } }] },
@@ -372,7 +384,7 @@ module Gitlab
end
end
- describe '#workflow_attributes' do
+ describe 'workflow attributes' do
context 'with disallowed workflow:variables' do
let(:config) do
<<-EOYML
@@ -403,11 +415,11 @@ module Gitlab
end
it 'parses the workflow:rules configuration' do
- expect(subject.workflow_attributes[:rules]).to contain_exactly({ if: '$VAR == "value"' })
+ expect(subject.workflow_rules).to contain_exactly({ if: '$VAR == "value"' })
end
- it 'parses the root:variables as yaml_variables:' do
- expect(subject.workflow_attributes[:yaml_variables])
+ it 'parses the root:variables as #root_variables' do
+ expect(subject.root_variables)
.to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true })
end
end
@@ -425,11 +437,11 @@ module Gitlab
end
it 'parses the workflow:rules configuration' do
- expect(subject.workflow_attributes[:rules]).to contain_exactly({ if: '$VAR == "value"' })
+ expect(subject.workflow_rules).to contain_exactly({ if: '$VAR == "value"' })
end
- it 'parses the root:variables as yaml_variables:' do
- expect(subject.workflow_attributes[:yaml_variables]).to eq([])
+ it 'parses the root:variables as #root_variables' do
+ expect(subject.root_variables).to eq([])
end
end
@@ -445,11 +457,11 @@ module Gitlab
end
it 'parses the workflow:rules configuration' do
- expect(subject.workflow_attributes[:rules]).to be_nil
+ expect(subject.workflow_rules).to be_nil
end
- it 'parses the root:variables as yaml_variables:' do
- expect(subject.workflow_attributes[:yaml_variables])
+ it 'parses the root:variables as #root_variables' do
+ expect(subject.root_variables)
.to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true })
end
end
@@ -463,11 +475,11 @@ module Gitlab
end
it 'parses the workflow:rules configuration' do
- expect(subject.workflow_attributes[:rules]).to be_nil
+ expect(subject.workflow_rules).to be_nil
end
- it 'parses the root:variables as yaml_variables:' do
- expect(subject.workflow_attributes[:yaml_variables]).to eq([])
+ it 'parses the root:variables as #root_variables' do
+ expect(subject.root_variables).to eq([])
end
end
end
@@ -853,6 +865,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -861,7 +875,7 @@ module Gitlab
config = YAML.dump({ image: "ruby:2.7",
services: ["mysql"],
before_script: ["pwd"],
- rspec: { image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+ rspec: { image: { name: "ruby:3.0", entrypoint: ["/usr/local/bin/init", "run"] },
services: [{ name: "postgresql", alias: "db-pg",
entrypoint: ["/usr/local/bin/init", "run"],
command: ["/usr/local/bin/init", "run"] }, "docker:dind"],
@@ -878,7 +892,7 @@ module Gitlab
options: {
before_script: ["pwd"],
script: ["rspec"],
- image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+ image: { name: "ruby:3.0", entrypoint: ["/usr/local/bin/init", "run"] },
services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
command: ["/usr/local/bin/init", "run"] },
{ name: "docker:dind" }]
@@ -886,6 +900,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -915,6 +931,8 @@ module Gitlab
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -923,7 +941,7 @@ module Gitlab
config = YAML.dump({ image: "ruby:2.7",
services: ["mysql"],
before_script: ["pwd"],
- rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } })
+ rspec: { image: "ruby:3.0", services: ["postgresql", "docker:dind"], script: "rspec" } })
config_processor = Gitlab::Ci::YamlProcessor.new(config).execute
@@ -936,12 +954,14 @@ module Gitlab
options: {
before_script: ["pwd"],
script: ["rspec"],
- image: { name: "ruby:2.5" },
+ image: { name: "ruby:3.0" },
services: [{ name: "postgresql" }, { name: "docker:dind" }]
},
allow_failure: false,
when: "on_success",
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -951,7 +971,10 @@ module Gitlab
describe 'Variables' do
subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute }
- let(:build_variables) { subject.builds.first[:yaml_variables] }
+ let(:build) { subject.builds.first }
+ let(:yaml_variables) { build[:yaml_variables] }
+ let(:job_variables) { build[:job_variables] }
+ let(:root_variables_inheritance) { build[:root_variables_inheritance] }
context 'when global variables are defined' do
let(:variables) do
@@ -967,10 +990,12 @@ module Gitlab
end
it 'returns global variables' do
- expect(build_variables).to contain_exactly(
+ expect(yaml_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(job_variables).to eq([])
+ expect(root_variables_inheritance).to eq(true)
end
end
@@ -979,7 +1004,7 @@ module Gitlab
{ 'VAR1' => 'global1', 'VAR3' => 'global3', 'VAR4' => 'global4' }
end
- let(:job_variables) do
+ let(:build_variables) do
{ 'VAR1' => 'value1', 'VAR2' => 'value2' }
end
@@ -987,20 +1012,25 @@ module Gitlab
{
before_script: ['pwd'],
variables: global_variables,
- rspec: { script: 'rspec', variables: job_variables, inherit: inherit }
+ rspec: { script: 'rspec', variables: build_variables, inherit: inherit }
}
end
context 'when no inheritance is specified' do
let(:inherit) { }
- it 'returns all unique variables' do
- expect(build_variables).to contain_exactly(
- { key: 'VAR4', value: 'global4', public: true },
+ it 'returns all variables' do
+ expect(yaml_variables).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true },
{ key: 'VAR3', value: 'global3', public: true },
+ { key: 'VAR4', value: 'global4', public: true }
+ )
+ expect(job_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(root_variables_inheritance).to eq(true)
end
end
@@ -1008,22 +1038,32 @@ module Gitlab
let(:inherit) { { variables: false } }
it 'does not inherit variables' do
- expect(build_variables).to contain_exactly(
+ expect(yaml_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(job_variables).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ expect(root_variables_inheritance).to eq(false)
end
end
context 'when specific variables are to inherited' do
let(:inherit) { { variables: %w[VAR1 VAR4] } }
- it 'returns all unique variables and inherits only specified variables' do
- expect(build_variables).to contain_exactly(
- { key: 'VAR4', value: 'global4', public: true },
+ it 'returns all variables and inherits only specified variables' do
+ expect(yaml_variables).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true },
+ { key: 'VAR4', value: 'global4', public: true }
+ )
+ expect(job_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(root_variables_inheritance).to eq(%w[VAR1 VAR4])
end
end
end
@@ -1042,10 +1082,15 @@ module Gitlab
end
it 'returns job variables' do
- expect(build_variables).to contain_exactly(
+ expect(yaml_variables).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ expect(job_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1', public: true },
{ key: 'VAR2', value: 'value2', public: true }
)
+ expect(root_variables_inheritance).to eq(true)
end
end
@@ -1068,8 +1113,11 @@ module Gitlab
# When variables config is empty, we assume this is a valid
# configuration, see issue #18775
#
- expect(build_variables).to be_an_instance_of(Array)
- expect(build_variables).to be_empty
+ expect(yaml_variables).to be_an_instance_of(Array)
+ expect(yaml_variables).to be_empty
+
+ expect(job_variables).to eq([])
+ expect(root_variables_inheritance).to eq(true)
end
end
end
@@ -1084,8 +1132,11 @@ module Gitlab
end
it 'returns empty array' do
- expect(build_variables).to be_an_instance_of(Array)
- expect(build_variables).to be_empty
+ expect(yaml_variables).to be_an_instance_of(Array)
+ expect(yaml_variables).to be_empty
+
+ expect(job_variables).to eq([])
+ expect(root_variables_inheritance).to eq(true)
end
end
end
@@ -1717,6 +1768,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -2080,6 +2133,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
)
expect(subject.builds[4]).to eq(
@@ -2095,6 +2150,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :dag
)
end
@@ -2122,6 +2179,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
)
expect(subject.builds[4]).to eq(
@@ -2139,6 +2198,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :dag
)
end
@@ -2162,6 +2223,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :dag
)
end
@@ -2193,6 +2256,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :dag
)
end
@@ -2391,6 +2456,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
@@ -2438,6 +2505,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
expect(subject.second).to eq({
@@ -2451,6 +2520,8 @@ module Gitlab
when: "on_success",
allow_failure: false,
yaml_variables: [],
+ job_variables: [],
+ root_variables_inheritance: true,
scheduling_type: :stage
})
end
diff --git a/spec/lib/gitlab/composer/version_index_spec.rb b/spec/lib/gitlab/composer/version_index_spec.rb
index 7b0ed703f42..a4d016636aa 100644
--- a/spec/lib/gitlab/composer/version_index_spec.rb
+++ b/spec/lib/gitlab/composer/version_index_spec.rb
@@ -27,6 +27,11 @@ RSpec.describe Gitlab::Composer::VersionIndex do
'type' => 'zip',
'url' => "http://localhost/api/v4/projects/#{project.id}/packages/composer/archives/#{package.name}.zip?sha=#{branch.target}"
},
+ 'source' => {
+ 'reference' => branch.target,
+ 'type' => 'git',
+ 'url' => project.http_url_to_repo
+ },
'name' => package.name,
'uid' => package.id,
'version' => package.version
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index bb9bee763d8..46e5334cd81 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::Conflict::File do
let(:section_keys) { conflict_file.sections.map { |section| section[:id] }.compact }
context 'when resolving everything to the same side' do
- let(:resolution_hash) { section_keys.map { |key| [key, 'head'] }.to_h }
+ let(:resolution_hash) { section_keys.to_h { |key| [key, 'head'] } }
let(:resolved_lines) { conflict_file.resolve_lines(resolution_hash) }
let(:expected_lines) { conflict_file.lines.reject { |line| line.type == 'old' } }
@@ -54,8 +54,8 @@ RSpec.describe Gitlab::Conflict::File do
end
it 'raises ResolutionError when passed a hash without resolutions for all sections' do
- empty_hash = section_keys.map { |key| [key, nil] }.to_h
- invalid_hash = section_keys.map { |key| [key, 'invalid'] }.to_h
+ empty_hash = section_keys.to_h { |key| [key, nil] }
+ invalid_hash = section_keys.to_h { |key| [key, 'invalid'] }
expect { conflict_file.resolve_lines({}) }
.to raise_error(Gitlab::Git::Conflict::Resolver::ResolutionError)
diff --git a/spec/lib/gitlab/crypto_helper_spec.rb b/spec/lib/gitlab/crypto_helper_spec.rb
index 024564ea213..616a37a4cb9 100644
--- a/spec/lib/gitlab/crypto_helper_spec.rb
+++ b/spec/lib/gitlab/crypto_helper_spec.rb
@@ -20,22 +20,24 @@ RSpec.describe Gitlab::CryptoHelper do
expect(encrypted).not_to include "\n"
end
- it 'does not save hashed token with iv value in database' do
- expect { described_class.aes256_gcm_encrypt('some-value') }.not_to change { TokenWithIv.count }
- end
-
it 'encrypts using static iv' do
expect(Encryptor).to receive(:encrypt).with(described_class::AES256_GCM_OPTIONS.merge(value: 'some-value', iv: described_class::AES256_GCM_IV_STATIC)).and_return('hashed_value')
described_class.aes256_gcm_encrypt('some-value')
end
- end
- describe '.aes256_gcm_decrypt' do
- before do
- stub_feature_flags(dynamic_nonce_creation: false)
+ context 'with provided iv' do
+ let(:iv) { create_nonce }
+
+ it 'encrypts using provided iv' do
+ expect(Encryptor).to receive(:encrypt).with(described_class::AES256_GCM_OPTIONS.merge(value: 'some-value', iv: iv)).and_return('hashed_value')
+
+ described_class.aes256_gcm_encrypt('some-value', nonce: iv)
+ end
end
+ end
+ describe '.aes256_gcm_decrypt' do
context 'when token was encrypted using static nonce' do
let(:encrypted) { described_class.aes256_gcm_encrypt('some-value', nonce: described_class::AES256_GCM_IV_STATIC) }
@@ -50,54 +52,22 @@ RSpec.describe Gitlab::CryptoHelper do
expect(decrypted).to eq 'some-value'
end
-
- it 'does not save hashed token with iv value in database' do
- expect { described_class.aes256_gcm_decrypt(encrypted) }.not_to change { TokenWithIv.count }
- end
-
- context 'with feature flag switched on' do
- before do
- stub_feature_flags(dynamic_nonce_creation: true)
- end
-
- it 'correctly decrypts encrypted string' do
- decrypted = described_class.aes256_gcm_decrypt(encrypted)
-
- expect(decrypted).to eq 'some-value'
- end
- end
end
context 'when token was encrypted using random nonce' do
let(:value) { 'random-value' }
-
- # for compatibility with tokens encrypted using dynamic nonce
- let!(:encrypted) do
- iv = create_nonce
- encrypted_token = described_class.create_encrypted_token(value, iv)
- TokenWithIv.create!(hashed_token: Digest::SHA256.digest(encrypted_token), hashed_plaintext_token: Digest::SHA256.digest(encrypted_token), iv: iv)
- encrypted_token
- end
-
- before do
- stub_feature_flags(dynamic_nonce_creation: true)
- end
+ let(:iv) { create_nonce }
+ let(:encrypted) { described_class.aes256_gcm_encrypt(value, nonce: iv) }
it 'correctly decrypts encrypted string' do
- decrypted = described_class.aes256_gcm_decrypt(encrypted)
+ decrypted = described_class.aes256_gcm_decrypt(encrypted, nonce: iv)
expect(decrypted).to eq value
end
-
- it 'does not save hashed token with iv value in database' do
- expect { described_class.aes256_gcm_decrypt(encrypted) }.not_to change { TokenWithIv.count }
- end
end
end
def create_nonce
- cipher = OpenSSL::Cipher.new('aes-256-gcm')
- cipher.encrypt # Required before '#random_iv' can be called
- cipher.random_iv # Ensures that the IV is the correct length respective to the algorithm used.
+ ::Digest::SHA256.hexdigest('my-value').bytes.take(TokenAuthenticatableStrategies::EncryptionHelper::NONCE_SIZE).pack('c*')
end
end
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index ab1728414bb..932238f281e 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -19,6 +19,9 @@ RSpec.describe Gitlab::DataBuilder::Build do
it { expect(data[:tag]).to eq(build.tag) }
it { expect(data[:build_id]).to eq(build.id) }
it { expect(data[:build_status]).to eq(build.status) }
+ it { expect(data[:build_created_at]).to eq(build.created_at) }
+ it { expect(data[:build_started_at]).to eq(build.started_at) }
+ it { expect(data[:build_finished_at]).to eq(build.finished_at) }
it { expect(data[:build_allow_failure]).to eq(false) }
it { expect(data[:build_failure_reason]).to eq(build.failure_reason) }
it { expect(data[:project_id]).to eq(build.project.id) }
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index cf04f560ceb..bec1e612c02 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -59,7 +59,6 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
expect(runner_data[:id]).to eq(ci_runner.id)
expect(runner_data[:description]).to eq(ci_runner.description)
expect(runner_data[:active]).to eq(ci_runner.active)
- expect(runner_data[:is_shared]).to eq(ci_runner.instance_type?)
expect(runner_data[:tags]).to match_array(tag_names)
end
end
diff --git a/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb b/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb
new file mode 100644
index 00000000000..e96862fbc2d
--- /dev/null
+++ b/spec/lib/gitlab/database/background_migration/batch_metrics_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Database::BackgroundMigration::BatchMetrics do
+ let(:batch_metrics) { described_class.new }
+
+ describe '#time_operation' do
+ it 'tracks the duration of the operation using monotonic time' do
+ expect(batch_metrics.timings).to be_empty
+
+ expect(Gitlab::Metrics::System).to receive(:monotonic_time)
+ .exactly(6).times
+ .and_return(0.0, 111.0, 200.0, 290.0, 300.0, 410.0)
+
+ batch_metrics.time_operation(:my_label) do
+ # some operation
+ end
+
+ batch_metrics.time_operation(:my_other_label) do
+ # some operation
+ end
+
+ batch_metrics.time_operation(:my_label) do
+ # some operation
+ end
+
+ expect(batch_metrics.timings).to eq(my_label: [111.0, 110.0], my_other_label: [90.0])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
new file mode 100644
index 00000000000..7d0e10b62c6
--- /dev/null
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
@@ -0,0 +1,198 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
+ let(:migration_wrapper) { double('test wrapper') }
+ let(:runner) { described_class.new(migration_wrapper) }
+
+ describe '#run_migration_job' do
+ shared_examples_for 'it has completed the migration' do
+ it 'does not create and run a migration job' do
+ expect(migration_wrapper).not_to receive(:perform)
+
+ expect do
+ runner.run_migration_job(migration)
+ end.not_to change { Gitlab::Database::BackgroundMigration::BatchedJob.count }
+ end
+
+ it 'marks the migration as finished' do
+ relation = Gitlab::Database::BackgroundMigration::BatchedMigration.finished.where(id: migration.id)
+
+ expect { runner.run_migration_job(migration) }.to change { relation.count }.by(1)
+ end
+ end
+
+ context 'when the migration has no previous jobs' do
+ let(:migration) { create(:batched_background_migration, :active, batch_size: 2) }
+
+ let(:job_relation) do
+ Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: migration.id)
+ end
+
+ context 'when the migration has batches to process' do
+ let!(:event1) { create(:event) }
+ let!(:event2) { create(:event) }
+ let!(:event3) { create(:event) }
+
+ it 'runs the job for the first batch' do
+ migration.update!(min_value: event1.id, max_value: event2.id)
+
+ expect(migration_wrapper).to receive(:perform) do |job_record|
+ expect(job_record).to eq(job_relation.first)
+ end
+
+ expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(1)
+
+ expect(job_relation.first).to have_attributes(
+ min_value: event1.id,
+ max_value: event2.id,
+ batch_size: migration.batch_size,
+ sub_batch_size: migration.sub_batch_size)
+ end
+ end
+
+ context 'when the batch maximum exceeds the migration maximum' do
+ let!(:events) { create_list(:event, 3) }
+ let(:event1) { events[0] }
+ let(:event2) { events[1] }
+
+ it 'clamps the batch maximum to the migration maximum' do
+ migration.update!(min_value: event1.id, max_value: event2.id, batch_size: 5)
+
+ expect(migration_wrapper).to receive(:perform)
+
+ expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(1)
+
+ expect(job_relation.first).to have_attributes(
+ min_value: event1.id,
+ max_value: event2.id,
+ batch_size: migration.batch_size,
+ sub_batch_size: migration.sub_batch_size)
+ end
+ end
+
+ context 'when the migration has no batches to process' do
+ it_behaves_like 'it has completed the migration'
+ end
+ end
+
+ context 'when the migration has previous jobs' do
+ let!(:event1) { create(:event) }
+ let!(:event2) { create(:event) }
+ let!(:event3) { create(:event) }
+
+ let!(:migration) do
+ create(:batched_background_migration, :active, batch_size: 2, min_value: event1.id, max_value: event3.id)
+ end
+
+ let!(:previous_job) do
+ create(:batched_background_migration_job,
+ batched_migration: migration,
+ min_value: event1.id,
+ max_value: event2.id,
+ batch_size: 2,
+ sub_batch_size: 1)
+ end
+
+ let(:job_relation) do
+ Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: migration.id)
+ end
+
+ context 'when the migration has batches to process' do
+ it 'runs the migration job for the next batch' do
+ expect(migration_wrapper).to receive(:perform) do |job_record|
+ expect(job_record).to eq(job_relation.last)
+ end
+
+ expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(1)
+
+ expect(job_relation.last).to have_attributes(
+ min_value: event3.id,
+ max_value: event3.id,
+ batch_size: migration.batch_size,
+ sub_batch_size: migration.sub_batch_size)
+ end
+
+ context 'when the batch minimum exceeds the migration maximum' do
+ before do
+ migration.update!(batch_size: 5, max_value: event2.id)
+ end
+
+ it_behaves_like 'it has completed the migration'
+ end
+ end
+
+ context 'when the migration has no batches remaining' do
+ before do
+ create(:batched_background_migration_job,
+ batched_migration: migration,
+ min_value: event3.id,
+ max_value: event3.id,
+ batch_size: 2,
+ sub_batch_size: 1)
+ end
+
+ it_behaves_like 'it has completed the migration'
+ end
+ end
+ end
+
+ describe '#run_entire_migration' do
+ context 'when not in a development or test environment' do
+ it 'raises an error' do
+ environment = double('environment', development?: false, test?: false)
+ migration = build(:batched_background_migration, :finished)
+
+ allow(Rails).to receive(:env).and_return(environment)
+
+ expect do
+ runner.run_entire_migration(migration)
+ end.to raise_error('this method is not intended for use in real environments')
+ end
+ end
+
+ context 'when the given migration is not active' do
+ it 'does not create and run migration jobs' do
+ migration = build(:batched_background_migration, :finished)
+
+ expect(migration_wrapper).not_to receive(:perform)
+
+ expect do
+ runner.run_entire_migration(migration)
+ end.not_to change { Gitlab::Database::BackgroundMigration::BatchedJob.count }
+ end
+ end
+
+ context 'when the given migration is active' do
+ let!(:event1) { create(:event) }
+ let!(:event2) { create(:event) }
+ let!(:event3) { create(:event) }
+
+ let!(:migration) do
+ create(:batched_background_migration, :active, batch_size: 2, min_value: event1.id, max_value: event3.id)
+ end
+
+ let(:job_relation) do
+ Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: migration.id)
+ end
+
+ it 'runs all jobs inline until finishing the migration' do
+ expect(migration_wrapper).to receive(:perform) do |job_record|
+ expect(job_record).to eq(job_relation.first)
+ end
+
+ expect(migration_wrapper).to receive(:perform) do |job_record|
+ expect(job_record).to eq(job_relation.last)
+ end
+
+ expect { runner.run_entire_migration(migration) }.to change { job_relation.count }.by(2)
+
+ expect(job_relation.first).to have_attributes(min_value: event1.id, max_value: event2.id)
+ expect(job_relation.last).to have_attributes(min_value: event3.id, max_value: event3.id)
+
+ expect(migration.reload).to be_finished
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
index f4a939e7c1f..261e23d0745 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
@@ -29,6 +29,16 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
end
+ describe '.active_migration' do
+ let!(:migration1) { create(:batched_background_migration, :finished) }
+ let!(:migration2) { create(:batched_background_migration, :active) }
+ let!(:migration3) { create(:batched_background_migration, :active) }
+
+ it 'returns the first active migration according to queue order' do
+ expect(described_class.active_migration).to eq(migration2)
+ end
+ end
+
describe '#interval_elapsed?' do
context 'when the migration has no last_job' do
let(:batched_migration) { build(:batched_background_migration) }
@@ -77,6 +87,34 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
end
end
+
+ context 'when an interval variance is given' do
+ let(:variance) { 2.seconds }
+
+ context 'when the last job is less than an interval with variance old' do
+ it 'returns false' do
+ freeze_time do
+ create(:batched_background_migration_job,
+ batched_migration: batched_migration,
+ created_at: Time.current - 1.minute - 57.seconds)
+
+ expect(batched_migration.interval_elapsed?(variance: variance)).to eq(false)
+ end
+ end
+ end
+
+ context 'when the last job is more than an interval with variance old' do
+ it 'returns true' do
+ freeze_time do
+ create(:batched_background_migration_job,
+ batched_migration: batched_migration,
+ created_at: Time.current - 1.minute - 58.seconds)
+
+ expect(batched_migration.interval_elapsed?(variance: variance)).to eq(true)
+ end
+ end
+ end
+ end
end
end
@@ -157,4 +195,17 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
describe '#batch_class_name=' do
it_behaves_like 'an attr_writer that demodulizes assigned class names', :batch_class_name
end
+
+ describe '#prometheus_labels' do
+ let(:batched_migration) { create(:batched_background_migration, job_class_name: 'TestMigration', table_name: 'foo', column_name: 'bar') }
+
+ it 'returns a hash with labels for the migration' do
+ labels = {
+ migration_id: batched_migration.id,
+ migration_identifier: 'TestMigration/foo.bar'
+ }
+
+ expect(batched_migration.prometheus_labels).to eq(labels)
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
index 17cceb35ff7..00d13f23d36 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
@@ -3,43 +3,105 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '#perform' do
- let(:migration_wrapper) { described_class.new }
+ subject { described_class.new.perform(job_record) }
+
let(:job_class) { Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJob }
let_it_be(:active_migration) { create(:batched_background_migration, :active, job_arguments: [:id, :other_id]) }
let!(:job_record) { create(:batched_background_migration_job, batched_migration: active_migration) }
+ let(:job_instance) { double('job instance', batch_metrics: {}) }
+
+ before do
+ allow(job_class).to receive(:new).and_return(job_instance)
+ end
it 'runs the migration job' do
- expect_next_instance_of(job_class) do |job_instance|
- expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
- end
+ expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
- migration_wrapper.perform(job_record)
+ subject
end
- it 'updates the the tracking record in the database' do
+ it 'updates the tracking record in the database' do
+ test_metrics = { 'my_metris' => 'some value' }
+
+ expect(job_instance).to receive(:perform)
+ expect(job_instance).to receive(:batch_metrics).and_return(test_metrics)
+
expect(job_record).to receive(:update!).with(hash_including(attempts: 1, status: :running)).and_call_original
freeze_time do
- migration_wrapper.perform(job_record)
+ subject
reloaded_job_record = job_record.reload
expect(reloaded_job_record).not_to be_pending
expect(reloaded_job_record.attempts).to eq(1)
expect(reloaded_job_record.started_at).to eq(Time.current)
+ expect(reloaded_job_record.metrics).to eq(test_metrics)
+ end
+ end
+
+ context 'reporting prometheus metrics' do
+ let(:labels) { job_record.batched_migration.prometheus_labels }
+
+ before do
+ allow(job_instance).to receive(:perform)
+ end
+
+ it 'reports batch_size' do
+ expect(described_class.metrics[:gauge_batch_size]).to receive(:set).with(labels, job_record.batch_size)
+
+ subject
+ end
+
+ it 'reports sub_batch_size' do
+ expect(described_class.metrics[:gauge_sub_batch_size]).to receive(:set).with(labels, job_record.sub_batch_size)
+
+ subject
+ end
+
+ it 'reports updated tuples (currently based on batch_size)' do
+ expect(described_class.metrics[:counter_updated_tuples]).to receive(:increment).with(labels, job_record.batch_size)
+
+ subject
+ end
+
+ it 'reports summary of query timings' do
+ metrics = { 'timings' => { 'update_all' => [1, 2, 3, 4, 5] } }
+
+ expect(job_instance).to receive(:batch_metrics).and_return(metrics)
+
+ metrics['timings'].each do |key, timings|
+ summary_labels = labels.merge(operation: key)
+ timings.each do |timing|
+ expect(described_class.metrics[:histogram_timings]).to receive(:observe).with(summary_labels, timing)
+ end
+ end
+
+ subject
+ end
+
+ it 'reports time efficiency' do
+ freeze_time do
+ expect(Time).to receive(:current).and_return(Time.zone.now - 5.seconds).ordered
+ expect(Time).to receive(:current).and_return(Time.zone.now).ordered
+
+ ratio = 5 / job_record.batched_migration.interval.to_f
+
+ expect(described_class.metrics[:histogram_time_efficiency]).to receive(:observe).with(labels, ratio)
+
+ subject
+ end
end
end
context 'when the migration job does not raise an error' do
it 'marks the tracking record as succeeded' do
- expect_next_instance_of(job_class) do |job_instance|
- expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
- end
+ expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
freeze_time do
- migration_wrapper.perform(job_record)
+ subject
reloaded_job_record = job_record.reload
@@ -51,14 +113,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
context 'when the migration job raises an error' do
it 'marks the tracking record as failed before raising the error' do
- expect_next_instance_of(job_class) do |job_instance|
- expect(job_instance).to receive(:perform)
- .with(1, 10, 'events', 'id', 1, 'id', 'other_id')
- .and_raise(RuntimeError, 'Something broke!')
- end
+ expect(job_instance).to receive(:perform)
+ .with(1, 10, 'events', 'id', 1, 'id', 'other_id')
+ .and_raise(RuntimeError, 'Something broke!')
freeze_time do
- expect { migration_wrapper.perform(job_record) }.to raise_error(RuntimeError, 'Something broke!')
+ expect { subject }.to raise_error(RuntimeError, 'Something broke!')
reloaded_job_record = job_record.reload
diff --git a/spec/lib/gitlab/database/background_migration/scheduler_spec.rb b/spec/lib/gitlab/database/background_migration/scheduler_spec.rb
deleted file mode 100644
index ba745acdf8a..00000000000
--- a/spec/lib/gitlab/database/background_migration/scheduler_spec.rb
+++ /dev/null
@@ -1,182 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Database::BackgroundMigration::Scheduler, '#perform' do
- let(:scheduler) { described_class.new }
-
- shared_examples_for 'it has no jobs to run' do
- it 'does not create and run a migration job' do
- test_wrapper = double('test wrapper')
-
- expect(test_wrapper).not_to receive(:perform)
-
- expect do
- scheduler.perform(migration_wrapper: test_wrapper)
- end.not_to change { Gitlab::Database::BackgroundMigration::BatchedJob.count }
- end
- end
-
- context 'when there are no active migrations' do
- let!(:migration) { create(:batched_background_migration, :finished) }
-
- it_behaves_like 'it has no jobs to run'
- end
-
- shared_examples_for 'it has completed the migration' do
- it 'marks the migration as finished' do
- relation = Gitlab::Database::BackgroundMigration::BatchedMigration.finished.where(id: first_migration.id)
-
- expect { scheduler.perform }.to change { relation.count }.by(1)
- end
- end
-
- context 'when there are active migrations' do
- let!(:first_migration) { create(:batched_background_migration, :active, batch_size: 2) }
- let!(:last_migration) { create(:batched_background_migration, :active) }
-
- let(:job_relation) do
- Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: first_migration.id)
- end
-
- context 'when the migration interval has not elapsed' do
- before do
- expect_next_found_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigration) do |migration|
- expect(migration).to receive(:interval_elapsed?).and_return(false)
- end
- end
-
- it_behaves_like 'it has no jobs to run'
- end
-
- context 'when the interval has elapsed' do
- before do
- expect_next_found_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigration) do |migration|
- expect(migration).to receive(:interval_elapsed?).and_return(true)
- end
- end
-
- context 'when the first migration has no previous jobs' do
- context 'when the migration has batches to process' do
- let!(:event1) { create(:event) }
- let!(:event2) { create(:event) }
- let!(:event3) { create(:event) }
-
- it 'runs the job for the first batch' do
- first_migration.update!(min_value: event1.id, max_value: event3.id)
-
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper|
- expect(wrapper).to receive(:perform).and_wrap_original do |_, job_record|
- expect(job_record).to eq(job_relation.first)
- end
- end
-
- expect { scheduler.perform }.to change { job_relation.count }.by(1)
-
- expect(job_relation.first).to have_attributes(
- min_value: event1.id,
- max_value: event2.id,
- batch_size: first_migration.batch_size,
- sub_batch_size: first_migration.sub_batch_size)
- end
- end
-
- context 'when the migration has no batches to process' do
- it_behaves_like 'it has no jobs to run'
- it_behaves_like 'it has completed the migration'
- end
- end
-
- context 'when the first migration has previous jobs' do
- let!(:event1) { create(:event) }
- let!(:event2) { create(:event) }
- let!(:event3) { create(:event) }
-
- let!(:previous_job) do
- create(:batched_background_migration_job,
- batched_migration: first_migration,
- min_value: event1.id,
- max_value: event2.id,
- batch_size: 2,
- sub_batch_size: 1)
- end
-
- context 'when the migration is ready to process another job' do
- it 'runs the migration job for the next batch' do
- first_migration.update!(min_value: event1.id, max_value: event3.id)
-
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper|
- expect(wrapper).to receive(:perform).and_wrap_original do |_, job_record|
- expect(job_record).to eq(job_relation.last)
- end
- end
-
- expect { scheduler.perform }.to change { job_relation.count }.by(1)
-
- expect(job_relation.last).to have_attributes(
- min_value: event3.id,
- max_value: event3.id,
- batch_size: first_migration.batch_size,
- sub_batch_size: first_migration.sub_batch_size)
- end
- end
-
- context 'when the migration has no batches remaining' do
- let!(:final_job) do
- create(:batched_background_migration_job,
- batched_migration: first_migration,
- min_value: event3.id,
- max_value: event3.id,
- batch_size: 2,
- sub_batch_size: 1)
- end
-
- it_behaves_like 'it has no jobs to run'
- it_behaves_like 'it has completed the migration'
- end
- end
-
- context 'when the bounds of the next batch exceed the migration maximum value' do
- let!(:events) { create_list(:event, 3) }
- let(:event1) { events[0] }
- let(:event2) { events[1] }
-
- context 'when the batch maximum exceeds the migration maximum' do
- it 'clamps the batch maximum to the migration maximum' do
- first_migration.update!(batch_size: 5, min_value: event1.id, max_value: event2.id)
-
- expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper) do |wrapper|
- expect(wrapper).to receive(:perform)
- end
-
- expect { scheduler.perform }.to change { job_relation.count }.by(1)
-
- expect(job_relation.first).to have_attributes(
- min_value: event1.id,
- max_value: event2.id,
- batch_size: first_migration.batch_size,
- sub_batch_size: first_migration.sub_batch_size)
- end
- end
-
- context 'when the batch minimum exceeds the migration maximum' do
- let!(:previous_job) do
- create(:batched_background_migration_job,
- batched_migration: first_migration,
- min_value: event1.id,
- max_value: event2.id,
- batch_size: 5,
- sub_batch_size: 1)
- end
-
- before do
- first_migration.update!(batch_size: 5, min_value: 1, max_value: event2.id)
- end
-
- it_behaves_like 'it has no jobs to run'
- it_behaves_like 'it has completed the migration'
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/database/batch_count_spec.rb b/spec/lib/gitlab/database/batch_count_spec.rb
index 29688b18e94..da13bc425d1 100644
--- a/spec/lib/gitlab/database/batch_count_spec.rb
+++ b/spec/lib/gitlab/database/batch_count_spec.rb
@@ -270,6 +270,8 @@ RSpec.describe Gitlab::Database::BatchCount do
end
it "defaults the batch size to #{Gitlab::Database::BatchCounter::DEFAULT_DISTINCT_BATCH_SIZE}" do
+ stub_feature_flags(loose_index_scan_for_distinct_values: false)
+
min_id = model.minimum(:id)
relation = instance_double(ActiveRecord::Relation)
allow(model).to receive_message_chain(:select, public_send: relation)
@@ -315,13 +317,85 @@ RSpec.describe Gitlab::Database::BatchCount do
end
end
- it_behaves_like 'when batch fetch query is canceled' do
+ context 'when the loose_index_scan_for_distinct_values feature flag is off' do
+ it_behaves_like 'when batch fetch query is canceled' do
+ let(:mode) { :distinct }
+ let(:operation) { :count }
+ let(:operation_args) { nil }
+ let(:column) { nil }
+
+ subject { described_class.method(:batch_distinct_count) }
+
+ before do
+ stub_feature_flags(loose_index_scan_for_distinct_values: false)
+ end
+ end
+ end
+
+ context 'when the loose_index_scan_for_distinct_values feature flag is on' do
let(:mode) { :distinct }
let(:operation) { :count }
let(:operation_args) { nil }
let(:column) { nil }
+ let(:batch_size) { 10_000 }
+
subject { described_class.method(:batch_distinct_count) }
+
+ before do
+ stub_feature_flags(loose_index_scan_for_distinct_values: true)
+ end
+
+ it 'reduces batch size by half and retry fetch' do
+ too_big_batch_relation_mock = instance_double(ActiveRecord::Relation)
+
+ count_method = double(send: 1)
+
+ allow(too_big_batch_relation_mock).to receive(:send).and_raise(ActiveRecord::QueryCanceled)
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive_message_chain(:new, :build_query).with(from: 0, to: batch_size).and_return(too_big_batch_relation_mock)
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive_message_chain(:new, :build_query).with(from: 0, to: batch_size / 2).and_return(count_method)
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive_message_chain(:new, :build_query).with(from: batch_size / 2, to: batch_size).and_return(count_method)
+
+ subject.call(model, column, batch_size: batch_size, start: 0, finish: batch_size - 1)
+ end
+
+ context 'when all retries fail' do
+ let(:batch_count_query) { 'SELECT COUNT(id) FROM relation WHERE id BETWEEN 0 and 1' }
+
+ before do
+ relation = instance_double(ActiveRecord::Relation)
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive_message_chain(:new, :build_query).and_return(relation)
+ allow(relation).to receive(:send).and_raise(ActiveRecord::QueryCanceled.new('query timed out'))
+ allow(relation).to receive(:to_sql).and_return(batch_count_query)
+ end
+
+ it 'logs failing query' do
+ expect(Gitlab::AppJsonLogger).to receive(:error).with(
+ event: 'batch_count',
+ relation: model.table_name,
+ operation: operation,
+ operation_args: operation_args,
+ start: 0,
+ mode: mode,
+ query: batch_count_query,
+ message: 'Query has been canceled with message: query timed out'
+ )
+ expect(subject.call(model, column, batch_size: batch_size, start: 0)).to eq(-1)
+ end
+ end
+
+ context 'when LooseIndexScanDistinctCount raises error' do
+ let(:column) { :creator_id }
+ let(:error_class) { Gitlab::Database::LooseIndexScanDistinctCount::ColumnConfigurationError }
+
+ it 'rescues ColumnConfigurationError' do
+ allow(Gitlab::Database::LooseIndexScanDistinctCount).to receive(:new).and_raise(error_class.new('error message'))
+
+ expect(Gitlab::AppJsonLogger).to receive(:error).with(a_hash_including(message: 'LooseIndexScanDistinctCount column error: error message'))
+
+ expect(subject.call(Project, column, batch_size: 10_000, start: 0)).to eq(-1)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/database/loose_index_scan_distinct_count_spec.rb b/spec/lib/gitlab/database/loose_index_scan_distinct_count_spec.rb
new file mode 100644
index 00000000000..e0eac26e4d9
--- /dev/null
+++ b/spec/lib/gitlab/database/loose_index_scan_distinct_count_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::LooseIndexScanDistinctCount do
+ context 'counting distinct users' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+
+ let(:column) { :creator_id }
+
+ before_all do
+ create_list(:project, 3, creator: user)
+ create_list(:project, 1, creator: other_user)
+ end
+
+ subject(:count) { described_class.new(Project, :creator_id).count(from: Project.minimum(:creator_id), to: Project.maximum(:creator_id) + 1) }
+
+ it { is_expected.to eq(2) }
+
+ context 'when STI model is queried' do
+ it 'does not raise error' do
+ expect { described_class.new(Group, :owner_id).count(from: 0, to: 1) }.not_to raise_error
+ end
+ end
+
+ context 'when model with default_scope is queried' do
+ it 'does not raise error' do
+ expect { described_class.new(GroupMember, :id).count(from: 0, to: 1) }.not_to raise_error
+ end
+ end
+
+ context 'when the fully qualified column is given' do
+ let(:column) { 'projects.creator_id' }
+
+ it { is_expected.to eq(2) }
+ end
+
+ context 'when AR attribute is given' do
+ let(:column) { Project.arel_table[:creator_id] }
+
+ it { is_expected.to eq(2) }
+ end
+
+ context 'when invalid value is given for the column' do
+ let(:column) { Class.new }
+
+ it { expect { described_class.new(Group, column) }.to raise_error(Gitlab::Database::LooseIndexScanDistinctCount::ColumnConfigurationError) }
+ end
+
+ context 'when null values are present' do
+ before do
+ create_list(:project, 2).each { |p| p.update_column(:creator_id, nil) }
+ end
+
+ it { is_expected.to eq(2) }
+ end
+ end
+
+ context 'counting STI models' do
+ let!(:groups) { create_list(:group, 3) }
+ let!(:namespaces) { create_list(:namespace, 2) }
+
+ let(:max_id) { Namespace.maximum(:id) + 1 }
+
+ it 'counts groups' do
+ count = described_class.new(Group, :id).count(from: 0, to: max_id)
+ expect(count).to eq(3)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 9178707a3d0..44293086e79 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -835,7 +835,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, '"users"', '"old"', '"new"')
+ .with(:users, :old, :new)
expect(model).to receive(:add_column)
.with(:users, :new, :integer,
@@ -860,14 +860,18 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
context 'with existing records and type casting' do
let(:trigger_name) { model.rename_trigger_name(:users, :id, :new) }
let(:user) { create(:user) }
+ let(:copy_trigger) { double('copy trigger') }
+
+ before do
+ expect(Gitlab::Database::UnidirectionalCopyTrigger).to receive(:on_table)
+ .with(:users).and_return(copy_trigger)
+ end
it 'copies the value to the new column using the type_cast_function', :aggregate_failures do
expect(model).to receive(:copy_indexes).with(:users, :id, :new)
expect(model).to receive(:add_not_null_constraint).with(:users, :new)
expect(model).to receive(:execute).with("UPDATE \"users\" SET \"new\" = cast_to_jsonb_with_default(\"users\".\"id\") WHERE \"users\".\"id\" >= #{user.id}")
- expect(model).to receive(:execute).with("DROP TRIGGER IF EXISTS #{trigger_name}\nON \"users\"\n")
- expect(model).to receive(:execute).with("CREATE TRIGGER #{trigger_name}\nBEFORE INSERT OR UPDATE\nON \"users\"\nFOR EACH ROW\nEXECUTE FUNCTION #{trigger_name}()\n")
- expect(model).to receive(:execute).with("CREATE OR REPLACE FUNCTION #{trigger_name}()\nRETURNS trigger AS\n$BODY$\nBEGIN\n NEW.\"new\" := NEW.\"id\";\n RETURN NEW;\nEND;\n$BODY$\nLANGUAGE 'plpgsql'\nVOLATILE\n")
+ expect(copy_trigger).to receive(:create).with(:id, :new, trigger_name: nil)
model.rename_column_concurrently(:users, :id, :new, type_cast_function: 'cast_to_jsonb_with_default')
end
@@ -996,7 +1000,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, '"users"', '"old"', '"new"')
+ .with(:users, :old, :new)
expect(model).to receive(:add_column)
.with(:users, :old, :integer,
@@ -1156,7 +1160,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
.with(:users, temp_undo_cleanup_column, :old)
expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, '"users"', '"old"', '"old_for_type_change"')
+ .with(:users, :old, 'old_for_type_change')
model.undo_cleanup_concurrent_column_type_change(:users, :old, :string)
end
@@ -1182,7 +1186,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
.with(:users, temp_undo_cleanup_column, :old)
expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, '"users"', '"old"', '"old_for_type_change"')
+ .with(:users, :old, 'old_for_type_change')
model.undo_cleanup_concurrent_column_type_change(
:users,
@@ -1204,28 +1208,25 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
describe '#install_rename_triggers_for_postgresql' do
it 'installs the triggers for PostgreSQL' do
- expect(model).to receive(:execute)
- .with(/CREATE OR REPLACE FUNCTION foo()/m)
+ copy_trigger = double('copy trigger')
- expect(model).to receive(:execute)
- .with(/DROP TRIGGER IF EXISTS foo/m)
+ expect(Gitlab::Database::UnidirectionalCopyTrigger).to receive(:on_table)
+ .with(:users).and_return(copy_trigger)
- expect(model).to receive(:execute)
- .with(/CREATE TRIGGER foo/m)
+ expect(copy_trigger).to receive(:create).with(:old, :new, trigger_name: 'foo')
- model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
- end
-
- it 'does not fail if trigger already exists' do
- model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
- model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
+ model.install_rename_triggers_for_postgresql(:users, :old, :new, trigger_name: 'foo')
end
end
describe '#remove_rename_triggers_for_postgresql' do
it 'removes the function and trigger' do
- expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo ON bar')
- expect(model).to receive(:execute).with('DROP FUNCTION IF EXISTS foo()')
+ copy_trigger = double('copy trigger')
+
+ expect(Gitlab::Database::UnidirectionalCopyTrigger).to receive(:on_table)
+ .with('bar').and_return(copy_trigger)
+
+ expect(copy_trigger).to receive(:drop).with('foo')
model.remove_rename_triggers_for_postgresql('bar', 'foo')
end
@@ -1702,65 +1703,171 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
describe '#initialize_conversion_of_integer_to_bigint' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:issue) { create(:issue, project: project) }
- let!(:event) do
- create(:event, :created, project: project, target: issue, author: user)
+ let(:table) { :test_table }
+ let(:column) { :id }
+ let(:tmp_column) { "#{column}_convert_to_bigint" }
+
+ before do
+ model.create_table table, id: false do |t|
+ t.integer :id, primary_key: true
+ t.integer :non_nullable_column, null: false
+ t.integer :nullable_column
+ t.timestamps
+ end
end
- context 'in a transaction' do
- it 'raises RuntimeError' do
- allow(model).to receive(:transaction_open?).and_return(true)
+ context 'when the target table does not exist' do
+ it 'raises an error' do
+ expect { model.initialize_conversion_of_integer_to_bigint(:this_table_is_not_real, column) }
+ .to raise_error('Table this_table_is_not_real does not exist')
+ end
+ end
- expect { model.initialize_conversion_of_integer_to_bigint(:events, :id) }
- .to raise_error(RuntimeError)
+ context 'when the primary key does not exist' do
+ it 'raises an error' do
+ expect { model.initialize_conversion_of_integer_to_bigint(table, column, primary_key: :foobar) }
+ .to raise_error("Column foobar does not exist on #{table}")
end
end
- context 'outside a transaction' do
- before do
- allow(model).to receive(:transaction_open?).and_return(false)
+ context 'when the column to convert does not exist' do
+ let(:column) { :foobar }
+
+ it 'raises an error' do
+ expect { model.initialize_conversion_of_integer_to_bigint(table, column) }
+ .to raise_error("Column #{column} does not exist on #{table}")
end
+ end
- it 'creates a bigint column and starts backfilling it' do
- expect(model)
- .to receive(:add_column)
- .with(
- :events,
- 'id_convert_to_bigint',
- :bigint,
- default: 0,
- null: false
- )
+ context 'when the column to convert is the primary key' do
+ it 'creates a not-null bigint column and installs triggers' do
+ expect(model).to receive(:add_column).with(table, tmp_column, :bigint, default: 0, null: false)
- expect(model)
- .to receive(:install_rename_triggers)
- .with(:events, :id, 'id_convert_to_bigint')
+ expect(model).to receive(:install_rename_triggers).with(table, column, tmp_column)
- expect(model).to receive(:queue_background_migration_jobs_by_range_at_intervals).and_call_original
+ model.initialize_conversion_of_integer_to_bigint(table, column)
+ end
+ end
- expect(BackgroundMigrationWorker)
- .to receive(:perform_in)
- .ordered
- .with(
- 2.minutes,
- 'CopyColumnUsingBackgroundMigrationJob',
- [event.id, event.id, :events, :id, 100, :id, 'id_convert_to_bigint']
- )
+ context 'when the column to convert is not the primary key, but non-nullable' do
+ let(:column) { :non_nullable_column }
+
+ it 'creates a not-null bigint column and installs triggers' do
+ expect(model).to receive(:add_column).with(table, tmp_column, :bigint, default: 0, null: false)
+
+ expect(model).to receive(:install_rename_triggers).with(table, column, tmp_column)
+
+ model.initialize_conversion_of_integer_to_bigint(table, column)
+ end
+ end
+
+ context 'when the column to convert is not the primary key, but nullable' do
+ let(:column) { :nullable_column }
+
+ it 'creates a nullable bigint column and installs triggers' do
+ expect(model).to receive(:add_column).with(table, tmp_column, :bigint, default: nil)
+
+ expect(model).to receive(:install_rename_triggers).with(table, column, tmp_column)
+
+ model.initialize_conversion_of_integer_to_bigint(table, column)
+ end
+ end
+ end
+
+ describe '#backfill_conversion_of_integer_to_bigint' do
+ let(:table) { :_test_backfill_table }
+ let(:column) { :id }
+ let(:tmp_column) { "#{column}_convert_to_bigint" }
+
+ before do
+ model.create_table table, id: false do |t|
+ t.integer :id, primary_key: true
+ t.text :message, null: false
+ t.timestamps
+ end
- expect(Gitlab::BackgroundMigration)
- .to receive(:steal)
- .ordered
- .with('CopyColumnUsingBackgroundMigrationJob')
+ allow(model).to receive(:perform_background_migration_inline?).and_return(false)
+ end
- model.initialize_conversion_of_integer_to_bigint(
- :events,
- :id,
- batch_size: 300,
- sub_batch_size: 100
+ context 'when the target table does not exist' do
+ it 'raises an error' do
+ expect { model.backfill_conversion_of_integer_to_bigint(:this_table_is_not_real, column) }
+ .to raise_error('Table this_table_is_not_real does not exist')
+ end
+ end
+
+ context 'when the primary key does not exist' do
+ it 'raises an error' do
+ expect { model.backfill_conversion_of_integer_to_bigint(table, column, primary_key: :foobar) }
+ .to raise_error("Column foobar does not exist on #{table}")
+ end
+ end
+
+ context 'when the column to convert does not exist' do
+ let(:column) { :foobar }
+
+ it 'raises an error' do
+ expect { model.backfill_conversion_of_integer_to_bigint(table, column) }
+ .to raise_error("Column #{column} does not exist on #{table}")
+ end
+ end
+
+ context 'when the temporary column does not exist' do
+ it 'raises an error' do
+ expect { model.backfill_conversion_of_integer_to_bigint(table, column) }
+ .to raise_error('The temporary column does not exist, initialize it with `initialize_conversion_of_integer_to_bigint`')
+ end
+ end
+
+ context 'when the conversion is properly initialized' do
+ let(:model_class) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = :_test_backfill_table
+ end
+ end
+
+ let(:migration_relation) { Gitlab::Database::BackgroundMigration::BatchedMigration.active }
+
+ before do
+ model.initialize_conversion_of_integer_to_bigint(table, column)
+
+ model_class.create!(message: 'hello')
+ model_class.create!(message: 'so long')
+ end
+
+ it 'creates the batched migration tracking record' do
+ last_record = model_class.create!(message: 'goodbye')
+
+ expect do
+ model.backfill_conversion_of_integer_to_bigint(table, column, batch_size: 2, sub_batch_size: 1)
+ end.to change { migration_relation.count }.by(1)
+
+ expect(migration_relation.last).to have_attributes(
+ job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
+ table_name: table.to_s,
+ column_name: column.to_s,
+ min_value: 1,
+ max_value: last_record.id,
+ interval: 120,
+ batch_size: 2,
+ sub_batch_size: 1,
+ job_arguments: [column.to_s, "#{column}_convert_to_bigint"]
)
end
+
+ context 'when the migration should be performed inline' do
+ it 'calls the runner to run the entire migration' do
+ expect(model).to receive(:perform_background_migration_inline?).and_return(true)
+
+ expect_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigrationRunner) do |scheduler|
+ expect(scheduler).to receive(:run_entire_migration) do |batched_migration|
+ expect(batched_migration).to eq(migration_relation.last)
+ end
+ end
+
+ model.backfill_conversion_of_integer_to_bigint(table, column, batch_size: 2, sub_batch_size: 1)
+ end
+ end
end
end
@@ -1910,9 +2017,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
def setup
namespace = namespaces.create!(name: 'foo', path: 'foo')
- project = projects.create!(namespace_id: namespace.id)
-
- project
+ projects.create!(namespace_id: namespace.id)
end
it 'generates iids properly for models created after the migration' do
diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
index e25e4af2e86..c6d456964cf 100644
--- a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
@@ -263,7 +263,15 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
end
describe '#queue_batched_background_migration' do
+ let(:pgclass_info) { instance_double('Gitlab::Database::PgClass', cardinality_estimate: 42) }
+
+ before do
+ allow(Gitlab::Database::PgClass).to receive(:for_table).and_call_original
+ end
+
it 'creates the database record for the migration' do
+ expect(Gitlab::Database::PgClass).to receive(:for_table).with(:projects).and_return(pgclass_info)
+
expect do
model.queue_batched_background_migration(
'MyJobClass',
@@ -288,7 +296,8 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
batch_size: 100,
sub_batch_size: 10,
job_arguments: %w[],
- status: 'active')
+ status: 'active',
+ total_tuple_count: pgclass_info.cardinality_estimate)
end
context 'when the job interval is lower than the minimum' do
@@ -431,4 +440,21 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
end
end
+
+ describe '#delete_queued_jobs' do
+ let(:job1) { double }
+ let(:job2) { double }
+
+ it 'deletes all queued jobs for the given background migration' do
+ expect(Gitlab::BackgroundMigration).to receive(:steal).with('BackgroundMigrationClassName') do |&block|
+ expect(block.call(job1)).to be(false)
+ expect(block.call(job2)).to be(false)
+ end
+
+ expect(job1).to receive(:delete)
+ expect(job2).to receive(:delete)
+
+ model.delete_queued_jobs('BackgroundMigrationClassName')
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
index b5d741fc5e9..5b2a29d1d2d 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
@@ -704,6 +704,72 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
end
+ describe '#drop_nonpartitioned_archive_table' do
+ subject { migration.drop_nonpartitioned_archive_table source_table }
+
+ let(:archived_table) { "#{source_table}_archived" }
+
+ before do
+ migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date
+ migration.replace_with_partitioned_table source_table
+ end
+
+ it 'drops the archive table' do
+ expect(table_type(archived_table)).to eq('normal')
+
+ subject
+
+ expect(table_type(archived_table)).to eq(nil)
+ end
+
+ it 'drops the trigger on the source table' do
+ expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update])
+
+ subject
+
+ expect_trigger_not_to_exist(source_table, trigger_name)
+ end
+
+ it 'drops the sync function' do
+ expect_function_to_exist(function_name)
+
+ subject
+
+ expect_function_not_to_exist(function_name)
+ end
+ end
+
+ describe '#create_trigger_to_sync_tables' do
+ subject { migration.create_trigger_to_sync_tables(source_table, target_table, :id) }
+
+ let(:target_table) { "#{source_table}_copy" }
+
+ before do
+ migration.create_table target_table do |t|
+ t.string :name, null: false
+ t.integer :age, null: false
+ t.datetime partition_column
+ t.datetime :updated_at
+ end
+ end
+
+ it 'creates the sync function' do
+ expect_function_not_to_exist(function_name)
+
+ subject
+
+ expect_function_to_exist(function_name)
+ end
+
+ it 'installs the trigger' do
+ expect_trigger_not_to_exist(source_table, trigger_name)
+
+ subject
+
+ expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update])
+ end
+ end
+
def filter_columns_by_name(columns, names)
columns.reject { |c| names.include?(c.name) }
end
diff --git a/spec/lib/gitlab/database/pg_class_spec.rb b/spec/lib/gitlab/database/pg_class_spec.rb
new file mode 100644
index 00000000000..83b50415a6c
--- /dev/null
+++ b/spec/lib/gitlab/database/pg_class_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PgClass, type: :model do
+ describe '#cardinality_estimate' do
+ context 'when no information is available' do
+ subject { described_class.new(reltuples: 0.0).cardinality_estimate }
+
+ it 'returns nil for the estimate' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'with reltuples available' do
+ subject { described_class.new(reltuples: 42.0).cardinality_estimate }
+
+ it 'returns the reltuples for the estimate' do
+ expect(subject).to eq(42)
+ end
+ end
+ end
+
+ describe '.for_table' do
+ let(:relname) { :projects }
+
+ subject { described_class.for_table(relname) }
+
+ it 'returns PgClass for this table' do
+ expect(subject).to be_a(described_class)
+ end
+
+ it 'matches the relname' do
+ expect(subject.relname).to eq(relname.to_s)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index 757da2d9092..1edcd890370 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -246,7 +246,8 @@ RSpec.describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :
subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed')
- old_path, new_path = [nil, nil]
+ old_path = nil
+ new_path = nil
Gitlab::Redis::SharedState.with do |redis|
rename_info = redis.lpop(key)
old_path, new_path = Gitlab::Json.parse(rename_info)
diff --git a/spec/lib/gitlab/database/unidirectional_copy_trigger_spec.rb b/spec/lib/gitlab/database/unidirectional_copy_trigger_spec.rb
new file mode 100644
index 00000000000..2955c208f16
--- /dev/null
+++ b/spec/lib/gitlab/database/unidirectional_copy_trigger_spec.rb
@@ -0,0 +1,191 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::UnidirectionalCopyTrigger do
+ include Database::TriggerHelpers
+
+ let(:table_name) { '_test_table' }
+ let(:connection) { ActiveRecord::Base.connection }
+ let(:copy_trigger) { described_class.on_table(table_name) }
+
+ describe '#name' do
+ context 'when a single column name is given' do
+ subject(:trigger_name) { copy_trigger.name('id', 'other_id') }
+
+ it 'returns the trigger name' do
+ expect(trigger_name).to eq('trigger_cfce7a56a9d6')
+ end
+ end
+
+ context 'when multiple column names are given' do
+ subject(:trigger_name) { copy_trigger.name(%w[id fk_id], %w[other_id other_fk_id]) }
+
+ it 'returns the trigger name' do
+ expect(trigger_name).to eq('trigger_166626e51481')
+ end
+ end
+
+ context 'when a different number of new and old column names are given' do
+ it 'raises an error' do
+ expect do
+ copy_trigger.name(%w[id fk_id], %w[other_id])
+ end.to raise_error(ArgumentError, 'number of source and destination columns must match')
+ end
+ end
+ end
+
+ describe '#create' do
+ let(:model) { Class.new(ActiveRecord::Base) }
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{table_name} (
+ id serial NOT NULL PRIMARY KEY,
+ other_id integer,
+ fk_id bigint,
+ other_fk_id bigint);
+ SQL
+
+ model.table_name = table_name
+ end
+
+ context 'when a single column name is given' do
+ let(:trigger_name) { 'trigger_cfce7a56a9d6' }
+
+ it 'creates the trigger and function' do
+ expect_function_not_to_exist(trigger_name)
+ expect_trigger_not_to_exist(table_name, trigger_name)
+
+ copy_trigger.create('id', 'other_id')
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+ end
+
+ it 'properly copies the column data using the trigger function' do
+ copy_trigger.create('id', 'other_id')
+
+ record = model.create!(id: 10)
+ expect(record.reload).to have_attributes(other_id: 10)
+
+ record.update!({ id: 20 })
+ expect(record.reload).to have_attributes(other_id: 20)
+ end
+ end
+
+ context 'when multiple column names are given' do
+ let(:trigger_name) { 'trigger_166626e51481' }
+
+ it 'creates the trigger and function to set all the columns' do
+ expect_function_not_to_exist(trigger_name)
+ expect_trigger_not_to_exist(table_name, trigger_name)
+
+ copy_trigger.create(%w[id fk_id], %w[other_id other_fk_id])
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+ end
+
+ it 'properly copies the columns using the trigger function' do
+ copy_trigger.create(%w[id fk_id], %w[other_id other_fk_id])
+
+ record = model.create!(id: 10, fk_id: 20)
+ expect(record.reload).to have_attributes(other_id: 10, other_fk_id: 20)
+
+ record.update!(id: 30, fk_id: 50)
+ expect(record.reload).to have_attributes(other_id: 30, other_fk_id: 50)
+ end
+ end
+
+ context 'when a custom trigger name is given' do
+ let(:trigger_name) { '_test_trigger' }
+
+ it 'creates the trigger and function with the custom name' do
+ expect_function_not_to_exist(trigger_name)
+ expect_trigger_not_to_exist(table_name, trigger_name)
+
+ copy_trigger.create('id', 'other_id', trigger_name: trigger_name)
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+ end
+ end
+
+ context 'when the trigger function already exists' do
+ let(:trigger_name) { 'trigger_cfce7a56a9d6' }
+
+ it 'does not raise an error' do
+ expect_function_not_to_exist(trigger_name)
+ expect_trigger_not_to_exist(table_name, trigger_name)
+
+ copy_trigger.create('id', 'other_id')
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+
+ copy_trigger.create('id', 'other_id')
+
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+ end
+ end
+
+ context 'when a different number of new and old column names are given' do
+ it 'raises an error' do
+ expect do
+ copy_trigger.create(%w[id fk_id], %w[other_id])
+ end.to raise_error(ArgumentError, 'number of source and destination columns must match')
+ end
+ end
+ end
+
+ describe '#drop' do
+ let(:trigger_name) { '_test_trigger' }
+
+ before do
+ connection.execute(<<~SQL)
+ CREATE TABLE #{table_name} (
+ id serial NOT NULL PRIMARY KEY,
+ other_id integer NOT NULL);
+
+ CREATE FUNCTION #{trigger_name}()
+ RETURNS trigger
+ LANGUAGE plpgsql AS
+ $$
+ BEGIN
+ RAISE NOTICE 'hello';
+ RETURN NEW;
+ END
+ $$;
+
+ CREATE TRIGGER #{trigger_name}
+ BEFORE INSERT OR UPDATE
+ ON #{table_name}
+ FOR EACH ROW
+ EXECUTE FUNCTION #{trigger_name}();
+ SQL
+ end
+
+ it 'drops the trigger and function for the given arguments' do
+ expect_function_to_exist(trigger_name)
+ expect_valid_function_trigger(table_name, trigger_name, trigger_name, before: %w[insert update])
+
+ copy_trigger.drop(trigger_name)
+
+ expect_trigger_not_to_exist(table_name, trigger_name)
+ expect_function_not_to_exist(trigger_name)
+ end
+
+ context 'when the trigger does not exist' do
+ it 'does not raise an error' do
+ copy_trigger.drop(trigger_name)
+
+ expect_trigger_not_to_exist(table_name, trigger_name)
+ expect_function_not_to_exist(trigger_name)
+
+ copy_trigger.drop(trigger_name)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
index 39029322e25..e70b34d6557 100644
--- a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
+++ b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do
end
end
- context 'with application settings and admin users' do
+ context 'with application settings and admin users', :do_not_mock_admin_mode_setting do
let(:group) { result[:group] }
let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 1553a989dba..b735ac7940b 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -407,13 +407,13 @@ RSpec.describe Gitlab::Database do
expect(described_class.db_read_only?).to be_truthy
end
- it 'detects a read write database' do
+ it 'detects a read-write database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
expect(described_class.db_read_only?).to be_falsey
end
- it 'detects a read write database' do
+ it 'detects a read-write database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }])
expect(described_class.db_read_only?).to be_falsey
diff --git a/spec/lib/gitlab/diff/char_diff_spec.rb b/spec/lib/gitlab/diff/char_diff_spec.rb
index e4e2a3ba050..d38008c16f2 100644
--- a/spec/lib/gitlab/diff/char_diff_spec.rb
+++ b/spec/lib/gitlab/diff/char_diff_spec.rb
@@ -49,15 +49,15 @@ RSpec.describe Gitlab::Diff::CharDiff do
old_diffs, new_diffs = subject
expect(old_diffs).to eq([])
- expect(new_diffs).to eq([0..12])
+ expect(new_diffs).to eq([Gitlab::MarkerRange.new(0, 12, mode: :addition)])
end
end
it 'returns ranges of changes' do
old_diffs, new_diffs = subject
- expect(old_diffs).to eq([11..11])
- expect(new_diffs).to eq([3..3])
+ expect(old_diffs).to eq([Gitlab::MarkerRange.new(11, 11, mode: :deletion)])
+ expect(new_diffs).to eq([Gitlab::MarkerRange.new(3, 3, mode: :addition)])
end
end
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index d26bc5fc9a8..4c56911e665 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -238,16 +238,36 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
subject { cache.key }
it 'returns cache key' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:true:true")
end
- context 'when feature flag is disabled' do
+ context 'when the `introduce_marker_ranges` feature flag is disabled' do
before do
stub_feature_flags(introduce_marker_ranges: false)
end
it 'returns the original version of the cache' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:false")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:false:true:true")
+ end
+ end
+
+ context 'when the `use_marker_ranges` feature flag is disabled' do
+ before do
+ stub_feature_flags(use_marker_ranges: false)
+ end
+
+ it 'returns the original version of the cache' do
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:false:true")
+ end
+ end
+
+ context 'when the `diff_line_syntax_highlighting` feature flag is disabled' do
+ before do
+ stub_feature_flags(diff_line_syntax_highlighting: false)
+ end
+
+ it 'returns the original version of the cache' do
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:true:false")
end
end
end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index e613674af3a..32ca6e4fde6 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -65,6 +65,14 @@ RSpec.describe Gitlab::Diff::Highlight do
expect(subject[5].rich_text).to eq(code)
end
+
+ context 'when use_marker_ranges feature flag is false too' do
+ it 'does not affect the result' do
+ code = %Q{+<span id="LC9" class="line" lang="ruby"> <span class="k">raise</span> <span class="no"><span class="idiff left">RuntimeError</span></span><span class="p"><span class="idiff">,</span></span><span class="idiff right"> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
+
+ expect(subject[5].rich_text).to eq(code)
+ end
+ end
end
context 'when no diff_refs' do
@@ -132,6 +140,18 @@ RSpec.describe Gitlab::Diff::Highlight do
end
end
+ context 'when `use_marker_ranges` feature flag is disabled' do
+ it 'returns the same result' do
+ with_feature_flag = described_class.new(diff_file, repository: project.repository).highlight
+
+ stub_feature_flags(use_marker_ranges: false)
+
+ without_feature_flag = described_class.new(diff_file, repository: project.repository).highlight
+
+ expect(with_feature_flag.map(&:rich_text)).to eq(without_feature_flag.map(&:rich_text))
+ end
+ end
+
context 'when no inline diffs' do
it_behaves_like 'without inline diffs'
end
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 714b5d813c4..d7b50eb73ee 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -3,68 +3,30 @@
require 'spec_helper'
RSpec.describe Gitlab::Diff::InlineDiff do
- describe '.for_lines' do
- let(:diff) do
- <<-EOF.strip_heredoc
- class Test
- - def initialize(test = true)
- + def initialize(test = false)
- @test = test
- - if true
- - @foo = "bar"
- + unless false
- + @foo = "baz"
- end
- end
- end
- EOF
- end
-
- let(:subject) { described_class.for_lines(diff.lines) }
+ describe '#inline_diffs' do
+ subject { described_class.new(old_line, new_line, offset: offset).inline_diffs }
- it 'finds all inline diffs' do
- expect(subject[0]).to be_nil
- expect(subject[1]).to eq([25..27])
- expect(subject[2]).to eq([25..28])
- expect(subject[3]).to be_nil
- expect(subject[4]).to eq([5..10])
- expect(subject[5]).to eq([17..17])
- expect(subject[6]).to eq([5..15])
- expect(subject[7]).to eq([17..17])
- expect(subject[8]).to be_nil
- end
+ let(:old_line) { 'XXX def initialize(test = true)' }
+ let(:new_line) { 'YYY def initialize(test = false)' }
+ let(:offset) { 3 }
- it 'can handle unchanged empty lines' do
- expect { described_class.for_lines(['- bar', '+ baz', '']) }.not_to raise_error
+ it 'finds the inline diff', :aggregate_failures do
+ expect(subject[0]).to eq([Gitlab::MarkerRange.new(26, 28, mode: :deletion)])
+ expect(subject[1]).to eq([Gitlab::MarkerRange.new(26, 29, mode: :addition)])
end
context 'when lines have multiple changes' do
- let(:diff) do
- <<~EOF
- - Hello, how are you?
- + Hi, how are you doing?
- EOF
- end
-
- let(:subject) { described_class.for_lines(diff.lines) }
-
- it 'finds all inline diffs' do
- expect(subject[0]).to eq([3..6])
- expect(subject[1]).to eq([3..3, 17..22])
+ let(:old_line) { '- Hello, how are you?' }
+ let(:new_line) { '+ Hi, how are you doing?' }
+ let(:offset) { 1 }
+
+ it 'finds all inline diffs', :aggregate_failures do
+ expect(subject[0]).to eq([Gitlab::MarkerRange.new(3, 6, mode: :deletion)])
+ expect(subject[1]).to eq([
+ Gitlab::MarkerRange.new(3, 3, mode: :addition),
+ Gitlab::MarkerRange.new(17, 22, mode: :addition)
+ ])
end
end
end
-
- describe "#inline_diffs" do
- let(:old_line) { "XXX def initialize(test = true)" }
- let(:new_line) { "YYY def initialize(test = false)" }
- let(:subject) { described_class.new(old_line, new_line, offset: 3).inline_diffs }
-
- it "finds the inline diff" do
- old_diffs, new_diffs = subject
-
- expect(old_diffs).to eq([26..28])
- expect(new_diffs).to eq([26..29])
- end
- end
end
diff --git a/spec/lib/gitlab/diff/line_spec.rb b/spec/lib/gitlab/diff/line_spec.rb
index e10a50afde9..949def599ae 100644
--- a/spec/lib/gitlab/diff/line_spec.rb
+++ b/spec/lib/gitlab/diff/line_spec.rb
@@ -17,6 +17,8 @@ RSpec.describe Gitlab::Diff::Line do
rich_text: rich_text)
end
+ let(:rich_text) { nil }
+
describe '.init_from_hash' do
let(:rich_text) { '&lt;input&gt;' }
@@ -43,6 +45,29 @@ RSpec.describe Gitlab::Diff::Line do
end
end
+ describe '#text' do
+ let(:line) { described_class.new(raw_diff, 'new', 0, 0, 0) }
+ let(:raw_diff) { '+Hello' }
+
+ it 'returns raw diff text' do
+ expect(line.text).to eq('+Hello')
+ end
+
+ context 'when prefix is disabled' do
+ it 'returns raw diff text without prefix' do
+ expect(line.text(prefix: false)).to eq('Hello')
+ end
+
+ context 'when diff is empty' do
+ let(:raw_diff) { '' }
+
+ it 'returns an empty raw diff' do
+ expect(line.text(prefix: false)).to eq('')
+ end
+ end
+ end
+ end
+
context "when setting rich text" do
it 'escapes any HTML special characters in the diff chunk header' do
subject = described_class.new("<input>", "", 0, 0, 0)
@@ -51,4 +76,14 @@ RSpec.describe Gitlab::Diff::Line do
expect(line[:rich_text]).to eq("&lt;input&gt;")
end
end
+
+ describe '#set_marker_ranges' do
+ let(:marker_ranges) { [Gitlab::MarkerRange.new(1, 10, mode: :deletion)] }
+
+ it 'stores MarkerRanges in Diff::Line object' do
+ line.set_marker_ranges(marker_ranges)
+
+ expect(line.marker_ranges).to eq(marker_ranges)
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/lines_unfolder_spec.rb b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
index 4163c0eced5..8385cba3532 100644
--- a/spec/lib/gitlab/diff/lines_unfolder_spec.rb
+++ b/spec/lib/gitlab/diff/lines_unfolder_spec.rb
@@ -302,7 +302,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -396,7 +397,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -490,7 +492,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -581,7 +584,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -691,7 +695,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
@@ -783,7 +788,8 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
new_diff_lines = subject.unfolded_diff_lines
new_diff_lines.each_with_index do |line, i|
- old_pos, new_pos = expected_diff_lines[i][0], expected_diff_lines[i][1]
+ old_pos = expected_diff_lines[i][0]
+ new_pos = expected_diff_lines[i][1]
unless line.type == 'match'
expect(line.line_code).to eq(Gitlab::Git.diff_line_code(diff_file.file_path, new_pos, old_pos))
diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
index 5efce414dc8..a00c55d4fb2 100644
--- a/spec/lib/gitlab/diff/suggestions_parser_spec.rb
+++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
@@ -56,7 +56,8 @@ RSpec.describe Gitlab::Diff::SuggestionsParser do
end
it 'parsed suggestion has correct data' do
- from_line, to_line = position.new_line, position.new_line
+ from_line = position.new_line
+ to_line = position.new_line
expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line),
to_content: " foo\n bar\n",
diff --git a/spec/lib/gitlab/downtime_check/message_spec.rb b/spec/lib/gitlab/downtime_check/message_spec.rb
deleted file mode 100644
index 2d82836db33..00000000000
--- a/spec/lib/gitlab/downtime_check/message_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::DowntimeCheck::Message do
- describe '#to_s' do
- it 'returns an ANSI formatted String for an offline migration' do
- message = described_class.new('foo.rb', true, 'hello')
-
- expect(message.to_s).to eq("[\e[31moffline\e[0m]: foo.rb:\n\nhello\n\n")
- end
-
- it 'returns an ANSI formatted String for an online migration' do
- message = described_class.new('foo.rb')
-
- expect(message.to_s).to eq("[\e[32monline\e[0m]: foo.rb")
- end
- end
-
- describe '#reason?' do
- it 'returns false when no reason is specified' do
- message = described_class.new('foo.rb')
-
- expect(message.reason?).to eq(false)
- end
-
- it 'returns true when a reason is specified' do
- message = described_class.new('foo.rb', true, 'hello')
-
- expect(message.reason?).to eq(true)
- end
- end
-
- describe '#reason' do
- it 'strips excessive whitespace from the returned String' do
- message = described_class.new('foo.rb', true, " hello\n world\n\n foo")
-
- expect(message.reason).to eq("hello\nworld\n\nfoo")
- end
- end
-end
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
deleted file mode 100644
index 761519425f6..00000000000
--- a/spec/lib/gitlab/downtime_check_spec.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::DowntimeCheck do
- subject { described_class.new }
-
- let(:path) { 'foo.rb' }
-
- describe '#check' do
- before do
- expect(subject).to receive(:require).with(path)
- end
-
- context 'when a migration does not specify if downtime is required' do
- it 'raises RuntimeError' do
- expect(subject).to receive(:class_for_migration_file)
- .with(path)
- .and_return(Class.new)
-
- expect { subject.check([path]) }
- .to raise_error(RuntimeError, /it requires downtime/)
- end
- end
-
- context 'when a migration requires downtime' do
- context 'when no reason is specified' do
- it 'raises RuntimeError' do
- stub_const('TestMigration::DOWNTIME', true)
-
- expect(subject).to receive(:class_for_migration_file)
- .with(path)
- .and_return(TestMigration)
-
- expect { subject.check([path]) }
- .to raise_error(RuntimeError, /no reason was given/)
- end
- end
-
- context 'when a reason is specified' do
- it 'returns an Array of messages' do
- stub_const('TestMigration::DOWNTIME', true)
- stub_const('TestMigration::DOWNTIME_REASON', 'foo')
-
- expect(subject).to receive(:class_for_migration_file)
- .with(path)
- .and_return(TestMigration)
-
- messages = subject.check([path])
-
- expect(messages).to be_an_instance_of(Array)
- expect(messages[0]).to be_an_instance_of(Gitlab::DowntimeCheck::Message)
-
- message = messages[0]
-
- expect(message.path).to eq(path)
- expect(message.offline).to eq(true)
- expect(message.reason).to eq('foo')
- end
- end
- end
- end
-
- describe '#check_and_print' do
- it 'checks the migrations and prints the results to STDOUT' do
- stub_const('TestMigration::DOWNTIME', true)
- stub_const('TestMigration::DOWNTIME_REASON', 'foo')
-
- expect(subject).to receive(:require).with(path)
-
- expect(subject).to receive(:class_for_migration_file)
- .with(path)
- .and_return(TestMigration)
-
- expect(subject).to receive(:puts).with(an_instance_of(String))
-
- subject.check_and_print([path])
- end
- end
-
- describe '#class_for_migration_file' do
- it 'returns the class for a migration file path' do
- expect(subject.class_for_migration_file('123_string.rb')).to eq(String)
- end
- end
-
- describe '#online?' do
- it 'returns true when a migration can be performed online' do
- stub_const('TestMigration::DOWNTIME', false)
-
- expect(subject.online?(TestMigration)).to eq(true)
- end
-
- it 'returns false when a migration can not be performed online' do
- stub_const('TestMigration::DOWNTIME', true)
-
- expect(subject.online?(TestMigration)).to eq(false)
- end
- end
-
- describe '#downtime_reason' do
- context 'when a reason is defined' do
- it 'returns the downtime reason' do
- stub_const('TestMigration::DOWNTIME_REASON', 'hello')
-
- expect(subject.downtime_reason(TestMigration)).to eq('hello')
- end
- end
-
- context 'when a reason is not defined' do
- it 'returns nil' do
- expect(subject.downtime_reason(Class.new)).to be_nil
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index 8872800069a..e76a5d3fe32 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
end
shared_examples 'a reply to existing comment' do
- it 'creates a comment' do
+ it 'creates a discussion' do
expect { receiver.execute }.to change { noteable.notes.count }.by(1)
new_note = noteable.notes.last
@@ -68,11 +68,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
expect(new_note.note).to include('I could not disagree more.')
expect(new_note.in_reply_to?(note)).to be_truthy
- if note.part_of_discussion?
- expect(new_note.discussion_id).to eq(note.discussion_id)
- else
- expect(new_note.discussion_id).not_to eq(note.discussion_id)
- end
+ expect(new_note.discussion_id).to eq(note.discussion_id)
end
end
diff --git a/spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb
index 94f28d3399a..d3535fa9bd3 100644
--- a/spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_on_issuable_handler_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteOnIssuableHandler do
it_behaves_like :note_handler_shared_examples, true do
let_it_be(:recipient) { user }
- let(:update_commands_only) { email_reply_fixture('emails/update_commands_only_reply.eml') }
+ let(:update_commands_only) { email_reply_fixture('emails/update_commands_only.eml') }
let(:no_content) { email_reply_fixture('emails/no_content_reply.eml') }
let(:commands_in_reply) { email_reply_fixture('emails/commands_in_reply.eml') }
let(:with_quick_actions) { email_reply_fixture('emails/valid_reply_with_quick_actions.eml') }
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
index 13ad9ddd8ef..2c1badbd113 100644
--- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::Email::Handler::UnsubscribeHandler do
context 'when the noteable could not be found' do
before do
- noteable.destroy
+ noteable.destroy!
end
it 'raises a NoteableNotFoundError' do
diff --git a/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb
index 0db40eca989..24f5299d357 100644
--- a/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb
@@ -1,45 +1,66 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do
- subject(:processor) { described_class.new }
-
- before do
- allow_next_instance_of(Gitlab::ErrorTracking::ContextPayloadGenerator) do |generator|
- allow(generator).to receive(:generate).and_return(
- user: { username: 'root' },
- tags: { locale: 'en', program: 'test', feature_category: 'feature_a', correlation_id: 'cid' },
- extra: { some_info: 'info' }
- )
+ shared_examples 'processing an exception' do
+ before do
+ allow_next_instance_of(Gitlab::ErrorTracking::ContextPayloadGenerator) do |generator|
+ allow(generator).to receive(:generate).and_return(
+ user: { username: 'root' },
+ tags: { locale: 'en', program: 'test', feature_category: 'feature_a', correlation_id: 'cid' },
+ extra: { some_info: 'info' }
+ )
+ end
end
- end
- it 'merges the context payload into event payload' do
- payload = {
- user: { ip_address: '127.0.0.1' },
- tags: { priority: 'high' },
- extra: { sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] } }
- }
-
- processor.process(payload)
-
- expect(payload).to eql(
- user: {
- ip_address: '127.0.0.1',
- username: 'root'
- },
- tags: {
- priority: 'high',
- locale: 'en',
- program: 'test',
- feature_category: 'feature_a',
- correlation_id: 'cid'
- },
- extra: {
- some_info: 'info',
- sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] }
+ let(:payload) do
+ {
+ user: { ip_address: '127.0.0.1' },
+ tags: { priority: 'high' },
+ extra: { sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] } }
}
- )
+ end
+
+ it 'merges the context payload into event payload', :aggregate_failures do
+ expect(result_hash[:user]).to include(ip_address: '127.0.0.1', username: 'root')
+
+ expect(result_hash[:tags])
+ .to include(priority: 'high',
+ locale: 'en',
+ program: 'test',
+ feature_category: 'feature_a',
+ correlation_id: 'cid')
+
+ expect(result_hash[:extra])
+ .to include(some_info: 'info',
+ sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] })
+ end
+ end
+
+ describe '.call' do
+ let(:event) { Raven::Event.new(payload) }
+ let(:result_hash) { described_class.call(event).to_hash }
+
+ it_behaves_like 'processing an exception'
+
+ context 'when followed by #process' do
+ let(:result_hash) { described_class.new.process(described_class.call(event).to_hash) }
+
+ it_behaves_like 'processing an exception'
+ end
+ end
+
+ describe '#process' do
+ let(:event) { Raven::Event.new(payload) }
+ let(:result_hash) { described_class.new.process(event.to_hash) }
+
+ context 'with sentry_processors_before_send disabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: false)
+ end
+
+ it_behaves_like 'processing an exception'
+ end
end
end
diff --git a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
index 797707114a1..4808fdf2f06 100644
--- a/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/grpc_error_processor_spec.rb
@@ -3,73 +3,83 @@
require 'spec_helper'
RSpec.describe Gitlab::ErrorTracking::Processor::GrpcErrorProcessor do
- describe '#process' do
- subject { described_class.new }
-
+ shared_examples 'processing an exception' do
context 'when there is no GRPC exception' do
+ let(:exception) { RuntimeError.new }
let(:data) { { fingerprint: ['ArgumentError', 'Missing arguments'] } }
it 'leaves data unchanged' do
- expect(subject.process(data)).to eq(data)
+ expect(result_hash).to include(data)
end
end
context 'when there is a GPRC exception with a debug string' do
+ let(:exception) { GRPC::DeadlineExceeded.new('Deadline Exceeded', {}, '{"hello":1}') }
+
let(:data) do
{
- exception: {
- values: [
- {
- type: "GRPC::DeadlineExceeded",
- value: "4:DeadlineExceeded. debug_error_string:{\"hello\":1}"
- }
- ]
- },
extra: {
caller: 'test'
},
fingerprint: [
- "GRPC::DeadlineExceeded",
- "4:Deadline Exceeded. debug_error_string:{\"created\":\"@1598938192.005782000\",\"description\":\"Error received from peer unix:/home/git/gitalypraefect.socket\",\"file\":\"src/core/lib/surface/call.cc\",\"file_line\":1055,\"grpc_message\":\"Deadline Exceeded\",\"grpc_status\":4}"
+ 'GRPC::DeadlineExceeded',
+ '4:Deadline Exceeded. debug_error_string:{"created":"@1598938192.005782000","description":"Error received from peer unix:/home/git/gitalypraefect.socket","file":"src/core/lib/surface/call.cc","file_line":1055,"grpc_message":"Deadline Exceeded","grpc_status":4}'
]
}
end
- let(:expected) do
- {
- fingerprint: [
- "GRPC::DeadlineExceeded",
- "4:Deadline Exceeded."
- ],
- exception: {
- values: [
- {
- type: "GRPC::DeadlineExceeded",
- value: "4:DeadlineExceeded."
- }
- ]
- },
- extra: {
- caller: 'test',
- grpc_debug_error_string: "{\"hello\":1}"
- }
- }
- end
-
it 'removes the debug error string and stores it as an extra field' do
- expect(subject.process(data)).to eq(expected)
+ expect(result_hash[:fingerprint])
+ .to eq(['GRPC::DeadlineExceeded', '4:Deadline Exceeded.'])
+
+ expect(result_hash[:exception][:values].first)
+ .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:extra])
+ .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
end
context 'with no custom fingerprint' do
- before do
- data.delete(:fingerprint)
- expected.delete(:fingerprint)
+ let(:data) do
+ { extra: { caller: 'test' } }
end
it 'removes the debug error string and stores it as an extra field' do
- expect(subject.process(data)).to eq(expected)
+ expect(result_hash).not_to include(:fingerprint)
+
+ expect(result_hash[:exception][:values].first)
+ .to include(type: 'GRPC::DeadlineExceeded', value: '4:Deadline Exceeded.')
+
+ expect(result_hash[:extra])
+ .to include(caller: 'test', grpc_debug_error_string: '{"hello":1}')
end
end
end
end
+
+ describe '.call' do
+ let(:event) { Raven::Event.from_exception(exception, data) }
+ let(:result_hash) { described_class.call(event).to_hash }
+
+ it_behaves_like 'processing an exception'
+
+ context 'when followed by #process' do
+ let(:result_hash) { described_class.new.process(described_class.call(event).to_hash) }
+
+ it_behaves_like 'processing an exception'
+ end
+ end
+
+ describe '#process' do
+ let(:event) { Raven::Event.from_exception(exception, data) }
+ let(:result_hash) { described_class.new.process(event.to_hash) }
+
+ context 'with sentry_processors_before_send disabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: false)
+ end
+
+ it_behaves_like 'processing an exception'
+ end
+ end
end
diff --git a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
index da7205c7f4f..20fd5d085a9 100644
--- a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
@@ -94,28 +94,37 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
end
end
- describe '#process' do
+ shared_examples 'processing an exception' do
context 'when there is Sidekiq data' do
+ let(:wrapped_value) { { extra: { sidekiq: value } } }
+
shared_examples 'Sidekiq arguments' do |args_in_job_hash: true|
let(:path) { [:extra, :sidekiq, args_in_job_hash ? :job : nil, 'args'].compact }
let(:args) { [1, 'string', { a: 1 }, [1, 2]] }
- it 'only allows numeric arguments for an unknown worker' do
- value = { 'args' => args, 'class' => 'UnknownWorker' }
+ context 'for an unknown worker' do
+ let(:value) do
+ hash = { 'args' => args, 'class' => 'UnknownWorker' }
- value = { job: value } if args_in_job_hash
+ args_in_job_hash ? { job: hash } : hash
+ end
- expect(subject.process(extra_sidekiq(value)).dig(*path))
- .to eq([1, described_class::FILTERED_STRING, described_class::FILTERED_STRING, described_class::FILTERED_STRING])
+ it 'only allows numeric arguments for an unknown worker' do
+ expect(result_hash.dig(*path))
+ .to eq([1, described_class::FILTERED_STRING, described_class::FILTERED_STRING, described_class::FILTERED_STRING])
+ end
end
- it 'allows all argument types for a permitted worker' do
- value = { 'args' => args, 'class' => 'PostReceive' }
+ context 'for a permitted worker' do
+ let(:value) do
+ hash = { 'args' => args, 'class' => 'PostReceive' }
- value = { job: value } if args_in_job_hash
+ args_in_job_hash ? { job: hash } : hash
+ end
- expect(subject.process(extra_sidekiq(value)).dig(*path))
- .to eq(args)
+ it 'allows all argument types for a permitted worker' do
+ expect(result_hash.dig(*path)).to eq(args)
+ end
end
end
@@ -127,39 +136,62 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor do
include_examples 'Sidekiq arguments', args_in_job_hash: false
end
- it 'removes a jobstr field if present' do
- value = {
- job: { 'args' => [1] },
- jobstr: { 'args' => [1] }.to_json
- }
+ context 'when a jobstr field is present' do
+ let(:value) do
+ {
+ job: { 'args' => [1] },
+ jobstr: { 'args' => [1] }.to_json
+ }
+ end
- expect(subject.process(extra_sidekiq(value)))
- .to eq(extra_sidekiq(value.except(:jobstr)))
+ it 'removes the jobstr' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value.except(:jobstr))
+ end
end
- it 'does nothing with no jobstr' do
- value = { job: { 'args' => [1] } }
+ context 'when no jobstr value is present' do
+ let(:value) { { job: { 'args' => [1] } } }
- expect(subject.process(extra_sidekiq(value)))
- .to eq(extra_sidekiq(value))
+ it 'does nothing' do
+ expect(result_hash.dig(:extra, :sidekiq)).to eq(value)
+ end
end
end
context 'when there is no Sidekiq data' do
- it 'does nothing' do
- value = {
- request: {
- method: 'POST',
- data: { 'key' => 'value' }
- }
- }
+ let(:value) { { tags: { foo: 'bar', baz: 'quux' } } }
+ let(:wrapped_value) { value }
- expect(subject.process(value)).to eq(value)
+ it 'does nothing' do
+ expect(result_hash).to include(value)
+ expect(result_hash.dig(:extra, :sidekiq)).to be_nil
end
end
+ end
+
+ describe '.call' do
+ let(:event) { Raven::Event.new(wrapped_value) }
+ let(:result_hash) { described_class.call(event).to_hash }
+
+ it_behaves_like 'processing an exception'
+
+ context 'when followed by #process' do
+ let(:result_hash) { described_class.new.process(described_class.call(event).to_hash) }
+
+ it_behaves_like 'processing an exception'
+ end
+ end
+
+ describe '#process' do
+ let(:event) { Raven::Event.new(wrapped_value) }
+ let(:result_hash) { described_class.new.process(event.to_hash) }
+
+ context 'with sentry_processors_before_send disabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: false)
+ end
- def extra_sidekiq(hash)
- { extra: { sidekiq: hash } }
+ it_behaves_like 'processing an exception'
end
end
end
diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb
index a905b9f8d40..2e67a9f0874 100644
--- a/spec/lib/gitlab/error_tracking_spec.rb
+++ b/spec/lib/gitlab/error_tracking_spec.rb
@@ -7,6 +7,7 @@ require 'raven/transports/dummy'
RSpec.describe Gitlab::ErrorTracking do
let(:exception) { RuntimeError.new('boom') }
let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' }
+ let(:extra) { { issue_url: issue_url, some_other_info: 'info' } }
let(:user) { create(:user) }
@@ -42,6 +43,8 @@ RSpec.describe Gitlab::ErrorTracking do
}
end
+ let(:sentry_event) { Gitlab::Json.parse(Raven.client.transport.events.last[1]) }
+
before do
stub_sentry_settings
@@ -133,8 +136,6 @@ RSpec.describe Gitlab::ErrorTracking do
end
describe '.track_exception' do
- let(:extra) { { issue_url: issue_url, some_other_info: 'info' } }
-
subject(:track_exception) { described_class.track_exception(exception, extra) }
before do
@@ -195,6 +196,55 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
+ context 'when the error is kind of an `ActiveRecord::StatementInvalid`' do
+ let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }
+
+ it 'injects the normalized sql query into extra' do
+ track_exception
+
+ expect(sentry_event.dig('extra', 'sql')).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
+ end
+ end
+
+ context 'when the `ActiveRecord::StatementInvalid` is wrapped in another exception' do
+ it 'injects the normalized sql query into extra' do
+ allow(exception).to receive(:cause).and_return(ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1'))
+
+ track_exception
+
+ expect(sentry_event.dig('extra', 'sql')).to eq('SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
+ end
+ end
+ end
+
+ shared_examples 'event processors' do
+ subject(:track_exception) { described_class.track_exception(exception, extra) }
+
+ before do
+ allow(Raven).to receive(:capture_exception).and_call_original
+ allow(Gitlab::ErrorTracking::Logger).to receive(:error)
+ end
+
+ context 'custom GitLab context when using Raven.capture_exception directly' do
+ subject(:raven_capture_exception) { Raven.capture_exception(exception) }
+
+ it 'merges a default set of tags into the existing tags' do
+ allow(Raven.context).to receive(:tags).and_return(foo: 'bar')
+
+ raven_capture_exception
+
+ expect(sentry_event['tags']).to include('correlation_id', 'feature_category', 'foo', 'locale', 'program')
+ end
+
+ it 'merges the current user information into the existing user information' do
+ Raven.user_context(id: -1)
+
+ raven_capture_exception
+
+ expect(sentry_event['user']).to eq('id' => -1, 'username' => user.username)
+ end
+ end
+
context 'with sidekiq args' do
context 'when the args does not have anything sensitive' do
let(:extra) { { sidekiq: { 'class' => 'PostReceive', 'args' => [1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'] } } }
@@ -211,16 +261,20 @@ RSpec.describe Gitlab::ErrorTracking do
)
)
end
+
+ it 'does not filter parameters when sending to Sentry' do
+ track_exception
+
+ expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq([1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'])
+ end
end
context 'when the args has sensitive information' do
let(:extra) { { sidekiq: { 'class' => 'UnknownWorker', 'args' => ['sensitive string', 1, 2] } } }
- it 'filters sensitive arguments before sending' do
+ it 'filters sensitive arguments before sending and logging' do
track_exception
- sentry_event = Gitlab::Json.parse(Raven.client.transport.events.last[1])
-
expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2])
expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with(
hash_including(
@@ -234,28 +288,44 @@ RSpec.describe Gitlab::ErrorTracking do
end
end
- context 'when the error is kind of an `ActiveRecord::StatementInvalid`' do
- let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') }
+ context 'when the error is a GRPC error' do
+ context 'when the GRPC error contains a debug_error_string value' do
+ let(:exception) { GRPC::DeadlineExceeded.new('unknown cause', {}, '{"hello":1}') }
- it 'injects the normalized sql query into extra' do
- allow(Raven.client.transport).to receive(:send_event) do |event|
- expect(event.extra).to include(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
+ it 'sets the GRPC debug error string in the Sentry event and adds a custom fingerprint' do
+ track_exception
+
+ expect(sentry_event.dig('extra', 'grpc_debug_error_string')).to eq('{"hello":1}')
+ expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause.'])
end
+ end
- track_exception
+ context 'when the GRPC error does not contain a debug_error_string value' do
+ let(:exception) { GRPC::DeadlineExceeded.new }
+
+ it 'does not do any processing on the event' do
+ track_exception
+
+ expect(sentry_event['extra']).not_to include('grpc_debug_error_string')
+ expect(sentry_event['fingerprint']).to eq(['GRPC::DeadlineExceeded', '4:unknown cause'])
+ end
end
end
+ end
- context 'when the `ActiveRecord::StatementInvalid` is wrapped in another exception' do
- let(:exception) { RuntimeError.new(cause: ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1')) }
+ context 'with sentry_processors_before_send enabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: true)
+ end
- it 'injects the normalized sql query into extra' do
- allow(Raven.client.transport).to receive(:send_event) do |event|
- expect(event.extra).to include(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1')
- end
+ include_examples 'event processors'
+ end
- track_exception
- end
+ context 'with sentry_processors_before_send disabled' do
+ before do
+ stub_feature_flags(sentry_processors_before_send: false)
end
+
+ include_examples 'event processors'
end
end
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index 83c6b556fc6..5fef14bd2a0 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -7,7 +7,6 @@ require 'spec_helper'
RSpec.describe Gitlab::Experimentation::EXPERIMENTS do
it 'temporarily ensures we know what experiments exist for backwards compatibility' do
expected_experiment_keys = [
- :upgrade_link_in_user_menu_a,
:invite_members_version_b,
:invite_members_empty_group_version_a,
:contact_sales_btn_in_app
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 1a3c332a21b..114b3d01952 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -31,6 +31,19 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
end
end
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ shared_examples 'overflow stuff' do
+ it 'returns the expected overflow values' do
+ subject.overflow?
+ expect(subject.overflow_max_bytes?).to eq(overflow_max_bytes)
+ expect(subject.overflow_max_files?).to eq(overflow_max_files)
+ expect(subject.overflow_max_lines?).to eq(overflow_max_lines)
+ end
+ end
+
subject do
Gitlab::Git::DiffCollection.new(
iterator,
@@ -76,12 +89,19 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
end
context 'overflow handling' do
+ subject { super() }
+
+ let(:collapsed_safe_files) { false }
+ let(:collapsed_safe_lines) { false }
+
context 'adding few enough files' do
let(:file_count) { 3 }
context 'and few enough lines' do
let(:line_count) { 10 }
+ it_behaves_like 'overflow stuff'
+
describe '#overflow?' do
subject { super().overflow? }
@@ -117,6 +137,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -155,6 +180,9 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'and too many lines' do
let(:line_count) { 1000 }
+ let(:overflow_max_lines) { true }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -184,6 +212,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -216,10 +249,13 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'adding too many files' do
let(:file_count) { 11 }
+ let(:overflow_max_files) { true }
context 'and few enough lines' do
let(:line_count) { 1 }
+ it_behaves_like 'overflow stuff'
+
describe '#overflow?' do
subject { super().overflow? }
@@ -248,6 +284,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -279,6 +320,10 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'and too many lines' do
let(:line_count) { 30 }
+ let(:overflow_max_lines) { true }
+ let(:overflow_max_files) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -308,6 +353,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -344,6 +394,8 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'and few enough lines' do
let(:line_count) { 1 }
+ it_behaves_like 'overflow stuff'
+
describe '#overflow?' do
subject { super().overflow? }
@@ -375,6 +427,9 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'adding too many bytes' do
let(:file_count) { 10 }
let(:line_length) { 5200 }
+ let(:overflow_max_bytes) { true }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -404,6 +459,11 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
context 'when limiting is disabled' do
let(:limits) { false }
+ let(:overflow_max_bytes) { false }
+ let(:overflow_max_files) { false }
+ let(:overflow_max_lines) { false }
+
+ it_behaves_like 'overflow stuff'
describe '#overflow?' do
subject { super().overflow? }
@@ -437,6 +497,8 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
describe 'empty collection' do
subject { Gitlab::Git::DiffCollection.new([]) }
+ it_behaves_like 'overflow stuff'
+
describe '#overflow?' do
subject { super().overflow? }
@@ -555,7 +617,7 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
.and_return({ max_files: 2, max_lines: max_lines })
end
- it 'prunes diffs by default even little ones' do
+ it 'prunes diffs by default even little ones and sets collapsed_safe_files true' do
subject.each_with_index do |d, i|
if i < 2
expect(d.diff).not_to eq('')
@@ -563,6 +625,8 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
expect(d.diff).to eq('')
end
end
+
+ expect(subject.collapsed_safe_files?).to eq(true)
end
end
@@ -582,7 +646,7 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
.and_return({ max_files: max_files, max_lines: 80 })
end
- it 'prunes diffs by default even little ones' do
+ it 'prunes diffs by default even little ones and sets collapsed_safe_lines true' do
subject.each_with_index do |d, i|
if i < 2
expect(d.diff).not_to eq('')
@@ -590,26 +654,30 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
expect(d.diff).to eq('')
end
end
+
+ expect(subject.collapsed_safe_lines?).to eq(true)
end
end
context 'when go over safe limits on bytes' do
let(:iterator) do
[
- fake_diff(1, 45),
- fake_diff(1, 45),
- fake_diff(1, 20480),
- fake_diff(1, 1)
+ fake_diff(5, 10),
+ fake_diff(5000, 10),
+ fake_diff(5, 10),
+ fake_diff(5, 10)
]
end
before do
+ allow(Gitlab::CurrentSettings).to receive(:diff_max_patch_bytes).and_return(1.megabyte)
+
allow(Gitlab::Git::DiffCollection)
.to receive(:default_limits)
- .and_return({ max_files: max_files, max_lines: 80 })
+ .and_return({ max_files: 4, max_lines: 3000 })
end
- it 'prunes diffs by default even little ones' do
+ it 'prunes diffs by default even little ones and sets collapsed_safe_bytes true' do
subject.each_with_index do |d, i|
if i < 2
expect(d.diff).not_to eq('')
@@ -617,6 +685,8 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
expect(d.diff).to eq('')
end
end
+
+ expect(subject.collapsed_safe_bytes?).to eq(true)
end
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index cc1b1ceadcf..1e259c9c153 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -564,6 +564,41 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#search_files_by_regexp' do
+ let(:ref) { 'master' }
+
+ subject(:result) { mutable_repository.search_files_by_regexp(filter, ref) }
+
+ context 'when sending a valid regexp' do
+ let(:filter) { 'files\/.*\/.*\.rb' }
+
+ it 'returns matched files' do
+ expect(result).to contain_exactly('files/links/regex.rb',
+ 'files/ruby/popen.rb',
+ 'files/ruby/regex.rb',
+ 'files/ruby/version_info.rb')
+ end
+ end
+
+ context 'when sending an ivalid regexp' do
+ let(:filter) { '*.rb' }
+
+ it 'raises error' do
+ expect { result }.to raise_error(GRPC::InvalidArgument,
+ /missing argument to repetition operator: `*`/)
+ end
+ end
+
+ context "when the ref doesn't exist" do
+ let(:filter) { 'files\/.*\/.*\.rb' }
+ let(:ref) { 'non-existing-branch' }
+
+ it 'returns an empty array' do
+ expect(result).to eq([])
+ end
+ end
+ end
+
describe '#find_remote_root_ref' do
it 'gets the remote root ref from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
@@ -1711,14 +1746,15 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
let(:right_branch) { 'test-master' }
let(:first_parent_ref) { 'refs/heads/test-master' }
let(:target_ref) { 'refs/merge-requests/999/merge' }
- let(:allow_conflicts) { false }
before do
repository.create_branch(right_branch, branch_head) unless repository.ref_exists?(first_parent_ref)
end
def merge_to_ref
- repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message', first_parent_ref, allow_conflicts)
+ repository.merge_to_ref(user,
+ source_sha: left_sha, branch: right_branch, target_ref: target_ref,
+ message: 'Merge message', first_parent_ref: first_parent_ref)
end
it 'generates a commit in the target_ref' do
diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb
index f83ccc6cae0..b6ff76c5e1c 100644
--- a/spec/lib/gitlab/git/tag_spec.rb
+++ b/spec/lib/gitlab/git/tag_spec.rb
@@ -101,4 +101,17 @@ RSpec.describe Gitlab::Git::Tag, :seed_helper do
end
end
end
+
+ describe "#cache_key" do
+ subject { repository.tags.first }
+
+ it "returns a cache key that changes based on changeable values" do
+ expect(subject).to receive(:name).and_return("v1.0.0")
+ expect(subject).to receive(:message).and_return("Initial release")
+
+ digest = Digest::SHA1.hexdigest(["v1.0.0", "Initial release", subject.target, subject.target_commit.sha].join)
+
+ expect(subject.cache_key).to eq("tag:#{digest}")
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
index 037734f1b13..f0ec58f3c2d 100644
--- a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
@@ -14,14 +14,14 @@ RSpec.describe Gitlab::GitalyClient::BlobService do
let(:limit) { 5 }
let(:not_in) { %w[branch-a branch-b] }
let(:expected_params) do
- { revision: revision, limit: limit, not_in_refs: not_in, not_in_all: false }
+ { revisions: ["master", "--not", "branch-a", "branch-b"], limit: limit }
end
subject { client.get_new_lfs_pointers(revision, limit, not_in) }
it 'sends a get_new_lfs_pointers message' do
expect_any_instance_of(Gitaly::BlobService::Stub)
- .to receive(:get_new_lfs_pointers)
+ .to receive(:list_lfs_pointers)
.with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([])
@@ -31,12 +31,39 @@ RSpec.describe Gitlab::GitalyClient::BlobService do
context 'with not_in = :all' do
let(:not_in) { :all }
let(:expected_params) do
- { revision: revision, limit: limit, not_in_refs: [], not_in_all: true }
+ { revisions: ["master", "--not", "--all"], limit: limit }
end
it 'sends the correct message' do
expect_any_instance_of(Gitaly::BlobService::Stub)
- .to receive(:get_new_lfs_pointers)
+ .to receive(:list_lfs_pointers)
+ .with(gitaly_request_with_params(expected_params), kind_of(Hash))
+ .and_return([])
+
+ subject
+ end
+ end
+
+ context 'with hook environment' do
+ let(:git_env) do
+ {
+ 'GIT_OBJECT_DIRECTORY_RELATIVE' => '.git/objects',
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => ['/dir/one', '/dir/two']
+ }
+ end
+
+ let(:expected_params) do
+ expected_repository = repository.gitaly_repository
+ expected_repository.git_alternate_object_directories = Google::Protobuf::RepeatedField.new(:string)
+
+ { limit: limit, repository: expected_repository }
+ end
+
+ it 'sends a list_all_lfs_pointers message' do
+ allow(Gitlab::Git::HookEnv).to receive(:all).with(repository.gl_repository).and_return(git_env)
+
+ expect_any_instance_of(Gitaly::BlobService::Stub)
+ .to receive(:list_all_lfs_pointers)
.with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([])
@@ -46,12 +73,16 @@ RSpec.describe Gitlab::GitalyClient::BlobService do
end
describe '#get_all_lfs_pointers' do
+ let(:expected_params) do
+ { revisions: ["--all"], limit: 0 }
+ end
+
subject { client.get_all_lfs_pointers }
it 'sends a get_all_lfs_pointers message' do
expect_any_instance_of(Gitaly::BlobService::Stub)
- .to receive(:get_all_lfs_pointers)
- .with(gitaly_request_with_params({}), kind_of(Hash))
+ .to receive(:list_lfs_pointers)
+ .with(gitaly_request_with_params(expected_params), kind_of(Hash))
.and_return([])
subject
diff --git a/spec/lib/gitlab/gitaly_client/call_spec.rb b/spec/lib/gitlab/gitaly_client/call_spec.rb
index 5c33ac40460..099307fc4e1 100644
--- a/spec/lib/gitlab/gitaly_client/call_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/call_spec.rb
@@ -24,11 +24,14 @@ RSpec.describe Gitlab::GitalyClient::Call do
def expect_call_details_to_match(duration_higher_than: 0)
expect(client.list_call_details.size).to eq(1)
expect(client.list_call_details.first)
- .to match a_hash_including(feature: "#{service}##{rpc}",
- duration: a_value > duration_higher_than,
- request: an_instance_of(Hash),
- rpc: rpc,
- backtrace: an_instance_of(Array))
+ .to match a_hash_including(
+ start: a_value > 0,
+ feature: "#{service}##{rpc}",
+ duration: a_value > duration_higher_than,
+ request: an_instance_of(Hash),
+ rpc: rpc,
+ backtrace: an_instance_of(Array)
+ )
end
context 'when the response is not an enumerator' do
diff --git a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
index 15eebf62a39..9c3bc935acc 100644
--- a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::GitalyClient::ObjectPoolService do
subject { described_class.new(object_pool) }
before do
- subject.create(raw_repository)
+ subject.create(raw_repository) # rubocop:disable Rails/SaveBang
end
describe '#create' do
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::GitalyClient::ObjectPoolService do
context 'when the pool already exists' do
it 'returns an error' do
expect do
- subject.create(raw_repository)
+ subject.create(raw_repository) # rubocop:disable Rails/SaveBang
end.to raise_error(GRPC::FailedPrecondition)
end
end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 22707c9a36b..9a17140a1e0 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -88,17 +88,29 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
let(:ref) { 'refs/merge-requests/x/merge' }
let(:message) { 'validación' }
- let(:allow_conflicts) { false }
let(:response) { Gitaly::UserMergeToRefResponse.new(commit_id: 'new-commit-id') }
- subject { client.user_merge_to_ref(user, source_sha, nil, ref, message, first_parent_ref, allow_conflicts) }
+ let(:payload) do
+ { source_sha: source_sha, branch: 'branch', target_ref: ref,
+ message: message, first_parent_ref: first_parent_ref, allow_conflicts: true }
+ end
it 'sends a user_merge_to_ref message' do
- expect_any_instance_of(Gitaly::OperationService::Stub)
- .to receive(:user_merge_to_ref).with(kind_of(Gitaly::UserMergeToRefRequest), kind_of(Hash))
- .and_return(response)
-
- subject
+ freeze_time do
+ expect_any_instance_of(Gitaly::OperationService::Stub).to receive(:user_merge_to_ref) do |_, request, options|
+ expect(options).to be_kind_of(Hash)
+ expect(request.to_h).to eq(
+ payload.merge({
+ repository: repository.gitaly_repository.to_h,
+ message: message.dup.force_encoding(Encoding::ASCII_8BIT),
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly.to_h,
+ timestamp: { nanos: 0, seconds: Time.current.to_i }
+ })
+ )
+ end.and_return(response)
+
+ client.user_merge_to_ref(user, **payload)
+ end
end
end
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 7a382df1248..26ec194a2e7 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -246,6 +246,21 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
end
end
+ describe '#search_files_by_regexp' do
+ subject(:result) { client.search_files_by_regexp('master', '.*') }
+
+ before do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:search_files_by_name)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return([double(files: ['file1.txt']), double(files: ['file2.txt'])])
+ end
+
+ it 'sends a search_files_by_name message and returns a flatten array' do
+ expect(result).to contain_exactly('file1.txt', 'file2.txt')
+ end
+ end
+
describe '#disconnect_alternates' do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
@@ -255,7 +270,7 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
let(:object_pool_service) { Gitlab::GitalyClient::ObjectPoolService.new(object_pool) }
before do
- object_pool_service.create(repository)
+ object_pool_service.create(repository) # rubocop:disable Rails/SaveBang
object_pool_service.link_repository(repository)
end
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb
index e42b6d89c30..01d9edf0ba1 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :clean_gitlab_redis_cache do
let_it_be(:merge_request) { create(:merged_merge_request) }
+
let(:project) { merge_request.project }
let(:merged_at) { Time.new(2017, 1, 1, 12, 00).utc }
let(:client_double) { double(user: double(id: 999, login: 'merger', email: 'merger@email.com')) }
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb
index 290f3f51202..5002e0384f3 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean
using RSpec::Parameterized::TableSyntax
let_it_be(:merge_request) { create(:merge_request) }
+
let(:project) { merge_request.project }
let(:client_double) { double(user: double(id: 999, login: 'author', email: 'author@email.com')) }
let(:submitted_at) { Time.new(2017, 1, 1, 12, 00).utc }
diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
index 5da45b1897f..fe8652eb5a2 100644
--- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let_it_be(:milestone) { create(:milestone, project: project) }
+
let(:finder) { described_class.new(project) }
describe '#id_for' do
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
deleted file mode 100644
index c88506899cd..00000000000
--- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
+++ /dev/null
@@ -1,253 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-# Also see spec/graphql/features/authorization_spec.rb for
-# integration tests of AuthorizeFieldService
-RSpec.describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
- def type(type_authorizations = [])
- Class.new(Types::BaseObject) do
- graphql_name 'TestType'
-
- authorize type_authorizations
- end
- end
-
- def type_with_field(field_type, field_authorizations = [], resolved_value = 'Resolved value', **options)
- Class.new(Types::BaseObject) do
- graphql_name 'TestTypeWithField'
- options.reverse_merge!(null: true)
- field :test_field, field_type,
- authorize: field_authorizations,
- **options
-
- define_method :test_field do
- resolved_value
- end
- end
- end
-
- def resolve
- service.authorized_resolve[type_instance, {}, context]
- end
-
- subject(:service) { described_class.new(field) }
-
- describe '#authorized_resolve' do
- let_it_be(:current_user) { build(:user) }
- let_it_be(:presented_object) { 'presented object' }
- let_it_be(:query_type) { GraphQL::ObjectType.new }
- let_it_be(:schema) { GitlabSchema }
- let_it_be(:query) { GraphQL::Query.new(schema, document: nil, context: {}, variables: {}) }
- let_it_be(:context) { GraphQL::Query::Context.new(query: query, values: { current_user: current_user }, object: nil) }
-
- let(:type_class) { type_with_field(custom_type, :read_field, presented_object) }
- let(:type_instance) { type_class.authorized_new(presented_object, context) }
- let(:field) { type_class.fields['testField'].to_graphql }
-
- subject(:resolved) { ::Gitlab::Graphql::Lazy.force(resolve) }
-
- context 'reading the field of a lazy value' do
- let(:ability) { :read_field }
- let(:presented_object) { lazy_upcase('a') }
- let(:type_class) { type_with_field(GraphQL::STRING_TYPE, ability) }
-
- let(:upcaser) do
- Module.new do
- def self.upcase(strs)
- strs.map(&:upcase)
- end
- end
- end
-
- def lazy_upcase(str)
- ::BatchLoader::GraphQL.for(str).batch do |strs, found|
- strs.zip(upcaser.upcase(strs)).each { |s, us| found[s, us] }
- end
- end
-
- it 'does not run authorizations until we force the resolved value' do
- expect(Ability).not_to receive(:allowed?)
-
- expect(resolve).to respond_to(:force)
- end
-
- it 'runs authorizations when we force the resolved value' do
- spy_ability_check_for(ability, 'A')
-
- expect(resolved).to eq('Resolved value')
- end
-
- it 'redacts values that fail the permissions check' do
- spy_ability_check_for(ability, 'A', passed: false)
-
- expect(resolved).to be_nil
- end
-
- context 'we batch two calls' do
- def resolve(value)
- instance = type_class.authorized_new(lazy_upcase(value), context)
- service.authorized_resolve[instance, {}, context]
- end
-
- it 'batches resolution, but authorizes each object separately' do
- expect(upcaser).to receive(:upcase).once.and_call_original
- spy_ability_check_for(:read_field, 'A', passed: true)
- spy_ability_check_for(:read_field, 'B', passed: false)
- spy_ability_check_for(:read_field, 'C', passed: true)
-
- a = resolve('a')
- b = resolve('b')
- c = resolve('c')
-
- expect(a.force).to be_present
- expect(b.force).to be_nil
- expect(c.force).to be_present
- end
- end
- end
-
- shared_examples 'authorizing fields' do
- context 'scalar types' do
- shared_examples 'checking permissions on the presented object' do
- it 'checks the abilities on the object being presented and returns the value' do
- expected_permissions.each do |permission|
- spy_ability_check_for(permission, presented_object, passed: true)
- end
-
- expect(resolved).to eq('Resolved value')
- end
-
- it 'returns nil if the value was not authorized' do
- allow(Ability).to receive(:allowed?).and_return false
-
- expect(resolved).to be_nil
- end
- end
-
- context 'when the field is a built-in scalar type' do
- let(:type_class) { type_with_field(GraphQL::STRING_TYPE, :read_field) }
- let(:expected_permissions) { [:read_field] }
-
- it_behaves_like 'checking permissions on the presented object'
- end
-
- context 'when the field is a list of scalar types' do
- let(:type_class) { type_with_field([GraphQL::STRING_TYPE], :read_field) }
- let(:expected_permissions) { [:read_field] }
-
- it_behaves_like 'checking permissions on the presented object'
- end
-
- context 'when the field is sub-classed scalar type' do
- let(:type_class) { type_with_field(Types::TimeType, :read_field) }
- let(:expected_permissions) { [:read_field] }
-
- it_behaves_like 'checking permissions on the presented object'
- end
-
- context 'when the field is a list of sub-classed scalar types' do
- let(:type_class) { type_with_field([Types::TimeType], :read_field) }
- let(:expected_permissions) { [:read_field] }
-
- it_behaves_like 'checking permissions on the presented object'
- end
- end
-
- context 'when the field is a connection' do
- context 'when it resolves to nil' do
- let(:type_class) { type_with_field(Types::QueryType.connection_type, :read_field, nil) }
-
- it 'does not fail when authorizing' do
- expect(resolved).to be_nil
- end
- end
-
- context 'when it returns values' do
- let(:objects) { [1, 2, 3] }
- let(:field_type) { type([:read_object]).connection_type }
- let(:type_class) { type_with_field(field_type, [], objects) }
-
- it 'filters out unauthorized values' do
- spy_ability_check_for(:read_object, 1, passed: true)
- spy_ability_check_for(:read_object, 2, passed: false)
- spy_ability_check_for(:read_object, 3, passed: true)
-
- expect(resolved.nodes).to eq [1, 3]
- end
- end
- end
-
- context 'when the field is a specific type' do
- let(:custom_type) { type(:read_type) }
- let(:object_in_field) { double('presented in field') }
-
- let(:type_class) { type_with_field(custom_type, :read_field, object_in_field) }
- let(:type_instance) { type_class.authorized_new(object_in_field, context) }
-
- it 'checks both field & type permissions' do
- spy_ability_check_for(:read_field, object_in_field, passed: true)
- spy_ability_check_for(:read_type, object_in_field, passed: true)
-
- expect(resolved).to eq(object_in_field)
- end
-
- it 'returns nil if viewing was not allowed' do
- spy_ability_check_for(:read_field, object_in_field, passed: false)
- spy_ability_check_for(:read_type, object_in_field, passed: true)
-
- expect(resolved).to be_nil
- end
-
- context 'when the field is not nullable' do
- let(:type_class) { type_with_field(custom_type, :read_field, object_in_field, null: false) }
-
- it 'returns nil when viewing is not allowed' do
- spy_ability_check_for(:read_type, object_in_field, passed: false)
-
- expect(resolved).to be_nil
- end
- end
-
- context 'when the field is a list' do
- let(:object_1) { double('presented in field 1') }
- let(:object_2) { double('presented in field 2') }
- let(:presented_types) { [double(object: object_1), double(object: object_2)] }
-
- let(:type_class) { type_with_field([custom_type], :read_field, presented_types) }
- let(:type_instance) { type_class.authorized_new(presented_types, context) }
-
- it 'checks all permissions' do
- allow(Ability).to receive(:allowed?) { true }
-
- spy_ability_check_for(:read_field, object_1, passed: true)
- spy_ability_check_for(:read_type, object_1, passed: true)
- spy_ability_check_for(:read_field, object_2, passed: true)
- spy_ability_check_for(:read_type, object_2, passed: true)
-
- expect(resolved).to eq(presented_types)
- end
-
- it 'filters out objects that the user cannot see' do
- allow(Ability).to receive(:allowed?) { true }
-
- spy_ability_check_for(:read_type, object_1, passed: false)
-
- expect(resolved).to contain_exactly(have_attributes(object: object_2))
- end
- end
- end
- end
-
- it_behaves_like 'authorizing fields'
- end
-
- private
-
- def spy_ability_check_for(ability, object, passed: true)
- expect(Ability)
- .to receive(:allowed?)
- .with(current_user, ability, object)
- .and_return(passed)
- end
-end
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
index c5d7665c3b2..0c548e1ce32 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
@@ -12,7 +12,8 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
authorize :read_the_thing
def initialize(user, found_object)
- @user, @found_object = user, found_object
+ @user = user
+ @found_object = found_object
end
def find_object
@@ -22,6 +23,14 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
def current_user
user
end
+
+ def context
+ { current_user: user }
+ end
+
+ def self.authorization
+ @authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(required_permissions)
+ end
end
end
@@ -30,11 +39,14 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
subject(:loading_resource) { fake_class.new(user, project) }
+ before do
+ # don't allow anything by default
+ allow(Ability).to receive(:allowed?).and_return(false)
+ end
+
context 'when the user is allowed to perform the action' do
before do
- allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project, scope: :user) do
- true
- end
+ allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project).and_return(true)
end
describe '#authorized_find!' do
@@ -48,24 +60,12 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
expect { loading_resource.authorize!(project) }.not_to raise_error
end
end
-
- describe '#authorized_resource?' do
- it 'is true' do
- expect(loading_resource.authorized_resource?(project)).to be(true)
- end
- end
end
context 'when the user is not allowed to perform the action' do
- before do
- allow(Ability).to receive(:allowed?).with(user, :read_the_thing, project, scope: :user) do
- false
- end
- end
-
describe '#authorized_find!' do
it 'raises an error' do
- expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ expect { loading_resource.authorized_find! }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
@@ -74,12 +74,6 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
-
- describe '#authorized_resource?' do
- it 'is false' do
- expect(loading_resource.authorized_resource?(project)).to be(false)
- end
- end
end
context 'when the class does not define #find_object' do
@@ -92,46 +86,6 @@ RSpec.describe Gitlab::Graphql::Authorize::AuthorizeResource do
end
end
- context 'when the class does not define authorize' do
- let(:fake_class) do
- Class.new do
- include Gitlab::Graphql::Authorize::AuthorizeResource
-
- attr_reader :user, :found_object
-
- def initialize(user, found_object)
- @user, @found_object = user, found_object
- end
-
- def find_object(*_args)
- found_object
- end
-
- def current_user
- user
- end
-
- def self.name
- 'TestClass'
- end
- end
- end
-
- let(:error) { /#{fake_class.name} has no authorizations/ }
-
- describe '#authorized_find!' do
- it 'raises a comprehensive error message' do
- expect { loading_resource.authorized_find! }.to raise_error(error)
- end
- end
-
- describe '#authorized_resource?' do
- it 'raises a comprehensive error message' do
- expect { loading_resource.authorized_resource?(project) }.to raise_error(error)
- end
- end
- end
-
describe '#authorize' do
it 'adds permissions from subclasses to those of superclasses when used on classes' do
base_class = Class.new do
diff --git a/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb b/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb
new file mode 100644
index 00000000000..73e25f23848
--- /dev/null
+++ b/spec/lib/gitlab/graphql/authorize/object_authorization_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe ::Gitlab::Graphql::Authorize::ObjectAuthorization do
+ describe '#ok?' do
+ subject { described_class.new(%i[go_fast go_slow]) }
+
+ let(:user) { double(:User, id: 10001) }
+
+ let(:policy) do
+ Class.new(::DeclarativePolicy::Base) do
+ condition(:fast, scope: :subject) { @subject.x >= 10 }
+ condition(:slow, scope: :subject) { @subject.y >= 10 }
+
+ rule { fast }.policy do
+ enable :go_fast
+ end
+
+ rule { slow }.policy do
+ enable :go_slow
+ end
+ end
+ end
+
+ before do
+ stub_const('Foo', Struct.new(:x, :y))
+ stub_const('FooPolicy', policy)
+ end
+
+ context 'when there are no abilities' do
+ subject { described_class.new([]) }
+
+ it { is_expected.to be_ok(double, double) }
+ end
+
+ context 'when no ability should be allowed' do
+ let(:object) { Foo.new(0, 0) }
+
+ it { is_expected.not_to be_ok(object, user) }
+ end
+
+ context 'when go_fast should be allowed' do
+ let(:object) { Foo.new(100, 0) }
+
+ it { is_expected.not_to be_ok(object, user) }
+ end
+
+ context 'when go_fast and go_slow should be allowed' do
+ let(:object) { Foo.new(100, 100) }
+
+ it { is_expected.to be_ok(object, user) }
+ end
+
+ context 'when the object delegates to another subject' do
+ def proxy(foo)
+ double(:Proxy, declarative_policy_subject: foo)
+ end
+
+ it { is_expected.to be_ok(proxy(Foo.new(100, 100)), user) }
+ it { is_expected.not_to be_ok(proxy(Foo.new(0, 100)), user) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/batch_key_spec.rb b/spec/lib/gitlab/graphql/batch_key_spec.rb
index 881fba5c1be..7b73b27f24b 100644
--- a/spec/lib/gitlab/graphql/batch_key_spec.rb
+++ b/spec/lib/gitlab/graphql/batch_key_spec.rb
@@ -6,6 +6,7 @@ require 'test_prof/recipes/rspec/let_it_be'
RSpec.describe ::Gitlab::Graphql::BatchKey do
let_it_be(:rect) { Struct.new(:len, :width) }
let_it_be(:circle) { Struct.new(:radius) }
+
let(:lookahead) { nil }
let(:object) { rect.new(2, 3) }
diff --git a/spec/lib/gitlab/graphql/deprecation_spec.rb b/spec/lib/gitlab/graphql/deprecation_spec.rb
new file mode 100644
index 00000000000..8b41145b855
--- /dev/null
+++ b/spec/lib/gitlab/graphql/deprecation_spec.rb
@@ -0,0 +1,213 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'active_model'
+
+RSpec.describe ::Gitlab::Graphql::Deprecation do
+ let(:options) { {} }
+
+ subject(:deprecation) { described_class.parse(options) }
+
+ describe '.parse' do
+ context 'with nil' do
+ let(:options) { nil }
+
+ it 'parses to nil' do
+ expect(deprecation).to be_nil
+ end
+ end
+
+ context 'with empty options' do
+ let(:options) { {} }
+
+ it 'parses to an empty deprecation' do
+ expect(deprecation).to eq(described_class.new)
+ end
+ end
+
+ context 'with defined options' do
+ let(:options) { { reason: :renamed, milestone: '10.10' } }
+
+ it 'assigns the properties' do
+ expect(deprecation).to eq(described_class.new(reason: 'This was renamed', milestone: '10.10'))
+ end
+ end
+ end
+
+ describe 'validations' do
+ let(:options) { { reason: :renamed, milestone: '10.10' } }
+
+ it { is_expected.to be_valid }
+
+ context 'when the milestone is absent' do
+ before do
+ options.delete(:milestone)
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the milestone is not milestone-ish' do
+ before do
+ options[:milestone] = 'next year'
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the milestone is not a string' do
+ before do
+ options[:milestone] = 10.01
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the reason is absent' do
+ before do
+ options.delete(:reason)
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the reason is not a known reason' do
+ before do
+ options[:reason] = :not_stylish_enough
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when the reason is a string' do
+ before do
+ options[:reason] = 'not stylish enough'
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when the reason is a string ending with a period' do
+ before do
+ options[:reason] = 'not stylish enough.'
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ describe '#deprecation_reason' do
+ context 'when there is a replacement' do
+ let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } }
+
+ it 'renders as reason-replacement-milestone' do
+ expect(deprecation.deprecation_reason).to eq('This was renamed. Please use `X.y`. Deprecated in 10.10.')
+ end
+ end
+
+ context 'when there is no replacement' do
+ let(:options) { { reason: :renamed, milestone: '10.10' } }
+
+ it 'renders as reason-milestone' do
+ expect(deprecation.deprecation_reason).to eq('This was renamed. Deprecated in 10.10.')
+ end
+ end
+
+ describe 'processing of reason' do
+ described_class::REASONS.each_key do |known_reason|
+ context "when the reason is a known reason such as #{known_reason.inspect}" do
+ let(:options) { { reason: known_reason } }
+
+ it 'renders the reason_text correctly' do
+ expect(deprecation.deprecation_reason).to start_with(described_class::REASONS[known_reason])
+ end
+ end
+ end
+
+ context 'when the reason is any other string' do
+ let(:options) { { reason: 'unhelpful' } }
+
+ it 'appends a period' do
+ expect(deprecation.deprecation_reason).to start_with('unhelpful.')
+ end
+ end
+ end
+ end
+
+ describe '#edit_description' do
+ let(:options) { { reason: :renamed, milestone: '10.10' } }
+
+ it 'appends milestone:reason with a leading space if there is a description' do
+ desc = deprecation.edit_description('Some description.')
+
+ expect(desc).to eq('Some description. Deprecated in 10.10: This was renamed.')
+ end
+
+ it 'returns nil if there is no description' do
+ desc = deprecation.edit_description(nil)
+
+ expect(desc).to be_nil
+ end
+ end
+
+ describe '#original_description' do
+ it 'records the description passed to it' do
+ deprecation.edit_description('Some description.')
+
+ expect(deprecation.original_description).to eq('Some description.')
+ end
+ end
+
+ describe '#markdown' do
+ context 'when there is a replacement' do
+ let(:options) { { reason: :renamed, milestone: '10.10', replacement: 'X.y' } }
+
+ context 'when the context is :inline' do
+ it 'renders on one line' do
+ expectation = '**Deprecated** in 10.10. This was renamed. Use: `X.y`.'
+
+ expect(deprecation.markdown).to eq(expectation)
+ expect(deprecation.markdown(context: :inline)).to eq(expectation)
+ end
+ end
+
+ context 'when the context is :block' do
+ it 'renders a warning note' do
+ expectation = <<~MD.chomp
+ WARNING:
+ **Deprecated** in 10.10.
+ This was renamed.
+ Use: `X.y`.
+ MD
+
+ expect(deprecation.markdown(context: :block)).to eq(expectation)
+ end
+ end
+ end
+
+ context 'when there is no replacement' do
+ let(:options) { { reason: 'Removed', milestone: '10.10' } }
+
+ context 'when the context is :inline' do
+ it 'renders on one line' do
+ expectation = '**Deprecated** in 10.10. Removed.'
+
+ expect(deprecation.markdown).to eq(expectation)
+ expect(deprecation.markdown(context: :inline)).to eq(expectation)
+ end
+ end
+
+ context 'when the context is :block' do
+ it 'renders a warning note' do
+ expectation = <<~MD.chomp
+ WARNING:
+ **Deprecated** in 10.10.
+ Removed.
+ MD
+
+ expect(deprecation.markdown(context: :block)).to eq(expectation)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/docs/renderer_spec.rb b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
index 5afed8c3390..8c0f7aac081 100644
--- a/spec/lib/gitlab/graphql/docs/renderer_spec.rb
+++ b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
@@ -1,32 +1,35 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
RSpec.describe Gitlab::Graphql::Docs::Renderer do
describe '#contents' do
- # Returns a Schema that uses the given `type`
- def mock_schema(type, field_description)
- query_type = Class.new(Types::BaseObject) do
- graphql_name 'Query'
+ let(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
- field :foo, type, null: true do
- description field_description
+ let(:query_type) do
+ Class.new(Types::BaseObject) { graphql_name 'Query' }.tap do |t|
+ # this keeps type and field_description in scope.
+ t.field :foo, type, null: true, description: field_description do
argument :id, GraphQL::ID_TYPE, required: false, description: 'ID of the object.'
end
end
+ end
- GraphQL::Schema.define(
- query: query_type,
- resolve_type: ->(obj, ctx) { raise 'Not a real schema' }
- )
+ let(:mock_schema) do
+ Class.new(GraphQL::Schema) do
+ def resolve_type(obj, ctx)
+ raise 'Not a real schema'
+ end
+ end
end
- let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/default.md.haml') }
let(:field_description) { 'List of objects.' }
subject(:contents) do
+ mock_schema.query(query_type)
+
described_class.new(
- mock_schema(type, field_description).graphql_definition,
+ mock_schema,
output_dir: nil,
template: template
).contents
@@ -136,6 +139,22 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
null: false,
deprecated: { reason: 'This is deprecated', milestone: '1.10' },
description: 'A description.'
+ field :foo_with_args,
+ type: GraphQL::STRING_TYPE,
+ null: false,
+ deprecated: { reason: 'Do not use', milestone: '1.10' },
+ description: 'A description.' do
+ argument :fooity, ::GraphQL::INT_TYPE, required: false, description: 'X'
+ end
+ field :bar,
+ type: GraphQL::STRING_TYPE,
+ null: false,
+ description: 'A description.',
+ deprecated: {
+ reason: :renamed,
+ milestone: '1.10',
+ replacement: 'Query.boom'
+ }
end
end
@@ -145,7 +164,40 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
| Field | Type | Description |
| ----- | ---- | ----------- |
- | `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated:** This is deprecated. Deprecated in 1.10. |
+ | `bar` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This was renamed. Use: `Query.boom`. |
+ | `foo` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. This is deprecated. |
+ | `fooWithArgs` **{warning-solid}** | [`String!`](#string) | **Deprecated** in 1.10. Do not use. |
+ DOC
+
+ is_expected.to include(expectation)
+ end
+ end
+
+ context 'when a Query.field is deprecated' do
+ let(:type) { ::GraphQL::INT_TYPE }
+
+ before do
+ query_type.field(
+ name: :bar,
+ type: type,
+ null: true,
+ description: 'A bar',
+ deprecated: { reason: :renamed, milestone: '10.11', replacement: 'Query.foo' }
+ )
+ end
+
+ it 'includes the deprecation' do
+ expectation = <<~DOC
+ ### `bar`
+
+ A bar.
+
+ WARNING:
+ **Deprecated** in 10.11.
+ This was renamed.
+ Use: `Query.foo`.
+
+ Returns [`Int`](#int).
DOC
is_expected.to include(expectation)
diff --git a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
index ae5d9686c54..35750a87fb5 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
+
let(:repository) { project.repository }
let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'files/lfs/lfs_object.iso'), repository) }
let(:otherblob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'README'), repository) }
diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb
index 0e36ea14ac3..44ca23f547c 100644
--- a/spec/lib/gitlab/graphql/markdown_field_spec.rb
+++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb
@@ -57,6 +57,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do
describe 'basic verification that references work' do
let_it_be(:project) { create(:project, :public) }
+
let(:issue) { create(:issue, project: project) }
let(:note) { build(:note, note: "Referencing #{issue.to_reference(full: true)}") }
diff --git a/spec/lib/gitlab/graphql/negatable_arguments_spec.rb b/spec/lib/gitlab/graphql/negatable_arguments_spec.rb
new file mode 100644
index 00000000000..bc6e25eb018
--- /dev/null
+++ b/spec/lib/gitlab/graphql/negatable_arguments_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Graphql::NegatableArguments do
+ let(:test_resolver) do
+ Class.new(Resolvers::BaseResolver).tap do |klass|
+ klass.extend described_class
+ allow(klass).to receive(:name).and_return('Resolvers::TestResolver')
+ end
+ end
+
+ describe '#negated' do
+ it 'defines :not argument' do
+ test_resolver.negated {}
+
+ expect(test_resolver.arguments['not'].type.name).to eq "Types::TestResolverNegatedParamsType"
+ end
+
+ it 'defines any arguments passed as block' do
+ test_resolver.negated do
+ argument :foo, GraphQL::STRING_TYPE, required: false
+ end
+
+ expect(test_resolver.arguments['not'].type.arguments.keys).to match_array(['foo'])
+ end
+
+ it 'defines all arguments passed as block even if called multiple times' do
+ test_resolver.negated do
+ argument :foo, GraphQL::STRING_TYPE, required: false
+ end
+ test_resolver.negated do
+ argument :bar, GraphQL::STRING_TYPE, required: false
+ end
+
+ expect(test_resolver.arguments['not'].type.arguments.keys).to match_array(%w[foo bar])
+ end
+
+ it 'allows to specify custom argument name' do
+ test_resolver.negated(param_key: :negative) {}
+
+ expect(test_resolver.arguments).to include('negative')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
index 02e67488d3f..839ad9110cc 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
@@ -337,6 +337,7 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
describe '#nodes' do
let_it_be(:all_nodes) { create_list(:project, 5) }
+
let(:paged_nodes) { subject.nodes }
it_behaves_like 'connection with paged nodes' do
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb
index ec2ec4bf50d..792cb03e8c7 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/last_items_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Graphql::Pagination::Keyset::LastItems do
let_it_be(:merge_request) { create(:merge_request) }
+
let(:scope) { MergeRequest.order_merged_at_asc }
subject { described_class.take_items(*args) }
diff --git a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
index 8450396284a..fc723138d88 100644
--- a/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
+++ b/spec/lib/gitlab/graphql/query_analyzers/logger_analyzer_spec.rb
@@ -3,43 +3,46 @@
require 'spec_helper'
RSpec.describe Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer do
- subject { described_class.new }
-
- describe '#initial_value' do
- it 'filters out sensitive variables' do
- doc = GraphQL.parse <<-GRAPHQL
- mutation createNote($body: String!) {
- createNote(input: {noteableId: "1", body: $body}) {
- note {
- id
- }
+ let(:initial_value) { analyzer.initial_value(query) }
+ let(:analyzer) { described_class.new }
+ let(:query) { GraphQL::Query.new(GitlabSchema, document: document, context: {}, variables: { body: "some note" }) }
+ let(:document) do
+ GraphQL.parse <<-GRAPHQL
+ mutation createNote($body: String!) {
+ createNote(input: {noteableId: "1", body: $body}) {
+ note {
+ id
}
}
- GRAPHQL
+ }
+ GRAPHQL
+ end
- query = GraphQL::Query.new(GitlabSchema, document: doc, context: {}, variables: { body: "some note" })
+ describe 'variables' do
+ subject { initial_value.fetch(:variables) }
- expect(subject.initial_value(query)[:variables]).to eq('{:body=>"[FILTERED]"}')
- end
+ it { is_expected.to eq('{:body=>"[FILTERED]"}') }
end
describe '#final_value' do
let(:monotonic_time_before) { 42 }
let(:monotonic_time_after) { 500 }
let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before }
+ let(:memo) { initial_value }
+
+ subject(:final_value) { analyzer.final_value(memo) }
+
+ before do
+ RequestStore.store[:graphql_logs] = nil
- it 'returns a duration in seconds' do
allow(GraphQL::Analysis).to receive(:analyze_query).and_return([4, 2, [[], []]])
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
allow(Gitlab::GraphqlLogger).to receive(:info)
+ end
- expected_duration = monotonic_time_duration
- memo = subject.initial_value(spy('query'))
-
- subject.final_value(memo)
-
- expect(memo).to have_key(:duration_s)
- expect(memo[:duration_s]).to eq(expected_duration)
+ it 'inserts duration in seconds to memo and sets request store' do
+ expect { final_value }.to change { memo[:duration_s] }.to(monotonic_time_duration)
+ .and change { RequestStore.store[:graphql_logs] }.to([memo])
end
end
end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 9271b868e36..1a929373716 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -79,6 +79,21 @@ RSpec.describe Gitlab::Highlight do
expect(result).to eq(expected)
end
+
+ context 'when start line number is set' do
+ let(:expected) do
+ %q(<span id="LC10" class="line" lang="diff"><span class="gi">+aaa</span></span>
+<span id="LC11" class="line" lang="diff"><span class="gi">+bbb</span></span>
+<span id="LC12" class="line" lang="diff"><span class="gd">- ccc</span></span>
+<span id="LC13" class="line" lang="diff"> ddd</span>)
+ end
+
+ it 'highlights each line properly' do
+ result = described_class.new(file_name, content).highlight(content, context: { line_number: 10 })
+
+ expect(result).to eq(expected)
+ end
+ end
end
describe 'with CRLF' do
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index 8a2395d70b2..8f898d898de 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::HookData::IssueBuilder do
let_it_be(:label) { create(:label) }
let_it_be(:issue) { create(:labeled_issue, labels: [label], project: label.project) }
+
let(:builder) { described_class.new(issue) }
describe '#build' do
diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
index fede7f273f1..0339faa9fcf 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::HookData::MergeRequestBuilder do
let_it_be(:merge_request) { create(:merge_request) }
+
let(:builder) { described_class.new(merge_request) }
describe '#build' do
diff --git a/spec/lib/gitlab/hook_data/release_builder_spec.rb b/spec/lib/gitlab/hook_data/release_builder_spec.rb
index b630780b162..449965f5df1 100644
--- a/spec/lib/gitlab/hook_data/release_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/release_builder_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::HookData::ReleaseBuilder do
let_it_be(:project) { create(:project, :public, :repository) }
+
let(:release) { create(:release, project: project) }
let(:builder) { described_class.new(release) }
diff --git a/spec/lib/gitlab/hook_data/user_builder_spec.rb b/spec/lib/gitlab/hook_data/user_builder_spec.rb
new file mode 100644
index 00000000000..f971089850b
--- /dev/null
+++ b/spec/lib/gitlab/hook_data/user_builder_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::HookData::UserBuilder do
+ let_it_be(:user) { create(:user, name: 'John Doe', username: 'johndoe', email: 'john@example.com') }
+
+ describe '#build' do
+ let(:data) { described_class.new(user).build(event) }
+ let(:event_name) { data[:event_name] }
+ let(:attributes) do
+ [
+ :event_name, :created_at, :updated_at, :name, :email, :user_id, :username
+ ]
+ end
+
+ context 'data' do
+ shared_examples_for 'includes the required attributes' do
+ it 'includes the required attributes' do
+ expect(data).to include(*attributes)
+
+ expect(data[:name]).to eq('John Doe')
+ expect(data[:email]).to eq('john@example.com')
+ expect(data[:user_id]).to eq(user.id)
+ expect(data[:username]).to eq('johndoe')
+ expect(data[:created_at]).to eq(user.created_at.xmlschema)
+ expect(data[:updated_at]).to eq(user.updated_at.xmlschema)
+ end
+ end
+
+ shared_examples_for 'does not include old username attributes' do
+ it 'does not include old username attributes' do
+ expect(data).not_to include(:old_username)
+ end
+ end
+
+ shared_examples_for 'does not include state attributes' do
+ it 'does not include state attributes' do
+ expect(data).not_to include(:state)
+ end
+ end
+
+ context 'on create' do
+ let(:event) { :create }
+
+ it { expect(event_name).to eq('user_create') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include old username attributes'
+ it_behaves_like 'does not include state attributes'
+ end
+
+ context 'on destroy' do
+ let(:event) { :destroy }
+
+ it { expect(event_name).to eq('user_destroy') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include old username attributes'
+ it_behaves_like 'does not include state attributes'
+ end
+
+ context 'on rename' do
+ let(:event) { :rename }
+
+ it { expect(event_name).to eq('user_rename') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include state attributes'
+
+ it 'includes old username details' do
+ allow(user).to receive(:username_before_last_save).and_return('old-username')
+
+ expect(data[:old_username]).to eq(user.username_before_last_save)
+ end
+ end
+
+ context 'on failed_login' do
+ let(:event) { :failed_login }
+
+ it { expect(event_name).to eq('user_failed_login') }
+ it_behaves_like 'includes the required attributes'
+ it_behaves_like 'does not include old username attributes'
+
+ it 'includes state details' do
+ user.ldap_block!
+
+ expect(data[:state]).to eq('ldap_blocked')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
index 96e6e485841..7c57d162e9b 100644
--- a/spec/lib/gitlab/http_connection_adapter_spec.rb
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -124,130 +124,5 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
expect(connection.port).to eq(443)
end
end
-
- context 'when proxy settings are configured' do
- let(:options) do
- {
- http_proxyaddr: 'https://proxy.org',
- http_proxyport: 1557,
- http_proxyuser: 'user',
- http_proxypass: 'pass'
- }
- end
-
- before do
- stub_all_dns('https://proxy.org', ip_address: '166.84.12.54')
- end
-
- it 'sets up the proxy settings' do
- expect(connection.proxy_address).to eq('https://166.84.12.54')
- expect(connection.proxy_port).to eq(1557)
- expect(connection.proxy_user).to eq('user')
- expect(connection.proxy_pass).to eq('pass')
- end
-
- context 'when the address has path' do
- before do
- options[:http_proxyaddr] = 'https://proxy.org/path'
- end
-
- it 'sets up the proxy settings' do
- expect(connection.proxy_address).to eq('https://166.84.12.54/path')
- expect(connection.proxy_port).to eq(1557)
- end
- end
-
- context 'when the port is in the address and port' do
- before do
- options[:http_proxyaddr] = 'https://proxy.org:1422'
- end
-
- it 'sets up the proxy settings' do
- expect(connection.proxy_address).to eq('https://166.84.12.54')
- expect(connection.proxy_port).to eq(1557)
- end
-
- context 'when the port is only in the address' do
- before do
- options[:http_proxyport] = nil
- end
-
- it 'sets up the proxy settings' do
- expect(connection.proxy_address).to eq('https://166.84.12.54')
- expect(connection.proxy_port).to eq(1422)
- end
- end
- end
-
- context 'when it is a request to local network' do
- before do
- options[:http_proxyaddr] = 'http://172.16.0.0/12'
- end
-
- it 'raises error' do
- expect { subject }.to raise_error(
- Gitlab::HTTP::BlockedUrlError,
- "URL 'http://172.16.0.0:1557/12' is blocked: Requests to the local network are not allowed"
- )
- end
-
- context 'when local request allowed' do
- before do
- options[:allow_local_requests] = true
- end
-
- it 'sets up the connection' do
- expect(connection.proxy_address).to eq('http://172.16.0.0/12')
- expect(connection.proxy_port).to eq(1557)
- end
- end
- end
-
- context 'when it is a request to local address' do
- before do
- options[:http_proxyaddr] = 'http://127.0.0.1'
- end
-
- it 'raises error' do
- expect { subject }.to raise_error(
- Gitlab::HTTP::BlockedUrlError,
- "URL 'http://127.0.0.1:1557' is blocked: Requests to localhost are not allowed"
- )
- end
-
- context 'when local request allowed' do
- before do
- options[:allow_local_requests] = true
- end
-
- it 'sets up the connection' do
- expect(connection.proxy_address).to eq('http://127.0.0.1')
- expect(connection.proxy_port).to eq(1557)
- end
- end
- end
-
- context 'when http(s) environment variable is set' do
- before do
- stub_env('https_proxy' => 'https://my.proxy')
- end
-
- it 'sets up the connection' do
- expect(connection.proxy_address).to eq('https://proxy.org')
- expect(connection.proxy_port).to eq(1557)
- end
- end
-
- context 'when DNS rebinding protection is disabled' do
- before do
- stub_application_setting(dns_rebinding_protection_enabled: false)
- end
-
- it 'sets up the connection' do
- expect(connection.proxy_address).to eq('https://proxy.org')
- expect(connection.proxy_port).to eq(1557)
- end
- end
- end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 37b43066a62..5d1e3c79474 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -256,6 +256,8 @@ ci_pipelines:
- messages
- pipeline_artifacts
- latest_statuses
+- dast_profile
+- dast_profiles_pipeline
ci_refs:
- project
- ci_pipelines
@@ -269,6 +271,7 @@ stages:
- builds
- bridges
- latest_statuses
+- retried_statuses
statuses:
- project
- pipeline
@@ -740,3 +743,5 @@ status_page_published_incident:
- issue
issuable_sla:
- issue
+push_rule:
+ - group
diff --git a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
index 5501e3dee5a..fd3539ab99c 100644
--- a/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/design_repo_saver_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::ImportExport::DesignRepoSaver do
describe 'bundle a design Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:design) { create(:design, :with_file, versions_count: 1) }
+
let!(:project) { create(:project, :design_repo) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
index d084b9d7f7e..29b192de809 100644
--- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe Gitlab::ImportExport::FastHashSerializer do
let_it_be(:user) { create(:user) }
let_it_be(:project) { setup_project }
+
let(:shared) { project.import_export_shared }
let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) }
let(:tree) { reader.project_tree }
diff --git a/spec/lib/gitlab/import_export/project/export_task_spec.rb b/spec/lib/gitlab/import_export/project/export_task_spec.rb
index 1048379a5d6..7fcd2187a90 100644
--- a/spec/lib/gitlab/import_export/project/export_task_spec.rb
+++ b/spec/lib/gitlab/import_export/project/export_task_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::ImportExport::Project::ExportTask do
let_it_be(:username) { 'root' }
let(:namespace_path) { username }
let_it_be(:user) { create(:user, username: username) }
+
let(:measurement_enabled) { false }
let(:file_path) { 'spec/fixtures/gitlab/import_export/test_project_export.tar.gz' }
let(:project) { create(:project, creator: user, namespace: user.namespace) }
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index e2bf87bf29f..bc5e6ea7bb3 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -684,7 +684,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
it 'overrides project feature access levels' do
access_level_keys = ProjectFeature.available_features.map { |feature| ProjectFeature.access_level_attribute(feature) }
- disabled_access_levels = Hash[access_level_keys.collect { |item| [item, 'disabled'] }]
+ disabled_access_levels = access_level_keys.to_h { |item| [item, 'disabled'] }
project.create_import_data(data: { override_params: disabled_access_levels })
diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
index 50494433c5d..fd6c66a10a7 100644
--- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -267,6 +267,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
describe '#saves project tree' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
+
let(:project) { setup_project }
let(:full_path) do
if ndjson_enabled
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index 52001e778d6..73e0e0a08b9 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport::RepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
+
let!(:project) { create(:project, :repository) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
index 323ed9a746e..9f3e8d2fa86 100644
--- a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::ImportExport::SnippetRepoSaver do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
+
let(:shared) { project.import_export_shared }
let(:bundler) { described_class.new(project: project, shared: shared, repository: snippet.repository) }
let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
diff --git a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
index 8507c46ec83..aa284c60e73 100644
--- a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::ImportExport::SnippetsRepoSaver do
describe 'bundle a project Git repo' do
let_it_be(:user) { create(:user) }
+
let!(:project) { create(:project) }
let(:shared) { project.import_export_shared }
let(:bundler) { described_class.new(current_user: user, project: project, shared: shared) }
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 540f90e7804..c936d2bc27d 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::ImportExport::WikiRepoSaver do
describe 'bundle a wiki Git repo' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :wiki_repo) }
+
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
let(:wiki_bundler) { described_class.new(exportable: project, shared: shared) }
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index a5c9cde4c37..488324ccddc 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -6,53 +6,6 @@ require 'rspec-parameterized'
RSpec.describe Gitlab::InstrumentationHelper do
using RSpec::Parameterized::TableSyntax
- describe '.keys' do
- it 'returns all available payload keys' do
- expected_keys = [
- :cpu_s,
- :gitaly_calls,
- :gitaly_duration_s,
- :rugged_calls,
- :rugged_duration_s,
- :elasticsearch_calls,
- :elasticsearch_duration_s,
- :elasticsearch_timed_out_count,
- :mem_objects,
- :mem_bytes,
- :mem_mallocs,
- :redis_calls,
- :redis_duration_s,
- :redis_read_bytes,
- :redis_write_bytes,
- :redis_action_cable_calls,
- :redis_action_cable_duration_s,
- :redis_action_cable_read_bytes,
- :redis_action_cable_write_bytes,
- :redis_cache_calls,
- :redis_cache_duration_s,
- :redis_cache_read_bytes,
- :redis_cache_write_bytes,
- :redis_queues_calls,
- :redis_queues_duration_s,
- :redis_queues_read_bytes,
- :redis_queues_write_bytes,
- :redis_shared_state_calls,
- :redis_shared_state_duration_s,
- :redis_shared_state_read_bytes,
- :redis_shared_state_write_bytes,
- :db_count,
- :db_write_count,
- :db_cached_count,
- :external_http_count,
- :external_http_duration_s,
- :rack_attack_redis_count,
- :rack_attack_redis_duration_s
- ]
-
- expect(described_class.keys).to eq(expected_keys)
- end
- end
-
describe '.add_instrumentation_data', :request_store do
let(:payload) { {} }
diff --git a/spec/lib/gitlab/jira_import/base_importer_spec.rb b/spec/lib/gitlab/jira_import/base_importer_spec.rb
index 1470bad2c4c..9d8143775f9 100644
--- a/spec/lib/gitlab/jira_import/base_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/base_importer_spec.rb
@@ -27,6 +27,7 @@ RSpec.describe Gitlab::JiraImport::BaseImporter do
context 'when import data exists' do
let_it_be(:project) { create(:project) }
let_it_be(:jira_import) { create(:jira_import_state, project: project) }
+
let(:subject) { described_class.new(project) }
context 'when #imported_items_cache_key is not implemented' do
diff --git a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
index 4e2c5afb077..b8c0dc64581 100644
--- a/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
+++ b/spec/lib/gitlab/jira_import/handle_labels_service_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe Gitlab::JiraImport::HandleLabelsService do
let_it_be(:project_label) { create(:label, project: project, title: 'bug') }
let_it_be(:other_project_label) { create(:label, title: 'feature') }
let_it_be(:group_label) { create(:group_label, group: group, title: 'dev') }
+
let(:jira_labels) { %w(bug feature dev group::new) }
subject { described_class.new(project, jira_labels).execute }
diff --git a/spec/lib/gitlab/jira_import_spec.rb b/spec/lib/gitlab/jira_import_spec.rb
index 2b602c80640..94fdff984d5 100644
--- a/spec/lib/gitlab/jira_import_spec.rb
+++ b/spec/lib/gitlab/jira_import_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe Gitlab::JiraImport do
include JiraServiceHelper
let_it_be(:project, reload: true) { create(:project) }
+
let(:additional_params) { {} }
subject { described_class.validate_project_settings!(project, **additional_params) }
diff --git a/spec/lib/gitlab/json_spec.rb b/spec/lib/gitlab/json_spec.rb
index 59ec94f2855..42c4b315edf 100644
--- a/spec/lib/gitlab/json_spec.rb
+++ b/spec/lib/gitlab/json_spec.rb
@@ -348,6 +348,66 @@ RSpec.describe Gitlab::Json do
subject
end
end
+
+ context "precompiled JSON" do
+ let(:obj) { Gitlab::Json::PrecompiledJson.new(result) }
+
+ it "renders the string directly" do
+ expect(subject).to eq(result)
+ end
+
+ it "calls #to_s on the object" do
+ expect(obj).to receive(:to_s).once
+
+ subject
+ end
+
+ it "doesn't run the JSON formatter" do
+ expect(Gitlab::Json).not_to receive(:dump)
+
+ subject
+ end
+ end
+ end
+
+ describe Gitlab::Json::PrecompiledJson do
+ subject(:precompiled) { described_class.new(obj) }
+
+ describe "#to_s" do
+ subject { precompiled.to_s }
+
+ context "obj is a string" do
+ let(:obj) { "{}" }
+
+ it "returns a string" do
+ expect(subject).to eq("{}")
+ end
+ end
+
+ context "obj is an array" do
+ let(:obj) { ["{\"foo\": \"bar\"}", "{}"] }
+
+ it "returns a string" do
+ expect(subject).to eq("[{\"foo\": \"bar\"},{}]")
+ end
+ end
+
+ context "obj is an array of un-stringables" do
+ let(:obj) { [BasicObject.new] }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(NoMethodError)
+ end
+ end
+
+ context "obj is something else" do
+ let(:obj) { {} }
+
+ it "raises an error" do
+ expect { subject }.to raise_error(described_class::UnsupportedFormatError)
+ end
+ end
+ end
end
describe Gitlab::Json::LimitedEncoder do
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index 56074147854..9a4d7bd996e 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -290,7 +290,7 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
subject { described_class.new(project) }
before do
- project.update(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git")
+ project.update!(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git")
end
it_behaves_like 'Gitlab::LegacyGithubImport::Importer#execute' do
diff --git a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
index 4b1e0d2c144..454bab8846c 100644
--- a/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/issue_formatter_spec.rb
@@ -152,7 +152,7 @@ RSpec.describe Gitlab::LegacyGithubImport::IssueFormatter do
context 'when importing a Gitea project' do
before do
- project.update(import_type: 'gitea')
+ project.update!(import_type: 'gitea')
end
it_behaves_like 'Gitlab::LegacyGithubImport::IssueFormatter#attributes'
diff --git a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
index 148b59dedab..64fcc46d304 100644
--- a/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/milestone_formatter_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::LegacyGithubImport::MilestoneFormatter do
let(:iid_attr) { :id }
before do
- project.update(import_type: 'gitea')
+ project.update!(import_type: 'gitea')
end
it_behaves_like 'Gitlab::LegacyGithubImport::MilestoneFormatter#attributes'
diff --git a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
index 3e6b9340d0b..7d8875e36c3 100644
--- a/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/pull_request_formatter_spec.rb
@@ -260,7 +260,7 @@ RSpec.describe Gitlab::LegacyGithubImport::PullRequestFormatter do
context 'when importing a Gitea project' do
before do
- project.update(import_type: 'gitea')
+ project.update!(import_type: 'gitea')
end
it_behaves_like 'Gitlab::LegacyGithubImport::PullRequestFormatter#attributes'
diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
index be562d916d3..23dbd4a5bb3 100644
--- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
end
let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
- let(:thing) { klass.create(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { klass.create!(title: markdown, title_html: html, cached_markdown_version: cache_version) }
let(:markdown) { '`Foo`' }
let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' }
@@ -28,7 +28,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
before do
thing.title = thing.title
- thing.save
+ thing.save!
end
it { expect(thing.title).to eq(markdown) }
@@ -38,11 +38,11 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
end
context 'a changed markdown field' do
- let(:thing) { klass.create(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { klass.create!(title: markdown, title_html: html, cached_markdown_version: cache_version) }
before do
thing.title = updated_markdown
- thing.save
+ thing.save!
end
it { expect(thing.title_html).to eq(updated_html) }
@@ -53,9 +53,9 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
it do
expect(thing).to receive(:refresh_markdown_cache).once
thing.title = ''
- thing.save
+ thing.save!
thing.title = ''
- thing.save
+ thing.save!
end
end
@@ -63,9 +63,9 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
it do
expect(thing).to receive(:refresh_markdown_cache).once
thing.title = '[//]: # (This is also a comment.)'
- thing.save
+ thing.save!
thing.title = '[//]: # (This is also a comment.)'
- thing.save
+ thing.save!
end
end
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
before do
thing.state_id = 2
- thing.save
+ thing.save!
end
it { expect(thing.state_id).to eq(2) }
@@ -87,7 +87,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
let(:thing) { klass.new(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
before do
- thing.save
+ thing.save!
end
it { expect(thing.title_html).to eq(updated_html) }
@@ -99,7 +99,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
thing.project = :new_project
allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
- thing.save
+ thing.save!
expect(thing.title_html).to eq(updated_html)
expect(thing.description_html).to eq(updated_html)
@@ -110,7 +110,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
thing.author = :new_author
allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html)
- thing.save
+ thing.save!
expect(thing.title_html).to eq(updated_html)
expect(thing.description_html).to eq(updated_html)
@@ -125,7 +125,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do
end
describe '#cached_html_up_to_date?' do
- let(:thing) { klass.create(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
+ let(:thing) { klass.create!(title: updated_markdown, title_html: html, cached_markdown_version: nil) }
subject { thing.cached_html_up_to_date?(:title) }
diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
index 3dcb9f160ba..b5d458f15fc 100644
--- a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb
@@ -7,7 +7,8 @@ RSpec.describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cach
include CacheMarkdownField
def initialize(title: nil, description: nil)
- @title, @description = title, description
+ @title = title
+ @description = description
end
attr_reader :title, :description
diff --git a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
index bf40af8e62e..07a87b245c2 100644
--- a/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/redis/store_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
describe '.bulk_read' do
before do
- store.save(field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1)
+ store.save(field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1) # rubocop:disable Rails/SaveBang
end
it 'returns a hash of values from store' do
@@ -59,7 +59,7 @@ RSpec.describe Gitlab::MarkdownCache::Redis::Store, :clean_gitlab_redis_cache do
it 'stores updates to html fields and version' do
values_to_store = { field_1_html: "hello", field_2_html: "world", cached_markdown_version: 1 }
- store.save(values_to_store)
+ store.save(values_to_store) # rubocop:disable Rails/SaveBang
expect(read_values)
.to eq(field_1_html: "hello", field_2_html: "world", cached_markdown_version: "1")
diff --git a/spec/lib/gitlab/marker_range_spec.rb b/spec/lib/gitlab/marker_range_spec.rb
index 5f73d2a5048..c4670ec58a8 100644
--- a/spec/lib/gitlab/marker_range_spec.rb
+++ b/spec/lib/gitlab/marker_range_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Gitlab::MarkerRange do
let(:last) { 10 }
let(:mode) { nil }
- it { is_expected.to eq(first..last) }
+ it { expect(marker_range.to_range).to eq(first..last) }
it 'behaves like a Range' do
is_expected.to be_kind_of(Range)
@@ -51,14 +51,14 @@ RSpec.describe Gitlab::MarkerRange do
end
it 'keeps correct range' do
- is_expected.to eq(range)
+ is_expected.to eq(described_class.new(1, 3))
end
context 'when range excludes end' do
let(:range) { 1...3 }
it 'keeps correct range' do
- is_expected.to eq(range)
+ is_expected.to eq(described_class.new(1, 3, exclude_end: true))
end
end
@@ -68,4 +68,31 @@ RSpec.describe Gitlab::MarkerRange do
it { is_expected.to be(marker_range) }
end
end
+
+ describe '#==' do
+ subject { default_marker_range == another_marker_range }
+
+ let(:default_marker_range) { described_class.new(0, 1, mode: :addition) }
+ let(:another_marker_range) { default_marker_range }
+
+ it { is_expected.to be_truthy }
+
+ context 'when marker ranges have different modes' do
+ let(:another_marker_range) { described_class.new(0, 1, mode: :deletion) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when marker ranges have different ranges' do
+ let(:another_marker_range) { described_class.new(0, 2, mode: :addition) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when marker ranges is a simple range' do
+ let(:another_marker_range) { (0..1) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb
index b31a2f7549a..d36ee24fc50 100644
--- a/spec/lib/gitlab/metrics/background_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/background_transaction_spec.rb
@@ -29,19 +29,62 @@ RSpec.describe Gitlab::Metrics::BackgroundTransaction do
end
describe '#labels' do
- it 'provides labels with endpoint_id and feature_category' do
- Labkit::Context.with_context(feature_category: 'projects', caller_id: 'TestWorker') do
- expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects' })
+ context 'when the worker queue is accessible' do
+ before do
+ test_worker_class = Class.new do
+ def self.queue
+ 'test_worker'
+ end
+ end
+ stub_const('TestWorker', test_worker_class)
+ end
+
+ it 'provides labels with endpoint_id, feature_category and queue' do
+ Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
+ expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects', queue: 'test_worker' })
+ end
+ end
+ end
+
+ context 'when the worker name does not exist' do
+ it 'provides labels with endpoint_id and feature_category' do
+ # 123TestWorker is an invalid constant
+ Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: '123TestWorker') do
+ expect(transaction.labels).to eq({ endpoint_id: '123TestWorker', feature_category: 'projects', queue: nil })
+ end
+ end
+ end
+
+ context 'when the worker queue is not accessible' do
+ before do
+ stub_const('TestWorker', Class.new)
+ end
+
+ it 'provides labels with endpoint_id and feature_category' do
+ Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
+ expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects', queue: nil })
+ end
end
end
end
RSpec.shared_examples 'metric with labels' do |metric_method|
+ before do
+ test_worker_class = Class.new do
+ def self.queue
+ 'test_worker'
+ end
+ end
+ stub_const('TestWorker', test_worker_class)
+ end
+
it 'measures with correct labels and value' do
value = 1
- expect(prometheus_metric).to receive(metric_method).with({ endpoint_id: 'TestWorker', feature_category: 'projects' }, value)
+ expect(prometheus_metric).to receive(metric_method).with({
+ endpoint_id: 'TestWorker', feature_category: 'projects', queue: 'test_worker'
+ }, value)
- Labkit::Context.with_context(feature_category: 'projects', caller_id: 'TestWorker') do
+ Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
transaction.send(metric_method, :test_metric, value)
end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index dffd37eeb9d..6bfcfa21289 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -8,65 +8,146 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActiveRecord do
let(:env) { {} }
let(:subscriber) { described_class.new }
let(:connection) { double(:connection) }
- let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10', connection: connection } }
-
- let(:event) do
- double(
- :event,
- name: 'sql.active_record',
- duration: 2,
- payload: payload
- )
- end
- # Emulate Marginalia pre-pending comments
- def sql(query, comments: true)
- if comments && !%w[BEGIN COMMIT].include?(query)
- "/*application:web,controller:badges,action:pipeline,correlation_id:01EYN39K9VMJC56Z7808N7RSRH*/ #{query}"
- else
- query
+ describe '#transaction' do
+ let(:web_transaction) { double('Gitlab::Metrics::WebTransaction') }
+ let(:background_transaction) { double('Gitlab::Metrics::WebTransaction') }
+
+ let(:event) do
+ double(
+ :event,
+ name: 'transaction.active_record',
+ duration: 230,
+ payload: { connection: connection }
+ )
end
- end
- shared_examples 'track generic sql events' do
- where(:name, :sql_query, :record_query, :record_write_query, :record_cached_query) do
- 'SQL' | 'SELECT * FROM users WHERE id = 10' | true | false | false
- 'SQL' | 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' | true | false | false
- 'SQL' | 'SELECT * FROM users WHERE id = 10 FOR UPDATE' | true | true | false
- 'SQL' | 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' | true | true | false
- 'SQL' | 'DELETE FROM users where id = 10' | true | true | false
- 'SQL' | 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' | true | true | false
- 'SQL' | 'UPDATE users SET admin = true WHERE id = 10' | true | true | false
- 'CACHE' | 'SELECT * FROM users WHERE id = 10' | true | false | true
- 'SCHEMA' | "SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '\"projects\"'::regclass" | false | false | false
- nil | 'BEGIN' | false | false | false
- nil | 'COMMIT' | false | false | false
+ before do
+ allow(background_transaction).to receive(:observe)
+ allow(web_transaction).to receive(:observe)
end
- with_them do
- let(:payload) { { name: name, sql: sql(sql_query, comments: comments), connection: connection } }
+ context 'when both web and background transaction are available' do
+ before do
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current)
+ .and_return(web_transaction)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current)
+ .and_return(background_transaction)
+ end
+
+ it 'captures the metrics for web only' do
+ expect(web_transaction).to receive(:observe).with(:gitlab_database_transaction_seconds, 0.23)
- it 'marks the current thread as using the database' do
- # since it would already have been toggled by other specs
- Thread.current[:uses_db_connection] = nil
+ expect(background_transaction).not_to receive(:observe)
+ expect(background_transaction).not_to receive(:increment)
- expect { subscriber.sql(event) }.to change { Thread.current[:uses_db_connection] }.from(nil).to(true)
+ subscriber.transaction(event)
end
+ end
+
+ context 'when web transaction is available' do
+ let(:web_transaction) { double('Gitlab::Metrics::WebTransaction') }
+
+ before do
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current)
+ .and_return(web_transaction)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current)
+ .and_return(nil)
+ end
+
+ it 'captures the metrics for web only' do
+ expect(web_transaction).to receive(:observe).with(:gitlab_database_transaction_seconds, 0.23)
- it_behaves_like 'record ActiveRecord metrics'
- it_behaves_like 'store ActiveRecord info in RequestStore'
+ expect(background_transaction).not_to receive(:observe)
+ expect(background_transaction).not_to receive(:increment)
+
+ subscriber.transaction(event)
+ end
end
- end
- context 'without Marginalia comments' do
- let(:comments) { false }
+ context 'when background transaction is available' do
+ let(:background_transaction) { double('Gitlab::Metrics::BackgroundTransaction') }
+
+ before do
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current)
+ .and_return(nil)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current)
+ .and_return(background_transaction)
+ end
- it_behaves_like 'track generic sql events'
+ it 'captures the metrics for web only' do
+ expect(background_transaction).to receive(:observe).with(:gitlab_database_transaction_seconds, 0.23)
+
+ expect(web_transaction).not_to receive(:observe)
+ expect(web_transaction).not_to receive(:increment)
+
+ subscriber.transaction(event)
+ end
+ end
end
- context 'with Marginalia comments' do
- let(:comments) { true }
+ describe '#sql' do
+ let(:payload) { { sql: 'SELECT * FROM users WHERE id = 10', connection: connection } }
- it_behaves_like 'track generic sql events'
+ let(:event) do
+ double(
+ :event,
+ name: 'sql.active_record',
+ duration: 2,
+ payload: payload
+ )
+ end
+
+ # Emulate Marginalia pre-pending comments
+ def sql(query, comments: true)
+ if comments && !%w[BEGIN COMMIT].include?(query)
+ "/*application:web,controller:badges,action:pipeline,correlation_id:01EYN39K9VMJC56Z7808N7RSRH*/ #{query}"
+ else
+ query
+ end
+ end
+
+ shared_examples 'track generic sql events' do
+ where(:name, :sql_query, :record_query, :record_write_query, :record_cached_query) do
+ 'SQL' | 'SELECT * FROM users WHERE id = 10' | true | false | false
+ 'SQL' | 'WITH active_milestones AS (SELECT COUNT(*), state FROM milestones GROUP BY state) SELECT * FROM active_milestones' | true | false | false
+ 'SQL' | 'SELECT * FROM users WHERE id = 10 FOR UPDATE' | true | true | false
+ 'SQL' | 'WITH archived_rows AS (SELECT * FROM users WHERE archived = true) INSERT INTO products_log SELECT * FROM archived_rows' | true | true | false
+ 'SQL' | 'DELETE FROM users where id = 10' | true | true | false
+ 'SQL' | 'INSERT INTO project_ci_cd_settings (project_id) SELECT id FROM projects' | true | true | false
+ 'SQL' | 'UPDATE users SET admin = true WHERE id = 10' | true | true | false
+ 'CACHE' | 'SELECT * FROM users WHERE id = 10' | true | false | true
+ 'SCHEMA' | "SELECT attr.attname FROM pg_attribute attr INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) WHERE cons.contype = 'p' AND cons.conrelid = '\"projects\"'::regclass" | false | false | false
+ nil | 'BEGIN' | false | false | false
+ nil | 'COMMIT' | false | false | false
+ end
+
+ with_them do
+ let(:payload) { { name: name, sql: sql(sql_query, comments: comments), connection: connection } }
+ let(:record_wal_query) { false }
+
+ it 'marks the current thread as using the database' do
+ # since it would already have been toggled by other specs
+ Thread.current[:uses_db_connection] = nil
+
+ expect { subscriber.sql(event) }.to change { Thread.current[:uses_db_connection] }.from(nil).to(true)
+ end
+
+ it_behaves_like 'record ActiveRecord metrics'
+ it_behaves_like 'store ActiveRecord info in RequestStore'
+ end
+ end
+
+ context 'without Marginalia comments' do
+ let(:comments) { false }
+
+ it_behaves_like 'track generic sql events'
+ end
+
+ context 'with Marginalia comments' do
+ let(:comments) { true }
+
+ it_behaves_like 'track generic sql events'
+ end
end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb b/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb
index 5bcaf8fbc47..adbc05cb711 100644
--- a/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/external_http_spec.rb
@@ -6,29 +6,45 @@ RSpec.describe Gitlab::Metrics::Subscribers::ExternalHttp, :request_store do
let(:transaction) { Gitlab::Metrics::Transaction.new }
let(:subscriber) { described_class.new }
+ around do |example|
+ freeze_time { example.run }
+ end
+
let(:event_1) do
- double(:event, payload: {
- method: 'POST', code: "200", duration: 0.321,
- scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
- query: 'current=true'
- })
+ double(
+ :event,
+ payload: {
+ method: 'POST', code: "200", duration: 0.321,
+ scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
+ query: 'current=true'
+ },
+ time: Time.current
+ )
end
let(:event_2) do
- double(:event, payload: {
- method: 'GET', code: "301", duration: 0.12,
- scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
- query: 'current=true'
- })
+ double(
+ :event,
+ payload: {
+ method: 'GET', code: "301", duration: 0.12,
+ scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
+ query: 'current=true'
+ },
+ time: Time.current
+ )
end
let(:event_3) do
- double(:event, payload: {
- method: 'POST', duration: 5.3,
- scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
- query: 'current=true',
- exception_object: Net::ReadTimeout.new
- })
+ double(
+ :event,
+ payload: {
+ method: 'POST', duration: 5.3,
+ scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
+ query: 'current=true',
+ exception_object: Net::ReadTimeout.new
+ },
+ time: Time.current
+ )
end
describe '.detail_store' do
@@ -134,19 +150,22 @@ RSpec.describe Gitlab::Metrics::Subscribers::ExternalHttp, :request_store do
subscriber.request(event_3)
expect(Gitlab::SafeRequestStore[:external_http_detail_store].length).to eq(3)
- expect(Gitlab::SafeRequestStore[:external_http_detail_store][0]).to include(
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store][0]).to match a_hash_including(
+ start: be_like_time(Time.current),
method: 'POST', code: "200", duration: 0.321,
scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
query: 'current=true', exception_object: nil,
backtrace: be_a(Array)
)
- expect(Gitlab::SafeRequestStore[:external_http_detail_store][1]).to include(
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store][1]).to match a_hash_including(
+ start: be_like_time(Time.current),
method: 'GET', code: "301", duration: 0.12,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
query: 'current=true', exception_object: nil,
backtrace: be_a(Array)
)
- expect(Gitlab::SafeRequestStore[:external_http_detail_store][2]).to include(
+ expect(Gitlab::SafeRequestStore[:external_http_detail_store][2]).to match a_hash_including(
+ start: be_like_time(Time.current),
method: 'POST', duration: 5.3,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
query: 'current=true',
diff --git a/spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb b/spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb
new file mode 100644
index 00000000000..b9d00b556c5
--- /dev/null
+++ b/spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rack'
+
+RSpec.describe Gitlab::Middleware::RackMultipartTempfileFactory do
+ let(:app) do
+ lambda do |env|
+ params = Rack::Request.new(env).params
+
+ if params['file']
+ [200, { 'Content-Type' => params['file'][:type] }, [params['file'][:tempfile].read]]
+ else
+ [204, {}, []]
+ end
+ end
+ end
+
+ let(:file_contents) { '/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg' }
+
+ let(:multipart_fixture) do
+ boundary = 'AaB03x'
+ data = <<~DATA
+ --#{boundary}\r
+ Content-Disposition: form-data; name="file"; filename="dj.jpg"\r
+ Content-Type: image/jpeg\r
+ Content-Transfer-Encoding: base64\r
+ \r
+ #{file_contents}\r
+ --#{boundary}--\r
+ DATA
+
+ {
+ 'CONTENT_TYPE' => "multipart/form-data; boundary=#{boundary}",
+ 'CONTENT_LENGTH' => data.bytesize.to_s,
+ input: StringIO.new(data)
+ }
+ end
+
+ subject { described_class.new(app) }
+
+ context 'for a multipart request' do
+ let(:env) { Rack::MockRequest.env_for('/', multipart_fixture) }
+
+ context 'when the environment variable is enabled' do
+ before do
+ stub_env('GITLAB_TEMPFILE_IMMEDIATE_UNLINK', '1')
+ end
+
+ it 'immediately unlinks the temporary file' do
+ tempfile = Tempfile.new('foo')
+
+ expect(tempfile.path).not_to be(nil)
+ expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).to receive(:call).and_return(tempfile)
+ expect(tempfile).to receive(:unlink).and_call_original
+
+ subject.call(env)
+
+ expect(tempfile.path).to be(nil)
+ end
+
+ it 'processes the request as normal' do
+ expect(subject.call(env)).to eq([200, { 'Content-Type' => 'image/jpeg' }, [file_contents]])
+ end
+ end
+
+ context 'when the environment variable is disabled' do
+ it 'does not immediately unlink the temporary file' do
+ tempfile = Tempfile.new('foo')
+
+ expect(tempfile.path).not_to be(nil)
+ expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).to receive(:call).and_return(tempfile)
+ expect(tempfile).not_to receive(:unlink).and_call_original
+
+ subject.call(env)
+
+ expect(tempfile.path).not_to be(nil)
+ end
+
+ it 'processes the request as normal' do
+ expect(subject.call(env)).to eq([200, { 'Content-Type' => 'image/jpeg' }, [file_contents]])
+ end
+ end
+ end
+
+ context 'for a regular request' do
+ let(:env) { Rack::MockRequest.env_for('/', params: { 'foo' => 'bar' }) }
+
+ it 'does nothing' do
+ expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).not_to receive(:call)
+ expect(subject.call(env)).to eq([204, {}, []])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index 08e1a5ee0a3..eebd67695e0 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -3,14 +3,16 @@
require 'spec_helper'
RSpec.describe Gitlab::ObjectHierarchy do
- let!(:parent) { create(:group) }
- let!(:child1) { create(:group, parent: parent) }
- let!(:child2) { create(:group, parent: child1) }
+ let_it_be(:parent) { create(:group) }
+ let_it_be(:child1) { create(:group, parent: parent) }
+ let_it_be(:child2) { create(:group, parent: child1) }
+
+ let(:options) { {} }
shared_context 'Gitlab::ObjectHierarchy test cases' do
describe '#base_and_ancestors' do
let(:relation) do
- described_class.new(Group.where(id: child2.id)).base_and_ancestors
+ described_class.new(Group.where(id: child2.id), options: options).base_and_ancestors
end
it 'includes the base rows' do
@@ -22,13 +24,13 @@ RSpec.describe Gitlab::ObjectHierarchy do
end
it 'can find ancestors upto a certain level' do
- relation = described_class.new(Group.where(id: child2)).base_and_ancestors(upto: child1)
+ relation = described_class.new(Group.where(id: child2), options: options).base_and_ancestors(upto: child1)
expect(relation).to contain_exactly(child2)
end
it 'uses ancestors_base #initialize argument' do
- relation = described_class.new(Group.where(id: child2.id), Group.none).base_and_ancestors
+ relation = described_class.new(Group.where(id: child2.id), Group.none, options: options).base_and_ancestors
expect(relation).to include(parent, child1, child2)
end
@@ -40,7 +42,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe 'hierarchy_order option' do
let(:relation) do
- described_class.new(Group.where(id: child2.id)).base_and_ancestors(hierarchy_order: hierarchy_order)
+ described_class.new(Group.where(id: child2.id), options: options).base_and_ancestors(hierarchy_order: hierarchy_order)
end
context ':asc' do
@@ -63,7 +65,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#base_and_descendants' do
let(:relation) do
- described_class.new(Group.where(id: parent.id)).base_and_descendants
+ described_class.new(Group.where(id: parent.id), options: options).base_and_descendants
end
it 'includes the base rows' do
@@ -75,7 +77,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
end
it 'uses descendants_base #initialize argument' do
- relation = described_class.new(Group.none, Group.where(id: parent.id)).base_and_descendants
+ relation = described_class.new(Group.none, Group.where(id: parent.id), options: options).base_and_descendants
expect(relation).to include(parent, child1, child2)
end
@@ -87,7 +89,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
context 'when with_depth is true' do
let(:relation) do
- described_class.new(Group.where(id: parent.id)).base_and_descendants(with_depth: true)
+ described_class.new(Group.where(id: parent.id), options: options).base_and_descendants(with_depth: true)
end
it 'includes depth in the results' do
@@ -106,14 +108,14 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#descendants' do
it 'includes only the descendants' do
- relation = described_class.new(Group.where(id: parent)).descendants
+ relation = described_class.new(Group.where(id: parent), options: options).descendants
expect(relation).to contain_exactly(child1, child2)
end
end
describe '#max_descendants_depth' do
- subject { described_class.new(base_relation).max_descendants_depth }
+ subject { described_class.new(base_relation, options: options).max_descendants_depth }
context 'when base relation is empty' do
let(:base_relation) { Group.where(id: nil) }
@@ -136,13 +138,13 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#ancestors' do
it 'includes only the ancestors' do
- relation = described_class.new(Group.where(id: child2)).ancestors
+ relation = described_class.new(Group.where(id: child2), options: options).ancestors
expect(relation).to contain_exactly(child1, parent)
end
it 'can find ancestors upto a certain level' do
- relation = described_class.new(Group.where(id: child2)).ancestors(upto: child1)
+ relation = described_class.new(Group.where(id: child2), options: options).ancestors(upto: child1)
expect(relation).to be_empty
end
@@ -150,7 +152,7 @@ RSpec.describe Gitlab::ObjectHierarchy do
describe '#all_objects' do
let(:relation) do
- described_class.new(Group.where(id: child1.id)).all_objects
+ described_class.new(Group.where(id: child1.id), options: options).all_objects
end
it 'includes the base rows' do
@@ -166,13 +168,13 @@ RSpec.describe Gitlab::ObjectHierarchy do
end
it 'uses ancestors_base #initialize argument for ancestors' do
- relation = described_class.new(Group.where(id: child1.id), Group.where(id: non_existing_record_id)).all_objects
+ relation = described_class.new(Group.where(id: child1.id), Group.where(id: non_existing_record_id), options: options).all_objects
expect(relation).to include(parent)
end
it 'uses descendants_base #initialize argument for descendants' do
- relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id)).all_objects
+ relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id), options: options).all_objects
expect(relation).to include(child2)
end
@@ -187,19 +189,78 @@ RSpec.describe Gitlab::ObjectHierarchy do
context 'when the use_distinct_in_object_hierarchy feature flag is enabled' do
before do
stub_feature_flags(use_distinct_in_object_hierarchy: true)
+ stub_feature_flags(use_distinct_for_all_object_hierarchy: false)
+ end
+
+ it_behaves_like 'Gitlab::ObjectHierarchy test cases'
+
+ it 'calls DISTINCT' do
+ expect(child2.self_and_ancestors.to_sql).to include("DISTINCT")
+ end
+
+ context 'when use_traversal_ids feature flag is enabled' do
+ it 'does not call DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).not_to include("DISTINCT")
+ end
+ end
+
+ context 'when use_traversal_ids feature flag is disabled' do
+ before do
+ stub_feature_flags(use_traversal_ids: false)
+ end
+
+ it 'calls DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
+ end
+ end
+ end
+
+ context 'when the use_distinct_for_all_object_hierarchy feature flag is enabled' do
+ before do
+ stub_feature_flags(use_distinct_in_object_hierarchy: false)
+ stub_feature_flags(use_distinct_for_all_object_hierarchy: true)
end
it_behaves_like 'Gitlab::ObjectHierarchy test cases'
it 'calls DISTINCT' do
- expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
expect(child2.self_and_ancestors.to_sql).to include("DISTINCT")
end
+
+ context 'when use_traversal_ids feature flag is enabled' do
+ it 'does not call DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).not_to include("DISTINCT")
+ end
+ end
+
+ context 'when use_traversal_ids feature flag is disabled' do
+ before do
+ stub_feature_flags(use_traversal_ids: false)
+ end
+
+ it 'calls DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
+ end
+
+ context 'when the skip_ordering option is set' do
+ let(:options) { { skip_ordering: true } }
+
+ it_behaves_like 'Gitlab::ObjectHierarchy test cases'
+
+ it 'does not include ROW_NUMBER()' do
+ query = described_class.new(Group.where(id: parent.id), options: options).base_and_descendants.to_sql
+
+ expect(query).to include("DISTINCT")
+ expect(query).not_to include("ROW_NUMBER()")
+ end
+ end
+ end
end
context 'when the use_distinct_in_object_hierarchy feature flag is disabled' do
before do
stub_feature_flags(use_distinct_in_object_hierarchy: false)
+ stub_feature_flags(use_distinct_for_all_object_hierarchy: false)
end
it_behaves_like 'Gitlab::ObjectHierarchy test cases'
diff --git a/spec/lib/gitlab/pages/settings_spec.rb b/spec/lib/gitlab/pages/settings_spec.rb
index f5424a98153..c89bf9ff206 100644
--- a/spec/lib/gitlab/pages/settings_spec.rb
+++ b/spec/lib/gitlab/pages/settings_spec.rb
@@ -3,11 +3,11 @@
require 'spec_helper'
RSpec.describe Gitlab::Pages::Settings do
+ let(:settings) { double(path: 'the path', local_store: 'local store') }
+
describe '#path' do
subject { described_class.new(settings).path }
- let(:settings) { double(path: 'the path') }
-
it { is_expected.to eq('the path') }
context 'when running under a web server outside of test mode' do
@@ -16,9 +16,43 @@ RSpec.describe Gitlab::Pages::Settings do
allow(::Gitlab::Runtime).to receive(:web_server?).and_return(true)
end
- it 'raises a DiskAccessDenied exception' do
- expect { subject }.to raise_error(described_class::DiskAccessDenied)
+ it 'logs a DiskAccessDenied error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ instance_of(described_class::DiskAccessDenied)
+ )
+
+ subject
+ end
+ end
+
+ context 'when local_store settings does not exist yet' do
+ before do
+ allow(Settings.pages).to receive(:local_store).and_return(nil)
end
+
+ it { is_expected.to eq('the path') }
+ end
+
+ context 'when local store exists but legacy storage is disabled' do
+ before do
+ allow(Settings.pages.local_store).to receive(:enabled).and_return(false)
+ end
+
+ it 'logs a DiskAccessDenied error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ instance_of(described_class::DiskAccessDenied)
+ )
+
+ subject
+ end
+ end
+ end
+
+ describe '#local_store' do
+ subject(:local_store) { described_class.new(settings).local_store }
+
+ it 'is an instance of Gitlab::Pages::Stores::LocalStore' do
+ expect(local_store).to be_a(Gitlab::Pages::Stores::LocalStore)
end
end
end
diff --git a/spec/lib/gitlab/pages/stores/local_store_spec.rb b/spec/lib/gitlab/pages/stores/local_store_spec.rb
new file mode 100644
index 00000000000..adab81b2589
--- /dev/null
+++ b/spec/lib/gitlab/pages/stores/local_store_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Pages::Stores::LocalStore do
+ describe '#enabled' do
+ let(:local_store) { double(enabled: true) }
+
+ subject(:local_store_enabled) { described_class.new(local_store).enabled }
+
+ context 'when the pages_update_legacy_storage FF is disabled' do
+ before do
+ stub_feature_flags(pages_update_legacy_storage: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the pages_update_legacy_storage FF is enabled' do
+ it 'is equal to the original value' do
+ expect(local_store_enabled).to eq(local_store.enabled)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/pages_transfer_spec.rb b/spec/lib/gitlab/pages_transfer_spec.rb
index 552a2e0701c..021d9cb7318 100644
--- a/spec/lib/gitlab/pages_transfer_spec.rb
+++ b/spec/lib/gitlab/pages_transfer_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe Gitlab::PagesTransfer do
end
it 'does nothing if legacy storage is disabled' do
- stub_feature_flags(pages_update_legacy_storage: false)
+ allow(Settings.pages.local_store).to receive(:enabled).and_return(false)
described_class::METHODS.each do |meth|
expect(PagesTransferWorker)
@@ -72,7 +72,7 @@ RSpec.describe Gitlab::PagesTransfer do
end
it 'does nothing if legacy storage is disabled' do
- stub_feature_flags(pages_update_legacy_storage: false)
+ allow(Settings.pages.local_store).to receive(:enabled).and_return(false)
subject.public_send(meth, *args)
diff --git a/spec/lib/gitlab/pagination/keyset/order_spec.rb b/spec/lib/gitlab/pagination/keyset/order_spec.rb
index 665f790ee47..06a8aee1048 100644
--- a/spec/lib/gitlab/pagination/keyset/order_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset/order_spec.rb
@@ -417,4 +417,59 @@ RSpec.describe Gitlab::Pagination::Keyset::Order do
end
end
end
+
+ context 'extract and apply cursor attributes' do
+ let(:model) { Project.new(id: 100) }
+ let(:scope) { Project.all }
+
+ shared_examples 'cursor attribute examples' do
+ describe '#cursor_attributes_for_node' do
+ it { expect(order.cursor_attributes_for_node(model)).to eq({ id: '100' }.with_indifferent_access) }
+ end
+
+ describe '#apply_cursor_conditions' do
+ context 'when params with string keys are passed' do
+ subject(:sql) { order.apply_cursor_conditions(scope, { 'id' => '100' }).to_sql }
+
+ it { is_expected.to include('"projects"."id" < 100)') }
+ end
+
+ context 'when params with symbol keys are passed' do
+ subject(:sql) { order.apply_cursor_conditions(scope, { id: '100' }).to_sql }
+
+ it { is_expected.to include('"projects"."id" < 100)') }
+ end
+ end
+ end
+
+ context 'when string attribute name is given' do
+ let(:order) do
+ Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'id',
+ order_expression: Project.arel_table['id'].desc,
+ nullable: :not_nullable,
+ distinct: true
+ )
+ ])
+ end
+
+ it_behaves_like 'cursor attribute examples'
+ end
+
+ context 'when symbol attribute name is given' do
+ let(:order) do
+ Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: :id,
+ order_expression: Project.arel_table['id'].desc,
+ nullable: :not_nullable,
+ distinct: true
+ )
+ ])
+ end
+
+ it_behaves_like 'cursor attribute examples'
+ end
+ end
end
diff --git a/spec/lib/gitlab/pagination/offset_header_builder_with_controller_spec.rb b/spec/lib/gitlab/pagination/offset_header_builder_with_controller_spec.rb
new file mode 100644
index 00000000000..85e4b621e83
--- /dev/null
+++ b/spec/lib/gitlab/pagination/offset_header_builder_with_controller_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Pagination::OffsetHeaderBuilder, type: :controller do
+ controller(ActionController::Base) do
+ def index
+ relation = Project.where(archived: params[:archived]).page(params[:page]).order(:id).per(1)
+
+ params_for_pagination = { archived: params[:archived], page: params[:page] }
+
+ Gitlab::Pagination::OffsetHeaderBuilder.new(
+ request_context: self,
+ per_page: relation.limit_value,
+ page: relation.current_page,
+ next_page: relation.next_page,
+ prev_page: relation.prev_page,
+ params: params_for_pagination
+ ).execute(exclude_total_headers: true, data_without_counts: true)
+
+ render json: relation.map(&:id)
+ end
+ end
+
+ let_it_be(:projects) { create_list(:project, 2, archived: true).sort_by(&:id) }
+
+ describe 'pagination' do
+ it 'returns correct result for the first page' do
+ get :index, params: { page: 1, archived: true }
+
+ expect(json_response).to eq([projects.first.id])
+ end
+
+ it 'returns correct result for the second page' do
+ get :index, params: { page: 2, archived: true }
+
+ expect(json_response).to eq([projects.last.id])
+ end
+ end
+
+ describe 'pagination heders' do
+ it 'adds next page header' do
+ get :index, params: { page: 1, archived: true }
+
+ expect(response.headers['X-Next-Page']).to eq('2')
+ end
+
+ it 'adds only the specified params to the lnk' do
+ get :index, params: { page: 1, archived: true, some_param: '1' }
+
+ expect(response.headers['Link']).not_to include('some_param')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
index 08ac85c2625..157b3ca56c9 100644
--- a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
+
let(:redis) { Gitlab::Redis::Cache }
subject(:map) { described_class.new(project) }
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
index 3cb15f08627..0539bacba44 100644
--- a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
RSpec.describe Gitlab::PhabricatorImport::Issues::TaskImporter do
let_it_be(:project) { create(:project) }
+
let(:task) do
Gitlab::PhabricatorImport::Representation::Task.new(
{
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 89917e515d0..48e2a2e9794 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -78,13 +78,8 @@ RSpec.describe Gitlab::Profiler do
end
it 'strips out the private token' do
- expect(custom_logger).to receive(:add) do |severity, _progname, message|
- next if message.include?('spec/')
-
- expect(severity).to eq(Logger::DEBUG)
- expect(message).to include('public').and include(described_class::FILTERED_STRING)
- expect(message).not_to include(private_token)
- end.at_least(1) # This spec could be wrapped in more blocks in the future
+ allow(custom_logger).to receive(:add).and_call_original
+ expect(custom_logger).to receive(:add).with(Logger::DEBUG, anything, 'public [FILTERED]').at_least(1)
custom_logger.debug("public #{private_token}")
end
diff --git a/spec/lib/gitlab/prometheus/adapter_spec.rb b/spec/lib/gitlab/prometheus/adapter_spec.rb
index 4762e4ad108..9d4806ea73b 100644
--- a/spec/lib/gitlab/prometheus/adapter_spec.rb
+++ b/spec/lib/gitlab/prometheus/adapter_spec.rb
@@ -32,6 +32,14 @@ RSpec.describe Gitlab::Prometheus::Adapter do
context "prometheus service can't execute queries" do
let(:prometheus_service) { double(:prometheus_service, can_query?: false) }
+ context 'with cluster with prometheus integration' do
+ let!(:prometheus_integration) { create(:clusters_integrations_prometheus, cluster: cluster) }
+
+ it 'returns the integration' do
+ expect(subject.prometheus_adapter).to eq(prometheus_integration)
+ end
+ end
+
context 'with cluster with prometheus not available' do
let!(:prometheus) { create(:clusters_applications_prometheus, :installable, cluster: cluster) }
@@ -46,6 +54,14 @@ RSpec.describe Gitlab::Prometheus::Adapter do
it 'returns application handling all environments' do
expect(subject.prometheus_adapter).to eq(prometheus)
end
+
+ context 'with cluster with prometheus integration' do
+ let!(:prometheus_integration) { create(:clusters_integrations_prometheus, cluster: cluster) }
+
+ it 'returns the integration instead' do
+ expect(subject.prometheus_adapter).to eq(prometheus_integration)
+ end
+ end
end
context 'with cluster without prometheus installed' do
diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb
index 40804736b86..76bb2b4c4cc 100644
--- a/spec/lib/gitlab/query_limiting/transaction_spec.rb
+++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb
@@ -68,11 +68,15 @@ RSpec.describe Gitlab::QueryLimiting::Transaction do
it 'increments the number of executed queries' do
transaction = described_class.new
- expect(transaction.count).to be_zero
+ expect { transaction.increment }.to change { transaction.count }.by(1)
+ end
+
+ it 'does not increment the number of executed queries when query limiting is disabled' do
+ transaction = described_class.new
- transaction.increment
+ allow(transaction).to receive(:enabled?).and_return(false)
- expect(transaction.count).to eq(1)
+ expect { transaction.increment }.not_to change { transaction.count }
end
end
diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb
index 4f70c65adca..fbb12629056 100644
--- a/spec/lib/gitlab/query_limiting_spec.rb
+++ b/spec/lib/gitlab/query_limiting_spec.rb
@@ -2,81 +2,85 @@
require 'spec_helper'
-RSpec.describe Gitlab::QueryLimiting do
- describe '.enable?' do
+RSpec.describe Gitlab::QueryLimiting, :request_store do
+ describe '.enabled_for_env?' do
it 'returns true in a test environment' do
- expect(described_class.enable?).to eq(true)
+ expect(described_class.enabled_for_env?).to eq(true)
end
it 'returns true in a development environment' do
stub_rails_env('development')
stub_rails_env('development')
- expect(described_class.enable?).to eq(true)
+ expect(described_class.enabled_for_env?).to eq(true)
end
it 'returns false on GitLab.com' do
stub_rails_env('production')
allow(Gitlab).to receive(:com?).and_return(true)
- expect(described_class.enable?).to eq(false)
+ expect(described_class.enabled_for_env?).to eq(false)
end
it 'returns false in a non GitLab.com' do
allow(Gitlab).to receive(:com?).and_return(false)
stub_rails_env('production')
- expect(described_class.enable?).to eq(false)
+ expect(described_class.enabled_for_env?).to eq(false)
end
end
- describe '.whitelist' do
- it 'raises ArgumentError when an invalid issue URL is given' do
- expect { described_class.whitelist('foo') }
- .to raise_error(ArgumentError)
+ shared_context 'disable and enable' do |result|
+ let(:transaction) { Gitlab::QueryLimiting::Transaction.new }
+ let(:code) do
+ proc do
+ 2.times { User.count }
+ end
end
- context 'without a transaction' do
- it 'does nothing' do
- expect { described_class.whitelist('https://example.com') }
- .not_to raise_error
- end
+ before do
+ allow(Gitlab::QueryLimiting::Transaction)
+ .to receive(:current)
+ .and_return(transaction)
end
+ end
- context 'with a transaction' do
- let(:transaction) { Gitlab::QueryLimiting::Transaction.new }
+ describe '.disable!' do
+ include_context 'disable and enable'
- before do
- allow(Gitlab::QueryLimiting::Transaction)
- .to receive(:current)
- .and_return(transaction)
- end
+ it 'raises an ArgumentError when an invalid issue URL is given' do
+ expect { described_class.disable!('foo') }
+ .to raise_error(ArgumentError)
+ end
- it 'does not increment the number of SQL queries executed in the block' do
- before = transaction.count
+ it 'stops the number of SQL queries from being incremented' do
+ described_class.disable!('https://example.com')
- described_class.whitelist('https://example.com')
+ expect { code.call }.not_to change { transaction.count }
+ end
+ end
- 2.times do
- User.count
- end
+ describe '.enable!' do
+ include_context 'disable and enable'
- expect(transaction.count).to eq(before)
- end
+ it 'allows the number of SQL queries to be incremented' do
+ described_class.enable!
- it 'whitelists when enabled' do
- described_class.whitelist('https://example.com')
+ expect { code.call }.to change { transaction.count }.by(2)
+ end
+ end
- expect(transaction.whitelisted).to eq(true)
- end
+ describe '#enabled?' do
+ it 'returns true when enabled' do
+ Gitlab::SafeRequestStore[:query_limiting_disabled] = nil
- it 'does not whitelist when disabled' do
- allow(described_class).to receive(:enable?).and_return(false)
+ expect(described_class).to be_enabled
+ end
- described_class.whitelist('https://example.com')
+ it 'returns false when disabled' do
+ Gitlab::SafeRequestStore[:query_limiting_disabled] = true
- expect(transaction.whitelisted).to eq(false)
- end
+ expect(described_class).not_to be_enabled
end
end
end
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index d63c21954f2..73629ce3da2 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -127,10 +127,10 @@ RSpec.describe Gitlab::QuickActions::CommandDefinition do
subject.condition_block = proc { false }
end
- it "doesn't execute the command" do
+ it "counts the command as executed" do
subject.execute(context, nil)
- expect(context.commands_executed_count).to be_nil
+ expect(context.commands_executed_count).to eq(1)
expect(context.run).to be false
end
end
@@ -238,8 +238,8 @@ RSpec.describe Gitlab::QuickActions::CommandDefinition do
subject.condition_block = proc { false }
end
- it 'returns nil' do
- expect(subject.execute_message({}, nil)).to be_nil
+ it 'returns an error message' do
+ expect(subject.execute_message({}, nil)).to eq('Could not apply command command.')
end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 1aca3dae41b..f62a3c74005 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -667,7 +667,14 @@ RSpec.describe Gitlab::Regex do
it { is_expected.to match('1.2.3') }
it { is_expected.to match('1.3.350') }
- it { is_expected.not_to match('1.3.350-20201230123456') }
+ it { is_expected.to match('1.3.350-20201230123456') }
+ it { is_expected.to match('1.2.3-rc1') }
+ it { is_expected.to match('1.2.3g') }
+ it { is_expected.to match('1.2') }
+ it { is_expected.to match('1.2.bananas') }
+ it { is_expected.to match('v1.2.4-build') }
+ it { is_expected.to match('d50d836eb3de6177ce6c7a5482f27f9c2c84b672') }
+ it { is_expected.to match('this_is_a_string_only') }
it { is_expected.not_to match('..1.2.3') }
it { is_expected.not_to match(' 1.2.3') }
it { is_expected.not_to match("1.2.3 \r\t") }
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 625dcf11546..d14c3f44c6f 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -29,10 +29,19 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do
def project
end
+
+ def cached_methods
+ [:letters]
+ end
+
+ def exists?
+ true
+ end
end
end
let(:fake_repository) { klass.new }
+ let(:redis_set_cache) { fake_repository.redis_set_cache }
context 'with an existing repository' do
it 'caches the output, sorting the results' do
@@ -42,47 +51,43 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do
expect(fake_repository.letters).to eq(%w(a b c))
end
- expect(fake_repository.redis_set_cache.exist?(:letters)).to eq(true)
+ expect(redis_set_cache.exist?(:letters)).to eq(true)
expect(fake_repository.instance_variable_get(:@letters)).to eq(%w(a b c))
end
context 'membership checks' do
context 'when the cache key does not exist' do
it 'calls the original method and populates the cache' do
- expect(fake_repository.redis_set_cache.exist?(:letters)).to eq(false)
+ expect(redis_set_cache.exist?(:letters)).to eq(false)
expect(fake_repository).to receive(:_uncached_letters).once.and_call_original
# This populates the cache and memoizes the full result
expect(fake_repository.letters_include?('a')).to eq(true)
expect(fake_repository.letters_include?('d')).to eq(false)
- expect(fake_repository.redis_set_cache.exist?(:letters)).to eq(true)
+ expect(redis_set_cache.exist?(:letters)).to eq(true)
end
end
context 'when the cache key exists' do
before do
- fake_repository.redis_set_cache.write(:letters, %w(b a c))
+ redis_set_cache.write(:letters, %w(b a c))
end
- it 'calls #include? on the set cache' do
- expect(fake_repository.redis_set_cache)
- .to receive(:include?).with(:letters, 'a').and_call_original
- expect(fake_repository.redis_set_cache)
- .to receive(:include?).with(:letters, 'd').and_call_original
+ it 'calls #try_include? on the set cache' do
+ expect(redis_set_cache).to receive(:try_include?).with(:letters, 'a').and_call_original
+ expect(redis_set_cache).to receive(:try_include?).with(:letters, 'd').and_call_original
expect(fake_repository.letters_include?('a')).to eq(true)
expect(fake_repository.letters_include?('d')).to eq(false)
end
it 'memoizes the result' do
- expect(fake_repository.redis_set_cache)
- .to receive(:include?).once.and_call_original
+ expect(redis_set_cache).to receive(:try_include?).once.and_call_original
expect(fake_repository.letters_include?('a')).to eq(true)
expect(fake_repository.letters_include?('a')).to eq(true)
- expect(fake_repository.redis_set_cache)
- .to receive(:include?).once.and_call_original
+ expect(redis_set_cache).to receive(:try_include?).once.and_call_original
expect(fake_repository.letters_include?('d')).to eq(false)
expect(fake_repository.letters_include?('d')).to eq(false)
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
index 07f4d7c462d..eaecbb0233d 100644
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -124,6 +124,18 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
end
end
+ describe '#search' do
+ subject do
+ cache.search(:foo, 'val*') do
+ %w[value helloworld notvalmatch]
+ end
+ end
+
+ it 'returns search pattern matches from the key' do
+ is_expected.to contain_exactly('value')
+ end
+ end
+
describe '#include?' do
it 'checks inclusion in the Redis set' do
cache.write(:foo, ['value'])
@@ -132,4 +144,15 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
expect(cache.include?(:foo, 'bar')).to be(false)
end
end
+
+ describe '#try_include?' do
+ it 'checks existence of the redis set and inclusion' do
+ expect(cache.try_include?(:foo, 'value')).to eq([false, false])
+
+ cache.write(:foo, ['value'])
+
+ expect(cache.try_include?(:foo, 'value')).to eq([true, true])
+ expect(cache.try_include?(:foo, 'bar')).to eq([false, true])
+ end
+ end
end
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index 63b2f3fc693..fbda9e6d0be 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -113,7 +113,7 @@ RSpec.describe Gitlab::Sanitizers::Exif do
it 'cleans only jpg/tiff images with the correct mime types' do
expect(sanitizer).not_to receive(:extra_tags)
- expect { subject }.to raise_error(RuntimeError, /File type text\/plain not supported/)
+ expect { subject }.to raise_error(RuntimeError, %r{File type text/plain not supported})
end
end
end
diff --git a/spec/lib/gitlab/search_context/builder_spec.rb b/spec/lib/gitlab/search_context/builder_spec.rb
index 5b4190fc67e..079477115bb 100644
--- a/spec/lib/gitlab/search_context/builder_spec.rb
+++ b/spec/lib/gitlab/search_context/builder_spec.rb
@@ -127,6 +127,35 @@ RSpec.describe Gitlab::SearchContext::Builder, type: :controller do
it { is_expected.to be_for_group }
it { is_expected.to be_search_context(group: group) }
+
+ context 'with group scope' do
+ let(:action_name) { '' }
+
+ before do
+ allow(controller).to receive(:controller_name).and_return('groups')
+ allow(controller).to receive(:action_name).and_return(action_name)
+ end
+
+ it 'returns nil without groups controller action' do
+ expect(subject.scope).to be(nil)
+ end
+
+ context 'when on issues scope' do
+ let(:action_name) { 'issues' }
+
+ it 'search context returns issues scope' do
+ expect(subject.scope).to be('issues')
+ end
+ end
+
+ context 'when on merge requests scope' do
+ let(:action_name) { 'merge_requests' }
+
+ it 'search context returns issues scope' do
+ expect(subject.scope).to be('merge_requests')
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
index 74834fb9014..43cbe71dd6b 100644
--- a/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
+++ b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
@@ -214,7 +214,7 @@ RSpec.describe Gitlab::SidekiqCluster::CLI do
expect(Gitlab::SidekiqCluster).not_to receive(:start)
expect { cli.run(%W(#{flag} unknown_field=chatops)) }
- .to raise_error(Gitlab::SidekiqConfig::CliMethods::QueryError)
+ .to raise_error(Gitlab::SidekiqConfig::WorkerMatcher::QueryError)
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
index 01e7c06249a..bc63289a344 100644
--- a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'fast_spec_helper'
-require 'rspec-parameterized'
RSpec.describe Gitlab::SidekiqConfig::CliMethods do
let(:dummy_root) { '/tmp/' }
@@ -122,10 +121,8 @@ RSpec.describe Gitlab::SidekiqConfig::CliMethods do
end
end
- describe '.query_workers' do
- using RSpec::Parameterized::TableSyntax
-
- let(:queues) do
+ describe '.query_queues' do
+ let(:worker_metadatas) do
[
{
name: 'a',
@@ -162,79 +159,16 @@ RSpec.describe Gitlab::SidekiqConfig::CliMethods do
]
end
- context 'with valid input' do
- where(:query, :selected_queues) do
- # feature_category
- 'feature_category=category_a' | %w(a a:2)
- 'feature_category=category_a,category_c' | %w(a a:2 c)
- 'feature_category=category_a|feature_category=category_c' | %w(a a:2 c)
- 'feature_category!=category_a' | %w(b c)
-
- # has_external_dependencies
- 'has_external_dependencies=true' | %w(b)
- 'has_external_dependencies=false' | %w(a a:2 c)
- 'has_external_dependencies=true,false' | %w(a a:2 b c)
- 'has_external_dependencies=true|has_external_dependencies=false' | %w(a a:2 b c)
- 'has_external_dependencies!=true' | %w(a a:2 c)
-
- # urgency
- 'urgency=high' | %w(a:2 b)
- 'urgency=low' | %w(a)
- 'urgency=high,low,throttled' | %w(a a:2 b c)
- 'urgency=low|urgency=throttled' | %w(a c)
- 'urgency!=high' | %w(a c)
-
- # name
- 'name=a' | %w(a)
- 'name=a,b' | %w(a b)
- 'name=a,a:2|name=b' | %w(a a:2 b)
- 'name!=a,a:2' | %w(b c)
-
- # resource_boundary
- 'resource_boundary=memory' | %w(b c)
- 'resource_boundary=memory,cpu' | %w(a b c)
- 'resource_boundary=memory|resource_boundary=cpu' | %w(a b c)
- 'resource_boundary!=memory,cpu' | %w(a:2)
-
- # tags
- 'tags=no_disk_io' | %w(a b)
- 'tags=no_disk_io,git_access' | %w(a a:2 b)
- 'tags=no_disk_io|tags=git_access' | %w(a a:2 b)
- 'tags=no_disk_io&tags=git_access' | %w(a)
- 'tags!=no_disk_io' | %w(a:2 c)
- 'tags!=no_disk_io,git_access' | %w(c)
- 'tags=unknown_tag' | []
- 'tags!=no_disk_io' | %w(a:2 c)
- 'tags!=no_disk_io,git_access' | %w(c)
- 'tags!=unknown_tag' | %w(a a:2 b c)
-
- # combinations
- 'feature_category=category_a&urgency=high' | %w(a:2)
- 'feature_category=category_a&urgency=high|feature_category=category_c' | %w(a:2 c)
- end
+ let(:worker_matcher) { double(:WorkerMatcher) }
+ let(:query) { 'feature_category=category_a,category_c' }
- with_them do
- it do
- expect(described_class.query_workers(query, queues))
- .to match_array(selected_queues)
- end
- end
+ before do
+ allow(::Gitlab::SidekiqConfig::WorkerMatcher).to receive(:new).with(query).and_return(worker_matcher)
+ allow(worker_matcher).to receive(:match?).and_return(true, true, false, true)
end
- context 'with invalid input' do
- where(:query, :error) do
- 'feature_category="category_a"' | described_class::InvalidTerm
- 'feature_category=' | described_class::InvalidTerm
- 'feature_category~category_a' | described_class::InvalidTerm
- 'worker_name=a' | described_class::UnknownPredicate
- end
-
- with_them do
- it do
- expect { described_class.query_workers(query, queues) }
- .to raise_error(error)
- end
- end
+ it 'returns the queue names of matched workers' do
+ expect(described_class.query_queues(query, worker_metadatas)).to match(%w(a a:2 c))
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb b/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb
new file mode 100644
index 00000000000..75e9c8c100b
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_config/worker_matcher_spec.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+RSpec.describe Gitlab::SidekiqConfig::WorkerMatcher do
+ describe '#match?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:worker_metadatas) do
+ [
+ {
+ name: 'a',
+ feature_category: :category_a,
+ has_external_dependencies: false,
+ urgency: :low,
+ resource_boundary: :cpu,
+ tags: [:no_disk_io, :git_access]
+ },
+ {
+ name: 'a:2',
+ feature_category: :category_a,
+ has_external_dependencies: false,
+ urgency: :high,
+ resource_boundary: :none,
+ tags: [:git_access]
+ },
+ {
+ name: 'b',
+ feature_category: :category_b,
+ has_external_dependencies: true,
+ urgency: :high,
+ resource_boundary: :memory,
+ tags: [:no_disk_io]
+ },
+ {
+ name: 'c',
+ feature_category: :category_c,
+ has_external_dependencies: false,
+ urgency: :throttled,
+ resource_boundary: :memory,
+ tags: []
+ }
+ ]
+ end
+
+ context 'with valid input' do
+ where(:query, :expected_metadatas) do
+ # feature_category
+ 'feature_category=category_a' | %w(a a:2)
+ 'feature_category=category_a,category_c' | %w(a a:2 c)
+ 'feature_category=category_a|feature_category=category_c' | %w(a a:2 c)
+ 'feature_category!=category_a' | %w(b c)
+
+ # has_external_dependencies
+ 'has_external_dependencies=true' | %w(b)
+ 'has_external_dependencies=false' | %w(a a:2 c)
+ 'has_external_dependencies=true,false' | %w(a a:2 b c)
+ 'has_external_dependencies=true|has_external_dependencies=false' | %w(a a:2 b c)
+ 'has_external_dependencies!=true' | %w(a a:2 c)
+
+ # urgency
+ 'urgency=high' | %w(a:2 b)
+ 'urgency=low' | %w(a)
+ 'urgency=high,low,throttled' | %w(a a:2 b c)
+ 'urgency=low|urgency=throttled' | %w(a c)
+ 'urgency!=high' | %w(a c)
+
+ # name
+ 'name=a' | %w(a)
+ 'name=a,b' | %w(a b)
+ 'name=a,a:2|name=b' | %w(a a:2 b)
+ 'name!=a,a:2' | %w(b c)
+
+ # resource_boundary
+ 'resource_boundary=memory' | %w(b c)
+ 'resource_boundary=memory,cpu' | %w(a b c)
+ 'resource_boundary=memory|resource_boundary=cpu' | %w(a b c)
+ 'resource_boundary!=memory,cpu' | %w(a:2)
+
+ # tags
+ 'tags=no_disk_io' | %w(a b)
+ 'tags=no_disk_io,git_access' | %w(a a:2 b)
+ 'tags=no_disk_io|tags=git_access' | %w(a a:2 b)
+ 'tags=no_disk_io&tags=git_access' | %w(a)
+ 'tags!=no_disk_io' | %w(a:2 c)
+ 'tags!=no_disk_io,git_access' | %w(c)
+ 'tags=unknown_tag' | []
+ 'tags!=no_disk_io' | %w(a:2 c)
+ 'tags!=no_disk_io,git_access' | %w(c)
+ 'tags!=unknown_tag' | %w(a a:2 b c)
+
+ # combinations
+ 'feature_category=category_a&urgency=high' | %w(a:2)
+ 'feature_category=category_a&urgency=high|feature_category=category_c' | %w(a:2 c)
+
+ # Match all
+ '*' | %w(a a:2 b c)
+ end
+
+ with_them do
+ it do
+ matched_metadatas = worker_metadatas.select do |metadata|
+ described_class.new(query).match?(metadata)
+ end
+ expect(matched_metadatas.map { |m| m[:name] }).to match_array(expected_metadatas)
+ end
+ end
+ end
+
+ context 'with invalid input' do
+ where(:query, :error) do
+ 'feature_category="category_a"' | described_class::InvalidTerm
+ 'feature_category=' | described_class::InvalidTerm
+ 'feature_category~category_a' | described_class::InvalidTerm
+ 'worker_name=a' | described_class::UnknownPredicate
+ end
+
+ with_them do
+ it do
+ worker_metadatas.each do |metadata|
+ expect { described_class.new(query).match?(metadata) }
+ .to raise_error(error)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 3e8e117ec71..537844df72f 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -10,80 +10,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
end
describe '#call', :request_store do
- let(:timestamp) { Time.iso8601('2018-01-01T12:00:00.000Z') }
- let(:created_at) { timestamp - 1.second }
- let(:scheduling_latency_s) { 1.0 }
-
- let(:job) do
- {
- "class" => "TestWorker",
- "args" => [1234, 'hello', { 'key' => 'value' }],
- "retry" => false,
- "queue" => "cronjob:test_queue",
- "queue_namespace" => "cronjob",
- "jid" => "da883554ee4fe414012f5f42",
- "created_at" => created_at.to_f,
- "enqueued_at" => created_at.to_f,
- "correlation_id" => 'cid',
- "error_message" => "wrong number of arguments (2 for 3)",
- "error_class" => "ArgumentError",
- "error_backtrace" => []
- }
- end
-
- let(:logger) { double }
- let(:clock_realtime_start) { 0.222222299 }
- let(:clock_realtime_end) { 1.333333799 }
- let(:clock_thread_cputime_start) { 0.222222299 }
- let(:clock_thread_cputime_end) { 1.333333799 }
- let(:start_payload) do
- job.except('error_backtrace', 'error_class', 'error_message').merge(
- 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start',
- 'job_status' => 'start',
- 'pid' => Process.pid,
- 'created_at' => created_at.to_f,
- 'enqueued_at' => created_at.to_f,
- 'scheduling_latency_s' => scheduling_latency_s,
- 'job_size_bytes' => be > 0
- )
- end
-
- let(:end_payload) do
- start_payload.merge(
- 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec',
- 'job_status' => 'done',
- 'duration_s' => 0.0,
- 'completed_at' => timestamp.to_f,
- 'cpu_s' => 1.111112,
- 'db_duration_s' => 0.0,
- 'db_cached_count' => 0,
- 'db_count' => 0,
- 'db_write_count' => 0
- )
- end
-
- let(:exception_payload) do
- end_payload.merge(
- 'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec',
- 'job_status' => 'fail',
- 'error_class' => 'ArgumentError',
- 'error_message' => 'Something went wrong',
- 'error_backtrace' => be_a(Array).and(be_present)
- )
- end
-
- before do
- allow(Sidekiq).to receive(:logger).and_return(logger)
-
- allow(subject).to receive(:current_time).and_return(timestamp.to_f)
-
- allow(Process).to receive(:clock_gettime).with(Process::CLOCK_REALTIME, :float_second)
- .and_return(clock_realtime_start, clock_realtime_end)
- allow(Process).to receive(:clock_gettime).with(Process::CLOCK_THREAD_CPUTIME_ID, :float_second)
- .and_return(clock_thread_cputime_start, clock_thread_cputime_end)
- end
-
- subject { described_class.new }
+ include_context 'structured_logger'
context 'with SIDEKIQ_LOG_ARGUMENTS enabled' do
before do
@@ -283,14 +210,19 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
end_payload.merge(timing_data.stringify_keys)
end
- it 'logs with Gitaly and Rugged timing data' do
+ before do
+ allow(::Gitlab::InstrumentationHelper).to receive(:add_instrumentation_data).and_wrap_original do |method, values|
+ method.call(values)
+ values.merge!(timing_data)
+ end
+ end
+
+ it 'logs with Gitaly and Rugged timing data', :aggregate_failures do
Timecop.freeze(timestamp) do
expect(logger).to receive(:info).with(start_payload).ordered
expect(logger).to receive(:info).with(expected_end_payload).ordered
- call_subject(job, 'test_queue') do
- job.merge!(timing_data)
- end
+ call_subject(job, 'test_queue') { }
end
end
end
@@ -361,15 +293,6 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
end
end
end
-
- def call_subject(job, queue)
- # This structured logger strongly depends on execution of `InstrumentationLogger`
- subject.call(job, queue) do
- ::Gitlab::SidekiqMiddleware::InstrumentationLogger.new.call('worker', job, queue) do
- yield
- end
- end
- end
end
describe '#add_time_keys!' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
index 3ba08455d01..9d5d5f28eab 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/client_spec.rb
@@ -74,9 +74,9 @@ RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Client, :request_store do
end
end
- context 'admin mode feature disabled' do
+ context 'admin mode setting disabled' do
before do
- stub_feature_flags(user_mode_in_session: false)
+ stub_application_setting(admin_mode: false)
end
it 'yields block' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
index e8322b11875..3ab1a9cd2f4 100644
--- a/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/admin_mode/server_spec.rb
@@ -52,9 +52,9 @@ RSpec.describe Gitlab::SidekiqMiddleware::AdminMode::Server, :request_store do
end
end
- context 'admin mode feature disabled' do
+ context 'admin mode setting disabled' do
before do
- stub_feature_flags(user_mode_in_session: false)
+ stub_application_setting(admin_mode: false)
end
it 'yields block' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
index e2b36125b4e..82ca84f0697 100644
--- a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
@@ -3,156 +3,33 @@
require 'spec_helper'
RSpec.describe Gitlab::SidekiqMiddleware::ClientMetrics do
- context "with worker attribution" do
- subject { described_class.new }
+ shared_examples "a metrics middleware" do
+ context "with mocked prometheus" do
+ let(:enqueued_jobs_metric) { double('enqueued jobs metric', increment: true) }
- let(:queue) { :test }
- let(:worker_class) { worker.class }
- let(:job) { {} }
- let(:default_labels) do
- { queue: queue.to_s,
- worker: worker_class.to_s,
- boundary: "",
- external_dependencies: "no",
- feature_category: "",
- urgency: "low" }
- end
-
- shared_examples "a metrics client middleware" do
- context "with mocked prometheus" do
- let(:enqueued_jobs_metric) { double('enqueued jobs metric', increment: true) }
-
- before do
- allow(Gitlab::Metrics).to receive(:counter).with(described_class::ENQUEUED, anything).and_return(enqueued_jobs_metric)
- end
-
- describe '#call' do
- it 'yields block' do
- expect { |b| subject.call(worker_class, job, :test, double, &b) }.to yield_control.once
- end
-
- it 'increments enqueued jobs metric with correct labels when worker is a string of the class' do
- expect(enqueued_jobs_metric).to receive(:increment).with(labels, 1)
-
- subject.call(worker_class.to_s, job, :test, double) { nil }
- end
-
- it 'increments enqueued jobs metric with correct labels' do
- expect(enqueued_jobs_metric).to receive(:increment).with(labels, 1)
-
- subject.call(worker_class, job, :test, double) { nil }
- end
- end
- end
- end
-
- context "when workers are not attributed" do
before do
- stub_const('TestNonAttributedWorker', Class.new)
- TestNonAttributedWorker.class_eval do
- include Sidekiq::Worker
- end
- end
-
- it_behaves_like "a metrics client middleware" do
- let(:worker) { TestNonAttributedWorker.new }
- let(:labels) { default_labels.merge(urgency: "") }
- end
- end
-
- context "when a worker is wrapped into ActiveJob" do
- before do
- stub_const('TestWrappedWorker', Class.new)
- TestWrappedWorker.class_eval do
- include Sidekiq::Worker
- end
- end
-
- it_behaves_like "a metrics client middleware" do
- let(:job) do
- {
- "class" => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper,
- "wrapped" => TestWrappedWorker
- }
- end
-
- let(:worker) { TestWrappedWorker.new }
- let(:labels) { default_labels.merge(urgency: "") }
- end
- end
-
- context "when workers are attributed" do
- def create_attributed_worker_class(urgency, external_dependencies, resource_boundary, category)
- klass = Class.new do
- include Sidekiq::Worker
- include WorkerAttributes
-
- urgency urgency if urgency
- worker_has_external_dependencies! if external_dependencies
- worker_resource_boundary resource_boundary unless resource_boundary == :unknown
- feature_category category unless category.nil?
- end
- stub_const("TestAttributedWorker", klass)
- end
-
- let(:urgency) { nil }
- let(:external_dependencies) { false }
- let(:resource_boundary) { :unknown }
- let(:feature_category) { nil }
- let(:worker_class) { create_attributed_worker_class(urgency, external_dependencies, resource_boundary, feature_category) }
- let(:worker) { worker_class.new }
-
- context "high urgency" do
- it_behaves_like "a metrics client middleware" do
- let(:urgency) { :high }
- let(:labels) { default_labels.merge(urgency: "high") }
- end
+ allow(Gitlab::Metrics).to receive(:counter).with(described_class::ENQUEUED, anything).and_return(enqueued_jobs_metric)
end
- context "no urgency" do
- it_behaves_like "a metrics client middleware" do
- let(:urgency) { :throttled }
- let(:labels) { default_labels.merge(urgency: "throttled") }
+ describe '#call' do
+ it 'yields block' do
+ expect { |b| subject.call(worker_class, job, :test, double, &b) }.to yield_control.once
end
- end
- context "external dependencies" do
- it_behaves_like "a metrics client middleware" do
- let(:external_dependencies) { true }
- let(:labels) { default_labels.merge(external_dependencies: "yes") }
- end
- end
+ it 'increments enqueued jobs metric with correct labels when worker is a string of the class' do
+ expect(enqueued_jobs_metric).to receive(:increment).with(labels, 1)
- context "cpu boundary" do
- it_behaves_like "a metrics client middleware" do
- let(:resource_boundary) { :cpu }
- let(:labels) { default_labels.merge(boundary: "cpu") }
+ subject.call(worker_class.to_s, job, :test, double) { nil }
end
- end
- context "memory boundary" do
- it_behaves_like "a metrics client middleware" do
- let(:resource_boundary) { :memory }
- let(:labels) { default_labels.merge(boundary: "memory") }
- end
- end
+ it 'increments enqueued jobs metric with correct labels' do
+ expect(enqueued_jobs_metric).to receive(:increment).with(labels, 1)
- context "feature category" do
- it_behaves_like "a metrics client middleware" do
- let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(feature_category: "authentication") }
- end
- end
-
- context "combined" do
- it_behaves_like "a metrics client middleware" do
- let(:urgency) { :high }
- let(:external_dependencies) { true }
- let(:resource_boundary) { :cpu }
- let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(urgency: "high", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
+ subject.call(worker_class, job, :test, double) { nil }
end
end
end
end
+
+ it_behaves_like 'metrics middleware with worker attribution'
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb b/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb
new file mode 100644
index 00000000000..eb9ba50cdcd
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::InstrumentationLogger do
+ let(:job) { { 'jid' => 123 } }
+ let(:queue) { 'test_queue' }
+ let(:worker) do
+ Class.new do
+ def self.name
+ 'TestDWorker'
+ end
+
+ include ApplicationWorker
+
+ def perform(*args)
+ end
+ end
+ end
+
+ subject { described_class.new }
+
+ before do
+ stub_const('TestWorker', worker)
+ end
+
+ describe '.keys' do
+ it 'returns all available payload keys' do
+ expected_keys = [
+ :cpu_s,
+ :gitaly_calls,
+ :gitaly_duration_s,
+ :rugged_calls,
+ :rugged_duration_s,
+ :elasticsearch_calls,
+ :elasticsearch_duration_s,
+ :elasticsearch_timed_out_count,
+ :mem_objects,
+ :mem_bytes,
+ :mem_mallocs,
+ :redis_calls,
+ :redis_duration_s,
+ :redis_read_bytes,
+ :redis_write_bytes,
+ :redis_action_cable_calls,
+ :redis_action_cable_duration_s,
+ :redis_action_cable_read_bytes,
+ :redis_action_cable_write_bytes,
+ :redis_cache_calls,
+ :redis_cache_duration_s,
+ :redis_cache_read_bytes,
+ :redis_cache_write_bytes,
+ :redis_queues_calls,
+ :redis_queues_duration_s,
+ :redis_queues_read_bytes,
+ :redis_queues_write_bytes,
+ :redis_shared_state_calls,
+ :redis_shared_state_duration_s,
+ :redis_shared_state_read_bytes,
+ :redis_shared_state_write_bytes,
+ :db_count,
+ :db_write_count,
+ :db_cached_count,
+ :external_http_count,
+ :external_http_duration_s,
+ :rack_attack_redis_count,
+ :rack_attack_redis_duration_s
+ ]
+
+ expect(described_class.keys).to include(*expected_keys)
+ end
+ end
+
+ describe '#call', :request_store do
+ let(:instrumentation_values) do
+ {
+ cpu_s: 10,
+ unknown_attribute: 123,
+ db_count: 0,
+ db_cached_count: 0,
+ db_write_count: 0,
+ gitaly_calls: 0,
+ redis_calls: 0
+ }
+ end
+
+ before do
+ allow(::Gitlab::InstrumentationHelper).to receive(:add_instrumentation_data) do |values|
+ values.merge!(instrumentation_values)
+ end
+ end
+
+ it 'merges correct instrumentation data in the job' do
+ expect { |b| subject.call(worker, job, queue, &b) }.to yield_control
+
+ expected_values = instrumentation_values.except(:unknown_attribute)
+
+ expect(job[:instrumentation]).to eq(expected_values)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
index 71f4f2a3b64..95be76ce351 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -4,296 +4,108 @@ require 'spec_helper'
# rubocop: disable RSpec/MultipleMemoizedHelpers
RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
- context "with worker attribution" do
- subject { described_class.new }
+ shared_examples "a metrics middleware" do
+ context "with mocked prometheus" do
+ include_context 'server metrics with mocked prometheus'
- let(:queue) { :test }
- let(:worker_class) { worker.class }
- let(:job) { {} }
- let(:job_status) { :done }
- let(:labels_with_job_status) { labels.merge(job_status: job_status.to_s) }
- let(:default_labels) do
- { queue: queue.to_s,
- worker: worker_class.to_s,
- boundary: "",
- external_dependencies: "no",
- feature_category: "",
- urgency: "low" }
- end
-
- shared_examples "a metrics middleware" do
- context "with mocked prometheus" do
- let(:concurrency_metric) { double('concurrency metric') }
-
- let(:queue_duration_seconds) { double('queue duration seconds metric') }
- let(:completion_seconds_metric) { double('completion seconds metric') }
- let(:user_execution_seconds_metric) { double('user execution seconds metric') }
- let(:db_seconds_metric) { double('db seconds metric') }
- let(:gitaly_seconds_metric) { double('gitaly seconds metric') }
- let(:failed_total_metric) { double('failed total metric') }
- let(:retried_total_metric) { double('retried total metric') }
- let(:redis_requests_total) { double('redis calls total metric') }
- let(:running_jobs_metric) { double('running jobs metric') }
- let(:redis_seconds_metric) { double('redis seconds metric') }
- let(:elasticsearch_seconds_metric) { double('elasticsearch seconds metric') }
- let(:elasticsearch_requests_total) { double('elasticsearch calls total metric') }
+ describe '#initialize' do
+ it 'sets concurrency metrics' do
+ expect(concurrency_metric).to receive(:set).with({}, Sidekiq.options[:concurrency].to_i)
- before do
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_queue_duration_seconds, anything, anything, anything).and_return(queue_duration_seconds)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything, anything).and_return(completion_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_cpu_seconds, anything, anything, anything).and_return(user_execution_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_db_seconds, anything, anything, anything).and_return(db_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_gitaly_seconds, anything, anything, anything).and_return(gitaly_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_redis_requests_duration_seconds, anything, anything, anything).and_return(redis_seconds_metric)
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_elasticsearch_requests_duration_seconds, anything, anything, anything).and_return(elasticsearch_seconds_metric)
- allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric)
- allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric)
- allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_redis_requests_total, anything).and_return(redis_requests_total)
- allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_elasticsearch_requests_total, anything).and_return(elasticsearch_requests_total)
- allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :all).and_return(running_jobs_metric)
- allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_concurrency, anything, {}, :all).and_return(concurrency_metric)
-
- allow(concurrency_metric).to receive(:set)
+ subject
end
+ end
- describe '#initialize' do
- it 'sets concurrency metrics' do
- expect(concurrency_metric).to receive(:set).with({}, Sidekiq.options[:concurrency].to_i)
+ describe '#call' do
+ include_context 'server metrics call'
- subject
- end
+ it 'yields block' do
+ expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once
end
- describe '#call' do
- let(:thread_cputime_before) { 1 }
- let(:thread_cputime_after) { 2 }
- let(:thread_cputime_duration) { thread_cputime_after - thread_cputime_before }
-
- let(:monotonic_time_before) { 11 }
- let(:monotonic_time_after) { 20 }
- let(:monotonic_time_duration) { monotonic_time_after - monotonic_time_before }
-
- let(:queue_duration_for_job) { 0.01 }
-
- let(:db_duration) { 3 }
- let(:gitaly_duration) { 4 }
-
- let(:redis_calls) { 2 }
- let(:redis_duration) { 0.01 }
-
- let(:elasticsearch_calls) { 8 }
- let(:elasticsearch_duration) { 0.54 }
-
- before do
- allow(subject).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after)
- allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
- allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job)
- allow(ActiveRecord::LogSubscriber).to receive(:runtime).and_return(db_duration * 1000)
-
- job[:gitaly_duration_s] = gitaly_duration
- job[:redis_calls] = redis_calls
- job[:redis_duration_s] = redis_duration
-
- job[:elasticsearch_calls] = elasticsearch_calls
- job[:elasticsearch_duration_s] = elasticsearch_duration
-
- allow(running_jobs_metric).to receive(:increment)
- allow(redis_requests_total).to receive(:increment)
- allow(elasticsearch_requests_total).to receive(:increment)
- allow(queue_duration_seconds).to receive(:observe)
- allow(user_execution_seconds_metric).to receive(:observe)
- allow(db_seconds_metric).to receive(:observe)
- allow(gitaly_seconds_metric).to receive(:observe)
- allow(completion_seconds_metric).to receive(:observe)
- allow(redis_seconds_metric).to receive(:observe)
- allow(elasticsearch_seconds_metric).to receive(:observe)
+ it 'calls BackgroundTransaction' do
+ expect_next_instance_of(Gitlab::Metrics::BackgroundTransaction) do |instance|
+ expect(instance).to receive(:run)
end
- it 'yields block' do
- expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once
- end
+ subject.call(worker, job, :test) {}
+ end
- it 'calls BackgroundTransaction' do
- expect_next_instance_of(Gitlab::Metrics::BackgroundTransaction) do |instance|
- expect(instance).to receive(:run)
- end
+ it 'sets queue specific metrics' do
+ expect(running_jobs_metric).to receive(:increment).with(labels, -1)
+ expect(running_jobs_metric).to receive(:increment).with(labels, 1)
+ expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job
+ expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration)
+ expect(db_seconds_metric).to receive(:observe).with(labels_with_job_status, db_duration)
+ expect(gitaly_seconds_metric).to receive(:observe).with(labels_with_job_status, gitaly_duration)
+ expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration)
+ expect(redis_seconds_metric).to receive(:observe).with(labels_with_job_status, redis_duration)
+ expect(elasticsearch_seconds_metric).to receive(:observe).with(labels_with_job_status, elasticsearch_duration)
+ expect(redis_requests_total).to receive(:increment).with(labels_with_job_status, redis_calls)
+ expect(elasticsearch_requests_total).to receive(:increment).with(labels_with_job_status, elasticsearch_calls)
+
+ subject.call(worker, job, :test) { nil }
+ end
- subject.call(worker, job, :test) {}
- end
+ it 'sets the thread name if it was nil' do
+ allow(Thread.current).to receive(:name).and_return(nil)
+ expect(Thread.current).to receive(:name=).with(Gitlab::Metrics::Samplers::ThreadsSampler::SIDEKIQ_WORKER_THREAD_NAME)
- it 'sets queue specific metrics' do
- expect(running_jobs_metric).to receive(:increment).with(labels, -1)
- expect(running_jobs_metric).to receive(:increment).with(labels, 1)
- expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job
- expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration)
- expect(db_seconds_metric).to receive(:observe).with(labels_with_job_status, db_duration)
- expect(gitaly_seconds_metric).to receive(:observe).with(labels_with_job_status, gitaly_duration)
- expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration)
- expect(redis_seconds_metric).to receive(:observe).with(labels_with_job_status, redis_duration)
- expect(elasticsearch_seconds_metric).to receive(:observe).with(labels_with_job_status, elasticsearch_duration)
- expect(redis_requests_total).to receive(:increment).with(labels_with_job_status, redis_calls)
- expect(elasticsearch_requests_total).to receive(:increment).with(labels_with_job_status, elasticsearch_calls)
+ subject.call(worker, job, :test) { nil }
+ end
- subject.call(worker, job, :test) { nil }
- end
+ context 'when job_duration is not available' do
+ let(:queue_duration_for_job) { nil }
- it 'sets the thread name if it was nil' do
- allow(Thread.current).to receive(:name).and_return(nil)
- expect(Thread.current).to receive(:name=).with(Gitlab::Metrics::Samplers::ThreadsSampler::SIDEKIQ_WORKER_THREAD_NAME)
+ it 'does not set the queue_duration_seconds histogram' do
+ expect(queue_duration_seconds).not_to receive(:observe)
subject.call(worker, job, :test) { nil }
end
+ end
- context 'when job_duration is not available' do
- let(:queue_duration_for_job) { nil }
-
- it 'does not set the queue_duration_seconds histogram' do
- expect(queue_duration_seconds).not_to receive(:observe)
-
- subject.call(worker, job, :test) { nil }
- end
- end
-
- context 'when error is raised' do
- let(:job_status) { :fail }
-
- it 'sets sidekiq_jobs_failed_total and reraises' do
- expect(failed_total_metric).to receive(:increment).with(labels, 1)
-
- expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
- end
- end
-
- context 'when job is retried' do
- let(:job) { { 'retry_count' => 1 } }
+ context 'when error is raised' do
+ let(:job_status) { :fail }
- it 'sets sidekiq_jobs_retried_total metric' do
- expect(retried_total_metric).to receive(:increment)
+ it 'sets sidekiq_jobs_failed_total and reraises' do
+ expect(failed_total_metric).to receive(:increment).with(labels, 1)
- subject.call(worker, job, :test) { nil }
- end
+ expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
end
end
- end
- context "with prometheus integrated" do
- describe '#call' do
- it 'yields block' do
- expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once
- end
+ context 'when job is retried' do
+ let(:job) { { 'retry_count' => 1 } }
- context 'when error is raised' do
- let(:job_status) { :fail }
+ it 'sets sidekiq_jobs_retried_total metric' do
+ expect(retried_total_metric).to receive(:increment)
- it 'sets sidekiq_jobs_failed_total and reraises' do
- expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
- end
+ subject.call(worker, job, :test) { nil }
end
end
end
end
- context "when workers are not attributed" do
- before do
- stub_const('TestNonAttributedWorker', Class.new)
- TestNonAttributedWorker.class_eval do
- include Sidekiq::Worker
+ context "with prometheus integrated" do
+ describe '#call' do
+ it 'yields block' do
+ expect { |b| subject.call(worker, job, :test, &b) }.to yield_control.once
end
- end
- let(:worker) { TestNonAttributedWorker.new }
- let(:labels) { default_labels.merge(urgency: "") }
+ context 'when error is raised' do
+ let(:job_status) { :fail }
- it_behaves_like "a metrics middleware"
- end
-
- context "when a worker is wrapped into ActiveJob" do
- before do
- stub_const('TestWrappedWorker', Class.new)
- TestWrappedWorker.class_eval do
- include Sidekiq::Worker
+ it 'sets sidekiq_jobs_failed_total and reraises' do
+ expect { subject.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed")
+ end
end
end
-
- let(:job) do
- {
- "class" => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper,
- "wrapped" => TestWrappedWorker
- }
- end
-
- let(:worker) { TestWrappedWorker.new }
- let(:worker_class) { TestWrappedWorker }
- let(:labels) { default_labels.merge(urgency: "") }
-
- it_behaves_like "a metrics middleware"
end
+ end
- context "when workers are attributed" do
- def create_attributed_worker_class(urgency, external_dependencies, resource_boundary, category)
- Class.new do
- include Sidekiq::Worker
- include WorkerAttributes
-
- urgency urgency if urgency
- worker_has_external_dependencies! if external_dependencies
- worker_resource_boundary resource_boundary unless resource_boundary == :unknown
- feature_category category unless category.nil?
- end
- end
-
- let(:urgency) { nil }
- let(:external_dependencies) { false }
- let(:resource_boundary) { :unknown }
- let(:feature_category) { nil }
- let(:worker_class) { create_attributed_worker_class(urgency, external_dependencies, resource_boundary, feature_category) }
- let(:worker) { worker_class.new }
-
- context "high urgency" do
- let(:urgency) { :high }
- let(:labels) { default_labels.merge(urgency: "high") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "external dependencies" do
- let(:external_dependencies) { true }
- let(:labels) { default_labels.merge(external_dependencies: "yes") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "cpu boundary" do
- let(:resource_boundary) { :cpu }
- let(:labels) { default_labels.merge(boundary: "cpu") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "memory boundary" do
- let(:resource_boundary) { :memory }
- let(:labels) { default_labels.merge(boundary: "memory") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "feature category" do
- let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(feature_category: "authentication") }
-
- it_behaves_like "a metrics middleware"
- end
-
- context "combined" do
- let(:urgency) { :throttled }
- let(:external_dependencies) { true }
- let(:resource_boundary) { :cpu }
- let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(urgency: "throttled", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
-
- it_behaves_like "a metrics middleware"
- end
- end
+ it_behaves_like 'metrics middleware with worker attribution' do
+ let(:job_status) { :done }
+ let(:labels_with_job_status) { labels.merge(job_status: job_status.to_s) }
end
end
# rubocop: enable RSpec/MultipleMemoizedHelpers
diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
index ca473462d2e..f736a7db774 100644
--- a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Server do
worker_context user: nil
def perform(identifier, *args)
- self.class.contexts.merge!(identifier => Labkit::Context.current.to_h)
+ self.class.contexts.merge!(identifier => Gitlab::ApplicationContext.current)
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb
index 755f6004e52..0efdef0c999 100644
--- a/spec/lib/gitlab/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb
@@ -69,11 +69,13 @@ RSpec.describe Gitlab::SidekiqMiddleware do
shared_examples "a server middleware chain" do
it "passes through the right server middlewares" do
enabled_sidekiq_middlewares.each do |middleware|
- expect_any_instance_of(middleware).to receive(:call).with(*middleware_expected_args).once.and_call_original
+ expect_next_instance_of(middleware) do |middleware_instance|
+ expect(middleware_instance).to receive(:call).with(*middleware_expected_args).once.and_call_original
+ end
end
disabled_sidekiq_middlewares.each do |middleware|
- expect_any_instance_of(middleware).not_to receive(:call)
+ expect(middleware).not_to receive(:new)
end
worker_class.perform_async(*job_args)
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
index 109b4b8fee1..690ffb15a5d 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_comment_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::SlashCommands::Presenters::IssueComment do
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:note) { create(:note, project: project, noteable: issue) }
+
let(:author) { note.author }
describe '#present' do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
index a4d8e3957cf..7b3440b40a7 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::SlashCommands::Presenters::IssueMove do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:other_project) { create(:project) }
let_it_be(:old_issue, reload: true) { create(:issue, project: project) }
+
let(:new_issue) { Issues::MoveService.new(project, user).execute(old_issue, other_project) }
let(:attachment) { subject[:attachments].first }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
index 03a94ea5e29..21a983090fb 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
@@ -1,19 +1,22 @@
# frozen_string_literal: true
-
require 'spec_helper'
RSpec.describe Gitlab::SlashCommands::Presenters::IssueNew do
+ include Gitlab::Routing
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
- let(:attachment) { subject[:attachments].first }
subject { described_class.new(issue).present }
it { is_expected.to be_a(Hash) }
it 'shows the issue' do
- expect(subject[:response_type]).to be(:in_channel)
- expect(subject).to have_key(:attachments)
- expect(attachment[:title]).to start_with(issue.title)
+ expected_text = "I created an issue on <#{url_for(issue.author)}|#{issue.author.to_reference}>'s behalf: *<#{project_issue_url(issue.project, issue)}|#{issue.to_reference}>* in <#{project.web_url}|#{project.full_name}>"
+
+ expect(subject).to eq(
+ response_type: :in_channel,
+ status: 200,
+ text: expected_text
+ )
end
end
diff --git a/spec/lib/gitlab/slash_commands/run_spec.rb b/spec/lib/gitlab/slash_commands/run_spec.rb
index c9ff580d586..9d204228d21 100644
--- a/spec/lib/gitlab/slash_commands/run_spec.rb
+++ b/spec/lib/gitlab/slash_commands/run_spec.rb
@@ -3,6 +3,26 @@
require 'spec_helper'
RSpec.describe Gitlab::SlashCommands::Run do
+ describe '.match' do
+ it 'returns true for a run command' do
+ expect(described_class.match('run foo')).to be_an_instance_of(MatchData)
+ end
+
+ it 'returns true for a run command with arguments' do
+ expect(described_class.match('run foo bar baz'))
+ .to be_an_instance_of(MatchData)
+ end
+
+ it 'returns true for a command containing newlines' do
+ expect(described_class.match("run foo\nbar\nbaz"))
+ .to be_an_instance_of(MatchData)
+ end
+
+ it 'returns false for an unrelated command' do
+ expect(described_class.match('foo bar')).to be_nil
+ end
+ end
+
describe '.available?' do
it 'returns true when builds are enabled for the project' do
project = double(:project, builds_enabled?: true)
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index 2177b2be6d6..fc342b7e9b1 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Gitlab::SnippetSearchResults do
include SearchHelpers
let_it_be(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
+
let(:results) { described_class.new(snippet.author, 'foo') }
describe '#snippet_titles_count' do
diff --git a/spec/lib/gitlab/sourcegraph_spec.rb b/spec/lib/gitlab/sourcegraph_spec.rb
index ad947475f06..6bebd1ca3e6 100644
--- a/spec/lib/gitlab/sourcegraph_spec.rb
+++ b/spec/lib/gitlab/sourcegraph_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Sourcegraph do
let_it_be(:user) { create(:user) }
+
let(:feature_scope) { true }
before do
diff --git a/spec/lib/gitlab/sql/cte_spec.rb b/spec/lib/gitlab/sql/cte_spec.rb
index fdc150cd4b9..4cf94f4dcab 100644
--- a/spec/lib/gitlab/sql/cte_spec.rb
+++ b/spec/lib/gitlab/sql/cte_spec.rb
@@ -14,7 +14,14 @@ RSpec.describe Gitlab::SQL::CTE do
relation.except(:order).to_sql
end
- expect(sql).to eq("#{name} AS (#{sql1})")
+ expected = [
+ "#{name} AS ",
+ Gitlab::Database::AsWithMaterialized.materialized_if_supported,
+ (' ' unless Gitlab::Database::AsWithMaterialized.materialized_if_supported.blank?),
+ "(#{sql1})"
+ ].join
+
+ expect(sql).to eq(expected)
end
end
@@ -41,4 +48,15 @@ RSpec.describe Gitlab::SQL::CTE do
expect(relation.to_a).to eq(User.where(id: user.id).to_a)
end
end
+
+ it_behaves_like 'CTE with MATERIALIZED keyword examples' do
+ let(:expected_query_block_with_materialized) { 'WITH "some_cte" AS MATERIALIZED (' }
+ let(:expected_query_block_without_materialized) { 'WITH "some_cte" AS (' }
+
+ let(:query) do
+ cte = described_class.new(:some_cte, User.active, **options)
+
+ User.with(cte.to_arel).to_sql
+ end
+ end
end
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
index 02611620989..edcacd404c2 100644
--- a/spec/lib/gitlab/sql/recursive_cte_spec.rb
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -57,4 +57,17 @@ RSpec.describe Gitlab::SQL::RecursiveCTE do
expect(relation.to_a).to eq(User.where(id: user.id).to_a)
end
end
+
+ it_behaves_like 'CTE with MATERIALIZED keyword examples' do
+ # MATERIALIZED keyword is not needed for recursive queries
+ let(:expected_query_block_with_materialized) { 'WITH RECURSIVE "some_cte" AS (' }
+ let(:expected_query_block_without_materialized) { 'WITH RECURSIVE "some_cte" AS (' }
+
+ let(:query) do
+ recursive_cte = described_class.new(:some_cte)
+ recursive_cte << User.active
+
+ User.with.recursive(recursive_cte.to_arel).to_sql
+ end
+ end
end
diff --git a/spec/lib/gitlab/subscription_portal_spec.rb b/spec/lib/gitlab/subscription_portal_spec.rb
index 351af3c07d2..ad1affdac0b 100644
--- a/spec/lib/gitlab/subscription_portal_spec.rb
+++ b/spec/lib/gitlab/subscription_portal_spec.rb
@@ -3,39 +3,41 @@
require 'spec_helper'
RSpec.describe ::Gitlab::SubscriptionPortal do
- describe '.default_subscriptions_url' do
- subject { described_class.default_subscriptions_url }
-
- context 'on non test and non dev environments' do
- before do
- allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
- allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
+ unless Gitlab.jh?
+ describe '.default_subscriptions_url' do
+ subject { described_class.default_subscriptions_url }
+
+ context 'on non test and non dev environments' do
+ before do
+ allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
+ allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
+ end
+
+ it 'returns production subscriptions app URL' do
+ is_expected.to eq('https://customers.gitlab.com')
+ end
end
- it 'returns production subscriptions app URL' do
- is_expected.to eq('https://customers.gitlab.com')
- end
- end
+ context 'on dev environment' do
+ before do
+ allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
+ allow(Rails).to receive_message_chain(:env, :development?).and_return(true)
+ end
- context 'on dev environment' do
- before do
- allow(Rails).to receive_message_chain(:env, :test?).and_return(false)
- allow(Rails).to receive_message_chain(:env, :development?).and_return(true)
+ it 'returns staging subscriptions app url' do
+ is_expected.to eq('https://customers.stg.gitlab.com')
+ end
end
- it 'returns staging subscriptions app url' do
- is_expected.to eq('https://customers.stg.gitlab.com')
- end
- end
-
- context 'on test environment' do
- before do
- allow(Rails).to receive_message_chain(:env, :test?).and_return(true)
- allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
- end
+ context 'on test environment' do
+ before do
+ allow(Rails).to receive_message_chain(:env, :test?).and_return(true)
+ allow(Rails).to receive_message_chain(:env, :development?).and_return(false)
+ end
- it 'returns staging subscriptions app url' do
- is_expected.to eq('https://customers.stg.gitlab.com')
+ it 'returns staging subscriptions app url' do
+ is_expected.to eq('https://customers.stg.gitlab.com')
+ end
end
end
end
diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
index 05f351be702..793ad1c1959 100644
--- a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
+++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Template::Finders::RepoTemplateFinder do
let_it_be(:project) { create(:project, :repository) }
+
let(:categories) { { 'HTML' => 'html' } }
subject(:finder) { described_class.new(project, 'files/', '.html', categories) }
diff --git a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
index 0e8647ad78a..65597e6568d 100644
--- a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
+++ b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb
@@ -41,21 +41,6 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow do
.with('category', 'action', 'label', 'property', 1.5, nil, (Time.now.to_f * 1000).to_i)
end
end
-
- describe '#self_describing_event' do
- it 'sends event to tracker' do
- allow(tracker).to receive(:track_self_describing_event).and_call_original
-
- subject.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' })
-
- expect(tracker).to have_received(:track_self_describing_event) do |event, context, timestamp|
- expect(event.to_json[:schema]).to eq('iglu:com.gitlab/foo/jsonschema/1-0-0')
- expect(event.to_json[:data]).to eq(foo: 'bar')
- expect(context).to eq(nil)
- expect(timestamp).to eq((Time.now.to_f * 1000).to_i)
- end
- end
- end
end
context 'when snowplow is not enabled' do
@@ -66,13 +51,5 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow do
subject.event('category', 'action', label: 'label', property: 'property', value: 1.5)
end
end
-
- describe '#self_describing_event' do
- it 'does not send event to tracker' do
- expect_any_instance_of(SnowplowTracker::Tracker).not_to receive(:track_self_describing_event)
-
- subject.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' })
- end
- end
end
end
diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb
index 561edbd38f8..dacd08cf12b 100644
--- a/spec/lib/gitlab/tracking/standard_context_spec.rb
+++ b/spec/lib/gitlab/tracking/standard_context_spec.rb
@@ -58,10 +58,16 @@ RSpec.describe Gitlab::Tracking::StandardContext do
end
context 'with extra data' do
- subject { described_class.new(foo: 'bar') }
+ subject { described_class.new(extra_key_1: 'extra value 1', extra_key_2: 'extra value 2') }
- it 'creates a Snowplow context with the given data' do
- expect(snowplow_context.to_json.dig(:data, :foo)).to eq('bar')
+ it 'includes extra data in `extra` hash' do
+ expect(snowplow_context.to_json.dig(:data, :extra)).to eq(extra_key_1: 'extra value 1', extra_key_2: 'extra value 2')
+ end
+ end
+
+ context 'without extra data' do
+ it 'contains an empty `extra` hash' do
+ expect(snowplow_context.to_json.dig(:data, :extra)).to be_empty
end
end
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index ac052bd7a80..4d856205609 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -36,12 +36,12 @@ RSpec.describe Gitlab::Tracking do
end
describe '.event' do
- before do
- allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow).to receive(:event)
- allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
- end
-
shared_examples 'delegates to destination' do |klass|
+ before do
+ allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow).to receive(:event)
+ allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event)
+ end
+
it "delegates to #{klass} destination" do
other_context = double(:context)
@@ -51,7 +51,7 @@ RSpec.describe Gitlab::Tracking do
expect(Gitlab::Tracking::StandardContext)
.to receive(:new)
- .with(project: project, user: user, namespace: namespace)
+ .with(project: project, user: user, namespace: namespace, extra_key_1: 'extra value 1', extra_key_2: 'extra value 2')
.and_call_original
expect_any_instance_of(klass).to receive(:event) do |_, category, action, args|
@@ -66,21 +66,21 @@ RSpec.describe Gitlab::Tracking do
end
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5,
- context: [other_context], project: project, user: user, namespace: namespace)
+ context: [other_context], project: project, user: user, namespace: namespace,
+ extra_key_1: 'extra value 1', extra_key_2: 'extra value 2')
end
end
- include_examples 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow
- include_examples 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics
- end
+ it_behaves_like 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow
+ it_behaves_like 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics
- describe '.self_describing_event' do
- it 'delegates to snowplow destination' do
- expect_any_instance_of(Gitlab::Tracking::Destinations::Snowplow)
- .to receive(:self_describing_event)
- .with('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' }, context: nil)
+ it 'tracks errors' do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(
+ an_instance_of(ContractError),
+ snowplow_category: nil, snowplow_action: 'some_action'
+ )
- described_class.self_describing_event('iglu:com.gitlab/foo/jsonschema/1-0-0', data: { foo: 'bar' })
+ described_class.event(nil, 'some_action')
end
end
end
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index 661ef507a82..a86afa9cba5 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -226,6 +226,7 @@ RSpec.describe Gitlab::TreeSummary do
describe 'References in commit messages' do
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:issue) { create(:issue, project: project) }
+
let(:entries) { summary.summarize.first }
let(:entry) { entries.find { |entry| entry[:file_name] == 'issue.txt' } }
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
index aac3d5e27f5..270c4beec97 100644
--- a/spec/lib/gitlab/untrusted_regexp_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -136,4 +136,22 @@ RSpec.describe Gitlab::UntrustedRegexp do
end
end
end
+
+ describe '#match' do
+ context 'when there are matches' do
+ it 'returns a match object' do
+ result = described_class.new('(?P<number>\d+)').match('hello 10')
+
+ expect(result[:number]).to eq('10')
+ end
+ end
+
+ context 'when there are no matches' do
+ it 'returns nil' do
+ result = described_class.new('(?P<number>\d+)').match('hello')
+
+ expect(result).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 6d055fe3643..b359eb422d7 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -92,6 +92,7 @@ RSpec.describe Gitlab::UrlBuilder do
context 'when passing a Snippet' do
let_it_be(:personal_snippet) { create(:personal_snippet, :repository) }
let_it_be(:project_snippet) { create(:project_snippet, :repository) }
+
let(:blob) { snippet.blobs.first }
let(:ref) { blob.repository.root_ref }
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
index 8b592838f5d..e99d720058a 100644
--- a/spec/lib/gitlab/usage/metric_definition_spec.rb
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -16,7 +16,8 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
time_frame: 'none',
data_source: 'database',
distribution: %w(ee ce),
- tier: %w(free starter premium ultimate bronze silver gold)
+ tier: %w(free starter premium ultimate bronze silver gold),
+ name: 'count_boards'
}
end
@@ -24,6 +25,13 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
let(:definition) { described_class.new(path, attributes) }
let(:yaml_content) { attributes.deep_stringify_keys.to_yaml }
+ def write_metric(metric, path, content)
+ path = File.join(metric, path)
+ dir = File.dirname(path)
+ FileUtils.mkdir_p(dir)
+ File.write(path, content)
+ end
+
it 'has all definitons valid' do
expect { described_class.definitions }.not_to raise_error(Gitlab::Usage::Metric::InvalidMetricError)
end
@@ -53,6 +61,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
:distribution | nil
:distribution | 'test'
:tier | %w(test ee)
+ :name | 'count_<adjective_describing>_boards'
end
with_them do
@@ -82,6 +91,28 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
end
end
+ describe 'statuses' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:status, :skip_validation?) do
+ 'deprecated' | true
+ 'removed' | true
+ 'data_available' | false
+ 'implemented' | false
+ 'not_used' | false
+ end
+
+ with_them do
+ subject(:validation) do
+ described_class.new(path, attributes.merge( { status: status } )).send(:skip_validation?)
+ end
+
+ it 'returns true/false for skip_validation' do
+ expect(validation).to eq(skip_validation?)
+ end
+ end
+ end
+
describe '.load_all!' do
let(:metric1) { Dir.mktmpdir('metric1') }
let(:metric2) { Dir.mktmpdir('metric2') }
@@ -121,12 +152,54 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
FileUtils.rm_rf(metric1)
FileUtils.rm_rf(metric2)
end
+ end
+
+ describe 'dump_metrics_yaml' do
+ let(:other_attributes) do
+ {
+ description: 'Test metric definition',
+ value_type: 'string',
+ product_category: 'collection',
+ product_stage: 'growth',
+ status: 'data_available',
+ default_generation: 'generation_1',
+ key_path: 'counter.category.event',
+ product_group: 'group::product analytics',
+ time_frame: 'none',
+ data_source: 'database',
+ distribution: %w(ee ce),
+ tier: %w(free starter premium ultimate bronze silver gold)
+ }
+ end
+
+ let(:other_yaml_content) { other_attributes.deep_stringify_keys.to_yaml }
+ let(:other_path) { File.join('metrics', 'test_metric.yml') }
+ let(:metric1) { Dir.mktmpdir('metric1') }
+ let(:metric2) { Dir.mktmpdir('metric2') }
+
+ before do
+ allow(described_class).to receive(:paths).and_return(
+ [
+ File.join(metric1, '**', '*.yml'),
+ File.join(metric2, '**', '*.yml')
+ ]
+ )
+ # Reset memoized `definitions` result
+ described_class.instance_variable_set(:@definitions, nil)
+ end
+
+ after do
+ FileUtils.rm_rf(metric1)
+ FileUtils.rm_rf(metric2)
+ end
+
+ subject { described_class.dump_metrics_yaml }
+
+ it 'returns a YAML with both metrics in a sequence' do
+ write_metric(metric1, path, yaml_content)
+ write_metric(metric2, other_path, other_yaml_content)
- def write_metric(metric, path, content)
- path = File.join(metric, path)
- dir = File.dirname(path)
- FileUtils.mkdir_p(dir)
- File.write(path, content)
+ is_expected.to eq([attributes, other_attributes].map(&:deep_stringify_keys).to_yaml)
end
end
end
diff --git a/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
index a2a40f17269..db878828cd6 100644
--- a/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::PostgresHll, :clean_
let_it_be(:end_date) { Date.current }
let_it_be(:recorded_at) { Time.current }
let_it_be(:time_period) { { created_at: (start_date..end_date) } }
+
let(:metric_1) { 'metric_1' }
let(:metric_2) { 'metric_2' }
let(:metric_names) { [metric_1, metric_2] }
diff --git a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
index cd0413feab4..34b073b4729 100644
--- a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
describe '#generate' do
shared_examples 'name suggestion' do
it 'return correct name' do
- expect(described_class.generate(key_path)).to eq name_suggestion
+ expect(described_class.generate(key_path)).to match name_suggestion
end
end
@@ -20,7 +20,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with count(Board)
let(:key_path) { 'counts.boards' }
- let(:name_suggestion) { 'count_boards' }
+ let(:name_suggestion) { /count_boards/ }
end
end
@@ -28,7 +28,44 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with distinct_count(ZoomMeeting, :issue_id)
let(:key_path) { 'counts.issues_using_zoom_quick_actions' }
- let(:name_suggestion) { 'count_distinct_issue_id_from_zoom_meetings' }
+ let(:name_suggestion) { /count_distinct_issue_id_from_zoom_meetings/ }
+ end
+ end
+
+ context 'joined relations' do
+ context 'counted attribute comes from joined relation' do
+ it_behaves_like 'name suggestion' do
+ # corresponding metric is collected with:
+ # distinct_count(
+ # ::Clusters::Applications::Ingress.modsecurity_enabled.logging
+ # .joins(cluster: :deployments)
+ # .merge(::Clusters::Cluster.enabled)
+ # .merge(Deployment.success),
+ # ::Deployment.arel_table[:environment_id]
+ # )
+ let(:key_path) { 'counts.ingress_modsecurity_logging' }
+ let(:name_suggestion) do
+ constrains = /'\(clusters_applications_ingress\.modsecurity_enabled = TRUE AND clusters_applications_ingress\.modsecurity_mode = \d+ AND clusters.enabled = TRUE AND deployments.status = \d+\)'/
+ /count_distinct_environment_id_from_<adjective describing\: #{constrains}>_deployments_<with>_<adjective describing\: #{constrains}>_clusters_<having>_<adjective describing\: #{constrains}>_clusters_applications_ingress/
+ end
+ end
+ end
+
+ context 'counted attribute comes from source relation' do
+ it_behaves_like 'name suggestion' do
+ # corresponding metric is collected with count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
+ let(:key_path) { 'counts.issues_created_manually_from_alerts' }
+ let(:name_suggestion) { /count_<adjective describing\: '\(issues\.author_id != \d+\)'>_issues_<with>_alert_management_alerts/ }
+ end
+ end
+ end
+
+ context 'strips off time period constraint' do
+ it_behaves_like 'name suggestion' do
+ # corresponding metric is collected with distinct_count(::Clusters::Cluster.aws_installed.enabled.where(time_period), :user_id)
+ let(:key_path) { 'usage_activity_by_stage_monthly.configure.clusters_platforms_eks' }
+ let(:constraints) { /<adjective describing\: '\(clusters.provider_type = \d+ AND \(cluster_providers_aws\.status IN \(\d+\)\) AND clusters\.enabled = TRUE\)'>/ }
+ let(:name_suggestion) { /count_distinct_user_id_from_#{constraints}_clusters_<with>_#{constraints}_cluster_providers_aws/ }
end
end
@@ -36,7 +73,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with sum(JiraImportState.finished, :imported_issues_count)
let(:key_path) { 'counts.jira_imports_total_imported_issues_count' }
- let(:name_suggestion) { "sum_imported_issues_count_from_<adjective describing: '(jira_imports.status = 4)'>_jira_imports" }
+ let(:name_suggestion) { /sum_imported_issues_count_from_<adjective describing\: '\(jira_imports\.status = \d+\)'>_jira_imports/ }
end
end
@@ -44,7 +81,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with add(data[:personal_snippets], data[:project_snippets])
let(:key_path) { 'counts.snippets' }
- let(:name_suggestion) { "add_count_<adjective describing: '(snippets.type = 'PersonalSnippet')'>_snippets_and_count_<adjective describing: '(snippets.type = 'ProjectSnippet')'>_snippets" }
+ let(:name_suggestion) { /add_count_<adjective describing\: '\(snippets\.type = 'PersonalSnippet'\)'>_snippets_and_count_<adjective describing\: '\(snippets\.type = 'ProjectSnippet'\)'>_snippets/ }
end
end
@@ -52,7 +89,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with redis_usage_data { unique_visit_service.unique_visits_for(targets: :analytics) }
let(:key_path) { 'analytics_unique_visits.analytics_unique_visits_for_any_target' }
- let(:name_suggestion) { '<please fill metric name>' }
+ let(:name_suggestion) { /<please fill metric name, suggested format is: {subject}_{verb}{ing|ed}_{object} eg: users_creating_epics or merge_requests_viewed_in_single_file_mode>/ }
end
end
@@ -60,7 +97,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
it_behaves_like 'name suggestion' do
# corresponding metric is collected with alt_usage_data(fallback: nil) { operating_system }
let(:key_path) { 'settings.operating_system' }
- let(:name_suggestion) { '<please fill metric name>' }
+ let(:name_suggestion) { /<please fill metric name>/ }
end
end
end
diff --git a/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb b/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb
new file mode 100644
index 00000000000..fb3bd564e34
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/names_suggestions/relation_parsers/joins_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::RelationParsers::Joins do
+ describe '#accept' do
+ let(:collector) { Arel::Collectors::SubstituteBinds.new(ActiveRecord::Base.connection, Arel::Collectors::SQLString.new) }
+
+ context 'with join added via string' do
+ it 'collects join parts' do
+ arel = Issue.joins('LEFT JOIN projects ON projects.id = issue.project_id')
+
+ arel = arel.arel
+ result = described_class.new(ApplicationRecord.connection).accept(arel)
+
+ expect(result).to match_array [{ source: "projects", constraints: "projects.id = issue.project_id" }]
+ end
+ end
+
+ context 'with join added via arel node' do
+ it 'collects join parts' do
+ source_table = Arel::Table.new('records')
+ joined_table = Arel::Table.new('joins')
+ second_level_joined_table = Arel::Table.new('second_level_joins')
+
+ arel = source_table
+ .from
+ .project(source_table['id'].count)
+ .join(joined_table, Arel::Nodes::OuterJoin)
+ .on(source_table[:id].eq(joined_table[:records_id]))
+ .join(second_level_joined_table, Arel::Nodes::OuterJoin)
+ .on(joined_table[:id].eq(second_level_joined_table[:joins_id]))
+
+ result = described_class.new(ApplicationRecord.connection).accept(arel)
+
+ expect(result).to match_array [{ source: "joins", constraints: "records.id = joins.records_id" }, { source: "second_level_joins", constraints: "joins.id = second_level_joins.joins_id" }]
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb b/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
deleted file mode 100644
index 9aba86cdaf2..00000000000
--- a/spec/lib/gitlab/usage_data_counters/aggregated_metrics_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'aggregated metrics' do
- RSpec::Matchers.define :be_known_event do
- match do |event|
- Gitlab::UsageDataCounters::HLLRedisCounter.known_event?(event)
- end
-
- failure_message do |event|
- "Event with name: `#{event}` can not be found within `#{Gitlab::UsageDataCounters::HLLRedisCounter::KNOWN_EVENTS_PATH}`"
- end
- end
-
- RSpec::Matchers.define :has_known_source do
- match do |aggregate|
- Gitlab::Usage::Metrics::Aggregates::SOURCES.include?(aggregate[:source])
- end
-
- failure_message do |aggregate|
- "Aggregate with name: `#{aggregate[:name]}` uses not allowed source `#{aggregate[:source]}`"
- end
- end
-
- RSpec::Matchers.define :have_known_time_frame do
- allowed_time_frames = [
- Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME,
- Gitlab::Utils::UsageData::TWENTY_EIGHT_DAYS_TIME_FRAME_NAME,
- Gitlab::Utils::UsageData::SEVEN_DAYS_TIME_FRAME_NAME
- ]
-
- match do |aggregate|
- (aggregate[:time_frame] - allowed_time_frames).empty?
- end
-
- failure_message do |aggregate|
- "Aggregate with name: `#{aggregate[:name]}` uses not allowed time_frame`#{aggregate[:time_frame] - allowed_time_frames}`"
- end
- end
-
- let_it_be(:known_events) do
- Gitlab::UsageDataCounters::HLLRedisCounter.known_events
- end
-
- Gitlab::Usage::Metrics::Aggregates::Aggregate.new(Time.current).send(:aggregated_metrics).tap do |aggregated_metrics|
- it 'all events has unique name' do
- event_names = aggregated_metrics&.map { |event| event[:name] }
-
- expect(event_names).to eq(event_names&.uniq)
- end
-
- it 'all aggregated metrics has known source' do
- expect(aggregated_metrics).to all has_known_source
- end
-
- it 'all aggregated metrics has known source' do
- expect(aggregated_metrics).to all have_known_time_frame
- end
-
- aggregated_metrics&.select { |agg| agg[:source] == Gitlab::Usage::Metrics::Aggregates::REDIS_SOURCE }&.each do |aggregate|
- context "for #{aggregate[:name]} aggregate of #{aggregate[:events].join(' ')}" do
- let_it_be(:events_records) { known_events.select { |event| aggregate[:events].include?(event[:name]) } }
-
- it "does not include 'all' time frame for Redis sourced aggregate" do
- expect(aggregate[:time_frame]).not_to include(Gitlab::Utils::UsageData::ALL_TIME_TIME_FRAME_NAME)
- end
-
- it "only refers to known events" do
- expect(aggregate[:events]).to all be_known_event
- end
-
- it "has expected structure" do
- expect(aggregate.keys).to include(*%w[name operator events])
- end
-
- it "uses allowed aggregation operators" do
- expect(Gitlab::Usage::Metrics::Aggregates::ALLOWED_METRICS_AGGREGATIONS).to include aggregate[:operator]
- end
-
- it "uses events from the same Redis slot" do
- event_slots = events_records.map { |event| event[:redis_slot] }.uniq
-
- expect(event_slots).to contain_exactly(be_present)
- end
-
- it "uses events with the same aggregation period" do
- event_slots = events_records.map { |event| event[:aggregation] }.uniq
-
- expect(event_slots).to contain_exactly(be_present)
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb b/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb
index 664e7938a7e..a1dee442131 100644
--- a/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
# If this spec fails, we need to add the new code review event to the correct aggregated metric
RSpec.describe 'Code review events' do
it 'the aggregated metrics contain all the code review metrics' do
- path = Rails.root.join('lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml')
+ path = Rails.root.join('config/metrics/aggregates/code_review.yml')
aggregated_events = YAML.safe_load(File.read(path), aliases: true)&.map(&:with_indifferent_access)
code_review_aggregated_events = aggregated_events
diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
index d12dcdae955..9fc28f6c4ec 100644
--- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
@@ -34,6 +34,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'source_code',
'incident_management',
'incident_management_alerts',
+ 'incident_management_oncall',
'testing',
'issues_edit',
'ci_secrets_management',
@@ -43,7 +44,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'ci_templates',
'quickactions',
'pipeline_authoring',
- 'epics_usage'
+ 'epics_usage',
+ 'secure'
)
end
end
@@ -93,7 +95,25 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
describe '.track_event' do
- context 'with feature flag set' do
+ context 'with redis_hll_tracking' do
+ it 'tracks the event when feature enabled' do
+ stub_feature_flags(redis_hll_tracking: true)
+
+ expect(Gitlab::Redis::HLL).to receive(:add)
+
+ described_class.track_event(weekly_event, values: 1)
+ end
+
+ it 'does not track the event with feature flag disabled' do
+ stub_feature_flags(redis_hll_tracking: false)
+
+ expect(Gitlab::Redis::HLL).not_to receive(:add)
+
+ described_class.track_event(weekly_event, values: 1)
+ end
+ end
+
+ context 'with event feature flag set' do
it 'tracks the event when feature enabled' do
stub_feature_flags(feature => true)
@@ -111,7 +131,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
end
- context 'with no feature flag set' do
+ context 'with no event feature flag set' do
it 'tracks the event' do
expect(Gitlab::Redis::HLL).to receive(:add)
@@ -289,6 +309,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
described_class.track_event(daily_event, values: entity4, time: 29.days.ago)
end
+ it 'returns 0 if there are no keys for the given events' do
+ expect(Gitlab::Redis::HLL).not_to receive(:count)
+ expect(described_class.unique_events(event_names: [weekly_event], start_date: Date.current, end_date: 4.weeks.ago)).to eq(-1)
+ end
+
it 'raise error if metrics are not in the same slot' do
expect do
described_class.unique_events(event_names: [compliance_slot_event, analytics_slot_event], start_date: 4.weeks.ago, end_date: Date.current)
@@ -508,6 +533,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
expect { described_class.calculate_events_union(**time_range.merge(event_names: %w[event1_slot event4])) }.to raise_error described_class::SlotMismatch
expect { described_class.calculate_events_union(**time_range.merge(event_names: %w[event5_slot event3_slot])) }.to raise_error described_class::AggregationMismatch
end
+
+ it 'returns 0 if there are no keys for given events' do
+ expect(Gitlab::Redis::HLL).not_to receive(:count)
+ expect(described_class.calculate_events_union(event_names: %w[event1_slot event2_slot event3_slot], start_date: Date.current, end_date: 4.weeks.ago)).to eq(-1)
+ end
end
describe '.weekly_time_range' do
diff --git a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
index f8f6494b92e..1b73e5269d7 100644
--- a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
@@ -3,9 +3,10 @@
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_gitlab_redis_shared_state do
- let(:user1) { build(:user, id: 1) }
- let(:user2) { build(:user, id: 2) }
- let(:user3) { build(:user, id: 3) }
+ let_it_be(:user1) { build(:user, id: 1) }
+ let_it_be(:user2) { build(:user, id: 2) }
+ let_it_be(:user3) { build(:user, id: 3) }
+
let(:time) { Time.zone.now }
context 'for Issue title edit actions' do
@@ -272,10 +273,13 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
described_class.track_issue_title_changed_action(author: user1)
described_class.track_issue_description_changed_action(author: user1)
described_class.track_issue_assignee_changed_action(author: user1)
- described_class.track_issue_title_changed_action(author: user2, time: time - 2.days)
- described_class.track_issue_title_changed_action(author: user3, time: time - 3.days)
- described_class.track_issue_description_changed_action(author: user3, time: time - 3.days)
- described_class.track_issue_assignee_changed_action(author: user3, time: time - 3.days)
+
+ travel_to(2.days.ago) do
+ described_class.track_issue_title_changed_action(author: user2)
+ described_class.track_issue_title_changed_action(author: user3)
+ described_class.track_issue_description_changed_action(author: user3)
+ described_class.track_issue_assignee_changed_action(author: user3)
+ end
events = Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category(described_class::ISSUE_CATEGORY)
today_count = Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, start_date: time, end_date: time)
diff --git a/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb
index 2df0f331f73..1940442d2ad 100644
--- a/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb
@@ -115,6 +115,26 @@ RSpec.describe Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter, :cle
end
end
+ context 'tracking spent' do
+ let(:quickaction_name) { 'spent' }
+
+ context 'adding time' do
+ let(:args) { '1d' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_spend_add' }
+ end
+ end
+
+ context 'removing time' do
+ let(:args) { '-1d' }
+
+ it_behaves_like 'a tracked quick action unique event' do
+ let(:action) { 'i_quickactions_spend_subtract' }
+ end
+ end
+ end
+
context 'tracking unassign' do
let(:quickaction_name) { 'unassign' }
diff --git a/spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb b/spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb
new file mode 100644
index 00000000000..32d1288c59c
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_non_sql_metrics_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataNonSqlMetrics do
+ let(:default_count) { Gitlab::UsageDataNonSqlMetrics::SQL_METRIC_DEFAULT }
+
+ describe '.count' do
+ it 'returns default value for count' do
+ expect(described_class.count(User)).to eq(default_count)
+ end
+ end
+
+ describe '.distinct_count' do
+ it 'returns default value for distinct count' do
+ expect(described_class.distinct_count(User)).to eq(default_count)
+ end
+ end
+
+ describe '.estimate_batch_distinct_count' do
+ it 'returns default value for estimate_batch_distinct_count' do
+ expect(described_class.estimate_batch_distinct_count(User)).to eq(default_count)
+ end
+ end
+
+ describe '.sum' do
+ it 'returns default value for sum' do
+ expect(described_class.sum(JiraImportState.finished, :imported_issues_count)).to eq(default_count)
+ end
+ end
+
+ describe '.histogram' do
+ it 'returns default value for histogram' do
+ expect(described_class.histogram(JiraImportState.finished, :imported_issues_count, buckets: [], bucket_size: 0)).to eq(default_count)
+ end
+ end
+
+ describe 'min/max methods' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:model, :result) do
+ User | nil
+ Issue | nil
+ Deployment | nil
+ Project | nil
+ end
+
+ with_them do
+ it 'returns nil' do
+ expect(described_class.minimum_id(model)).to eq(result)
+ expect(described_class.maximum_id(model)).to eq(result)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_queries_spec.rb b/spec/lib/gitlab/usage_data_queries_spec.rb
index 12eac643383..718ab3b2d95 100644
--- a/spec/lib/gitlab/usage_data_queries_spec.rb
+++ b/spec/lib/gitlab/usage_data_queries_spec.rb
@@ -11,12 +11,24 @@ RSpec.describe Gitlab::UsageDataQueries do
it 'returns the raw SQL' do
expect(described_class.count(User)).to start_with('SELECT COUNT("users"."id") FROM "users"')
end
+
+ it 'does not mix a nil column with keyword arguments' do
+ expect(described_class).to receive(:raw_sql).with(User, nil)
+
+ described_class.count(User, start: 1, finish: 2)
+ end
end
describe '.distinct_count' do
it 'returns the raw SQL' do
expect(described_class.distinct_count(Issue, :author_id)).to eq('SELECT COUNT(DISTINCT "issues"."author_id") FROM "issues"')
end
+
+ it 'does not mix a nil column with keyword arguments' do
+ expect(described_class).to receive(:raw_sql).with(Issue, nil, :distinct)
+
+ described_class.distinct_count(Issue, nil, start: 1, finish: 2)
+ end
end
describe '.redis_usage_data' do
@@ -46,4 +58,24 @@ RSpec.describe Gitlab::UsageDataQueries do
.to eq('SELECT (SELECT COUNT("users"."id") FROM "users") + (SELECT COUNT("issues"."id") FROM "issues")')
end
end
+
+ describe 'min/max methods' do
+ it 'returns nil' do
+ # user min/max
+ expect(described_class.minimum_id(User)).to eq(nil)
+ expect(described_class.maximum_id(User)).to eq(nil)
+
+ # issue min/max
+ expect(described_class.minimum_id(Issue)).to eq(nil)
+ expect(described_class.maximum_id(Issue)).to eq(nil)
+
+ # deployment min/max
+ expect(described_class.minimum_id(Deployment)).to eq(nil)
+ expect(described_class.maximum_id(Deployment)).to eq(nil)
+
+ # project min/max
+ expect(described_class.minimum_id(Project)).to eq(nil)
+ expect(described_class.maximum_id(Project)).to eq(nil)
+ end
+ end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index b1581bf02a6..01701f7aebd 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -167,7 +167,10 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
create(:key, user: user)
create(:project, creator: user, disable_overriding_approvers_per_merge_request: true)
create(:project, creator: user, disable_overriding_approvers_per_merge_request: false)
- create(:remote_mirror, project: project)
+ create(:remote_mirror, project: project, enabled: true)
+ another_user = create(:user)
+ another_project = create(:project, :repository, creator: another_user)
+ create(:remote_mirror, project: another_project, enabled: false)
create(:snippet, author: user)
end
@@ -176,7 +179,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
keys: 2,
merge_requests: 2,
projects_with_disable_overriding_approvers_per_merge_request: 2,
- projects_without_disable_overriding_approvers_per_merge_request: 4,
+ projects_without_disable_overriding_approvers_per_merge_request: 6,
remote_mirrors: 2,
snippets: 2
)
@@ -185,7 +188,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
keys: 1,
merge_requests: 1,
projects_with_disable_overriding_approvers_per_merge_request: 1,
- projects_without_disable_overriding_approvers_per_merge_request: 2,
+ projects_without_disable_overriding_approvers_per_merge_request: 3,
remote_mirrors: 1,
snippets: 1
)
@@ -1288,6 +1291,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
'p_analytics_repo' => 123,
'i_analytics_cohorts' => 123,
'i_analytics_dev_ops_score' => 123,
+ 'i_analytics_dev_ops_adoption' => 123,
'i_analytics_instance_statistics' => 123,
'p_analytics_merge_request' => 123,
'g_analytics_merge_request' => 123,
@@ -1358,24 +1362,36 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
let(:ineligible_total_categories) do
- %w[source_code ci_secrets_management incident_management_alerts snippets terraform epics_usage]
+ %w[source_code ci_secrets_management incident_management_alerts snippets terraform incident_management_oncall secure]
end
- it 'has all known_events' do
- expect(subject).to have_key(:redis_hll_counters)
+ context 'with redis_hll_tracking feature enabled' do
+ it 'has all known_events' do
+ stub_feature_flags(redis_hll_tracking: true)
- expect(subject[:redis_hll_counters].keys).to match_array(categories)
+ expect(subject).to have_key(:redis_hll_counters)
- categories.each do |category|
- keys = ::Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category(category)
+ expect(subject[:redis_hll_counters].keys).to match_array(categories)
- metrics = keys.map { |key| "#{key}_weekly" } + keys.map { |key| "#{key}_monthly" }
+ categories.each do |category|
+ keys = ::Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category(category)
- if ineligible_total_categories.exclude?(category)
- metrics.append("#{category}_total_unique_counts_weekly", "#{category}_total_unique_counts_monthly")
+ metrics = keys.map { |key| "#{key}_weekly" } + keys.map { |key| "#{key}_monthly" }
+
+ if ineligible_total_categories.exclude?(category)
+ metrics.append("#{category}_total_unique_counts_weekly", "#{category}_total_unique_counts_monthly")
+ end
+
+ expect(subject[:redis_hll_counters][category].keys).to match_array(metrics)
end
+ end
+ end
+
+ context 'with redis_hll_tracking disabled' do
+ it 'does not have redis_hll_tracking key' do
+ stub_feature_flags(redis_hll_tracking: false)
- expect(subject[:redis_hll_counters][category].keys).to match_array(metrics)
+ expect(subject).not_to have_key(:redis_hll_counters)
end
end
end
diff --git a/spec/lib/gitlab/utils/lazy_attributes_spec.rb b/spec/lib/gitlab/utils/lazy_attributes_spec.rb
index dfffe70defb..1ebc9b0d711 100644
--- a/spec/lib/gitlab/utils/lazy_attributes_spec.rb
+++ b/spec/lib/gitlab/utils/lazy_attributes_spec.rb
@@ -13,8 +13,10 @@ RSpec.describe Gitlab::Utils::LazyAttributes do
def initialize
@number = -> { 1 }
- @reader_1, @reader_2 = 'reader_1', -> { 'reader_2' }
- @incorrect_type, @accessor_2 = -> { :incorrect_type }, -> { 'accessor_2' }
+ @reader_1 = 'reader_1'
+ @reader_2 = -> { 'reader_2' }
+ @incorrect_type = -> { :incorrect_type }
+ @accessor_2 = -> { 'accessor_2' }
end
end
end
diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb
index 6e1904c43e1..11b2a12f228 100644
--- a/spec/lib/gitlab/utils/usage_data_spec.rb
+++ b/spec/lib/gitlab/utils/usage_data_spec.rb
@@ -187,6 +187,7 @@ RSpec.describe Gitlab::Utils::UsageData do
describe '#histogram' do
let_it_be(:projects) { create_list(:project, 3) }
+
let(:project1) { projects.first }
let(:project2) { projects.second }
let(:project3) { projects.third }
@@ -478,4 +479,22 @@ RSpec.describe Gitlab::Utils::UsageData do
expect { described_class.track_usage_event(unknown_event, value) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent)
end
end
+
+ describe 'min/max' do
+ let(:model) { double(:relation) }
+
+ it 'returns min from the model' do
+ allow(model).to receive(:minimum).and_return(2)
+ allow(model).to receive(:name).and_return('sample_min_model')
+
+ expect(described_class.minimum_id(model)).to eq(2)
+ end
+
+ it 'returns max from the model' do
+ allow(model).to receive(:maximum).and_return(100)
+ allow(model).to receive(:name).and_return('sample_max_model')
+
+ expect(described_class.maximum_id(model)).to eq(100)
+ end
+ end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 665eebdfd9e..11dba610faf 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -162,7 +162,7 @@ RSpec.describe Gitlab::Utils do
describe '.nlbr' do
it 'replaces new lines with <br>' do
- expect(described_class.nlbr("<b>hello</b>\n<i>world</i>".freeze)).to eq("hello<br>world")
+ expect(described_class.nlbr("<b>hello</b>\n<i>world</i>")).to eq("hello<br>world")
end
end
@@ -192,6 +192,7 @@ RSpec.describe Gitlab::Utils do
expect(to_boolean('YeS')).to be(true)
expect(to_boolean('t')).to be(true)
expect(to_boolean('1')).to be(true)
+ expect(to_boolean(1)).to be(true)
expect(to_boolean('ON')).to be(true)
expect(to_boolean('FaLse')).to be(false)
@@ -199,6 +200,7 @@ RSpec.describe Gitlab::Utils do
expect(to_boolean('NO')).to be(false)
expect(to_boolean('n')).to be(false)
expect(to_boolean('0')).to be(false)
+ expect(to_boolean(0)).to be(false)
expect(to_boolean('oFF')).to be(false)
end
@@ -388,8 +390,8 @@ RSpec.describe Gitlab::Utils do
describe ".safe_downcase!" do
where(:str, :result) do
- "test".freeze | "test"
- "Test".freeze | "test"
+ "test" | "test"
+ "Test" | "test"
"test" | "test"
"Test" | "test"
end
diff --git a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
index 3e29bf89785..8dbe64af1c7 100644
--- a/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/web_ide/config/entry/global_spec.rb
@@ -83,6 +83,7 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Global do
expect(global.terminal_value).to eq({
tag_list: [],
yaml_variables: [],
+ job_variables: [],
options: {
before_script: ['ls'],
script: ['sleep 10s'],
diff --git a/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb b/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
index 0df0f56f440..d6d0fc4224d 100644
--- a/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
+++ b/spec/lib/gitlab/web_ide/config/entry/terminal_spec.rb
@@ -132,7 +132,7 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Terminal do
{ before_script: %w[ls pwd],
script: 'sleep 100',
tags: ['webide'],
- image: 'ruby:2.5',
+ image: 'ruby:3.0',
services: ['mysql'],
variables: { KEY: 'value' } }
end
@@ -142,8 +142,9 @@ RSpec.describe Gitlab::WebIde::Config::Entry::Terminal do
.to eq(
tag_list: ['webide'],
yaml_variables: [{ key: 'KEY', value: 'value', public: true }],
+ job_variables: [{ key: 'KEY', value: 'value', public: true }],
options: {
- image: { name: "ruby:2.5" },
+ image: { name: "ruby:3.0" },
services: [{ name: "mysql" }],
before_script: %w[ls pwd],
script: ['sleep 100']
diff --git a/spec/lib/gitlab/word_diff/chunk_collection_spec.rb b/spec/lib/gitlab/word_diff/chunk_collection_spec.rb
index aa837f760c1..73e9ff3974a 100644
--- a/spec/lib/gitlab/word_diff/chunk_collection_spec.rb
+++ b/spec/lib/gitlab/word_diff/chunk_collection_spec.rb
@@ -41,4 +41,27 @@ RSpec.describe Gitlab::WordDiff::ChunkCollection do
expect(collection.content).to eq('')
end
end
+
+ describe '#marker_ranges' do
+ let(:chunks) do
+ [
+ Gitlab::WordDiff::Segments::Chunk.new(' Hello '),
+ Gitlab::WordDiff::Segments::Chunk.new('-World'),
+ Gitlab::WordDiff::Segments::Chunk.new('+GitLab'),
+ Gitlab::WordDiff::Segments::Chunk.new('+!!!')
+ ]
+ end
+
+ it 'returns marker ranges for every chunk with changes' do
+ chunks.each { |chunk| collection.add(chunk) }
+
+ expect(collection.marker_ranges).to eq(
+ [
+ Gitlab::MarkerRange.new(6, 10, mode: :deletion),
+ Gitlab::MarkerRange.new(11, 16, mode: :addition),
+ Gitlab::MarkerRange.new(17, 19, mode: :addition)
+ ]
+ )
+ end
+ end
end
diff --git a/spec/lib/gitlab/word_diff/parser_spec.rb b/spec/lib/gitlab/word_diff/parser_spec.rb
index 3aeefb57a02..e793e44fd45 100644
--- a/spec/lib/gitlab/word_diff/parser_spec.rb
+++ b/spec/lib/gitlab/word_diff/parser_spec.rb
@@ -36,15 +36,26 @@ RSpec.describe Gitlab::WordDiff::Parser do
aggregate_failures do
expect(diff_lines.count).to eq(7)
- expect(diff_lines.map(&:to_hash)).to match_array(
+ expect(diff_lines.map { |line| diff_line_attributes(line) }).to eq(
[
- a_hash_including(index: 0, old_pos: 1, new_pos: 1, text: '', type: nil),
- a_hash_including(index: 1, old_pos: 2, new_pos: 2, text: 'Unchanged line', type: nil),
- a_hash_including(index: 2, old_pos: 3, new_pos: 3, text: '', type: nil),
- a_hash_including(index: 3, old_pos: 4, new_pos: 4, text: 'Old changeNew addition unchanged content', type: nil),
- a_hash_including(index: 4, old_pos: 50, new_pos: 50, text: '@@ -50,14 +50,13 @@', type: 'match'),
- a_hash_including(index: 5, old_pos: 50, new_pos: 50, text: 'First change same same same_removed_added_end of the line', type: nil),
- a_hash_including(index: 6, old_pos: 51, new_pos: 51, text: '', type: nil)
+ { index: 0, old_pos: 1, new_pos: 1, text: '', type: nil, marker_ranges: [] },
+ { index: 1, old_pos: 2, new_pos: 2, text: 'Unchanged line', type: nil, marker_ranges: [] },
+ { index: 2, old_pos: 3, new_pos: 3, text: '', type: nil, marker_ranges: [] },
+ { index: 3, old_pos: 4, new_pos: 4, text: 'Old changeNew addition unchanged content', type: nil,
+ marker_ranges: [
+ Gitlab::MarkerRange.new(0, 9, mode: :deletion),
+ Gitlab::MarkerRange.new(10, 21, mode: :addition)
+ ] },
+
+ { index: 4, old_pos: 50, new_pos: 50, text: '@@ -50,14 +50,13 @@', type: 'match', marker_ranges: [] },
+ { index: 5, old_pos: 50, new_pos: 50, text: 'First change same same same_removed_added_end of the line', type: nil,
+ marker_ranges: [
+ Gitlab::MarkerRange.new(0, 11, mode: :addition),
+ Gitlab::MarkerRange.new(28, 35, mode: :deletion),
+ Gitlab::MarkerRange.new(36, 41, mode: :addition)
+ ] },
+
+ { index: 6, old_pos: 51, new_pos: 51, text: '', type: nil, marker_ranges: [] }
]
)
end
@@ -64,4 +75,17 @@ RSpec.describe Gitlab::WordDiff::Parser do
it { is_expected.to eq([]) }
end
end
+
+ private
+
+ def diff_line_attributes(diff_line)
+ {
+ index: diff_line.index,
+ old_pos: diff_line.old_pos,
+ new_pos: diff_line.new_pos,
+ text: diff_line.text,
+ type: diff_line.type,
+ marker_ranges: diff_line.marker_ranges
+ }
+ end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index c22df5dd063..d40ecc7e04e 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Workhorse do
let_it_be(:project) { create(:project, :repository) }
+
let(:repository) { project.repository }
def decode_workhorse_header(array)
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index c5738ae730f..4df00eaa439 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -247,75 +247,117 @@ RSpec.describe Gitlab do
end
end
- describe '.ee?' do
+ describe 'ee? and jh?' do
before do
- stub_env('FOSS_ONLY', nil) # Make sure the ENV is clean
+ # Make sure the ENV is clean
+ stub_env('FOSS_ONLY', nil)
+ stub_env('EE_ONLY', nil)
+
described_class.instance_variable_set(:@is_ee, nil)
+ described_class.instance_variable_set(:@is_jh, nil)
end
after do
described_class.instance_variable_set(:@is_ee, nil)
+ described_class.instance_variable_set(:@is_jh, nil)
end
- context 'for EE' do
- before do
- root = Pathname.new('dummy')
- license_path = double(:path, exist?: true)
+ def stub_path(*paths, **arguments)
+ root = Pathname.new('dummy')
+ pathname = double(:path, **arguments)
- allow(described_class)
- .to receive(:root)
- .and_return(root)
+ allow(described_class)
+ .to receive(:root)
+ .and_return(root)
+ allow(root).to receive(:join)
+
+ paths.each do |path|
allow(root)
.to receive(:join)
- .with('ee/app/models/license.rb')
- .and_return(license_path)
+ .with(path)
+ .and_return(pathname)
end
+ end
- context 'when using FOSS_ONLY=1' do
+ describe '.ee?' do
+ context 'for EE' do
before do
- stub_env('FOSS_ONLY', '1')
+ stub_path('ee/app/models/license.rb', exist?: true)
end
- it 'returns not to be EE' do
- expect(described_class).not_to be_ee
+ context 'when using FOSS_ONLY=1' do
+ before do
+ stub_env('FOSS_ONLY', '1')
+ end
+
+ it 'returns not to be EE' do
+ expect(described_class).not_to be_ee
+ end
end
- end
- context 'when using FOSS_ONLY=0' do
- before do
- stub_env('FOSS_ONLY', '0')
+ context 'when using FOSS_ONLY=0' do
+ before do
+ stub_env('FOSS_ONLY', '0')
+ end
+
+ it 'returns to be EE' do
+ expect(described_class).to be_ee
+ end
end
- it 'returns to be EE' do
- expect(described_class).to be_ee
+ context 'when using default FOSS_ONLY' do
+ it 'returns to be EE' do
+ expect(described_class).to be_ee
+ end
end
end
- context 'when using default FOSS_ONLY' do
- it 'returns to be EE' do
- expect(described_class).to be_ee
+ context 'for CE' do
+ before do
+ stub_path('ee/app/models/license.rb', exist?: false)
+ end
+
+ it 'returns not to be EE' do
+ expect(described_class).not_to be_ee
end
end
end
- context 'for CE' do
- before do
- root = double(:path)
- license_path = double(:path, exists?: false)
+ describe '.jh?' do
+ context 'for JH' do
+ before do
+ stub_path(
+ 'ee/app/models/license.rb',
+ 'jh',
+ exist?: true)
+ end
- allow(described_class)
- .to receive(:root)
- .and_return(Pathname.new('dummy'))
+ context 'when using default FOSS_ONLY and EE_ONLY' do
+ it 'returns to be JH' do
+ expect(described_class).to be_jh
+ end
+ end
- allow(root)
- .to receive(:join)
- .with('ee/app/models/license.rb')
- .and_return(license_path)
- end
+ context 'when using FOSS_ONLY=1' do
+ before do
+ stub_env('FOSS_ONLY', '1')
+ end
+
+ it 'returns not to be JH' do
+ expect(described_class).not_to be_jh
+ end
+ end
+
+ context 'when using EE_ONLY=1' do
+ before do
+ stub_env('EE_ONLY', '1')
+ end
- it 'returns not to be EE' do
- expect(described_class).not_to be_ee
+ it 'returns not to be JH' do
+ expect(described_class).not_to be_jh
+ end
+ end
end
end
end
diff --git a/spec/lib/kramdown/kramdown_spec.rb b/spec/lib/kramdown/kramdown_spec.rb
new file mode 100644
index 00000000000..986a8d9959e
--- /dev/null
+++ b/spec/lib/kramdown/kramdown_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Ensure kramdown detects invalid syntax highlighting formatters' do
+ subject { Kramdown::Document.new(options + "\n" + code).to_html }
+
+ let(:code) do
+ <<-RUBY
+~~~ ruby
+ def what?
+ 42
+ end
+~~~
+ RUBY
+ end
+
+ context 'with invalid formatter' do
+ let(:options) { %({::options auto_ids="false" footnote_nr="5" syntax_highlighter="rouge" syntax_highlighter_opts="{formatter: CSV, line_numbers: true\\}" /}) }
+
+ it 'falls back to standard HTML and disallows CSV' do
+ expect(CSV).not_to receive(:new)
+ expect(::Rouge::Formatters::HTML).to receive(:new).and_call_original
+
+ expect(subject).to be_present
+ end
+ end
+
+ context 'with valid formatter' do
+ let(:options) { %({::options auto_ids="false" footnote_nr="5" syntax_highlighter="rouge" syntax_highlighter_opts="{formatter: HTMLLegacy\\}" /}) }
+
+ it 'allows formatter' do
+ expect(::Rouge::Formatters::HTMLLegacy).to receive(:new).and_call_original
+
+ expect(subject).to be_present
+ end
+ end
+end
diff --git a/spec/lib/marginalia_spec.rb b/spec/lib/marginalia_spec.rb
index 2ee27fbe20c..040f70236c6 100644
--- a/spec/lib/marginalia_spec.rb
+++ b/spec/lib/marginalia_spec.rb
@@ -3,18 +3,28 @@
require 'spec_helper'
RSpec.describe 'Marginalia spec' do
- class MarginaliaTestController < ActionController::Base
+ class MarginaliaTestController < ApplicationController
+ skip_before_action :authenticate_user!, :check_two_factor_requirement
+
def first_user
User.first
render body: nil
end
+
+ private
+
+ [:auth_user, :current_user, :set_experimentation_subject_id_cookie, :signed_in?].each do |method|
+ define_method(method) { }
+ end
end
class MarginaliaTestJob
include Sidekiq::Worker
def perform
- User.first
+ Gitlab::ApplicationContext.with_context(caller_id: self.class.name) do
+ User.first
+ end
end
end
@@ -30,10 +40,9 @@ RSpec.describe 'Marginalia spec' do
let(:component_map) do
{
- "application" => "test",
- "controller" => "marginalia_test",
- "action" => "first_user",
- "correlation_id" => correlation_id
+ "application" => "test",
+ "endpoint_id" => "MarginaliaTestController#first_user",
+ "correlation_id" => correlation_id
}
end
@@ -47,6 +56,7 @@ RSpec.describe 'Marginalia spec' do
describe 'for Sidekiq worker jobs' do
around do |example|
with_sidekiq_server_middleware do |chain|
+ chain.add Labkit::Middleware::Sidekiq::Context::Server
chain.add Marginalia::SidekiqInstrumentation::Middleware
Marginalia.application_name = "sidekiq"
example.run
@@ -66,10 +76,10 @@ RSpec.describe 'Marginalia spec' do
let(:component_map) do
{
- "application" => "sidekiq",
- "job_class" => "MarginaliaTestJob",
- "correlation_id" => sidekiq_job['correlation_id'],
- "jid" => sidekiq_job['jid']
+ "application" => "sidekiq",
+ "endpoint_id" => "MarginaliaTestJob",
+ "correlation_id" => sidekiq_job['correlation_id'],
+ "jid" => sidekiq_job['jid']
}
end
@@ -80,19 +90,33 @@ RSpec.describe 'Marginalia spec' do
end
describe 'for ActionMailer delivery jobs' do
+ # We need to ensure that this runs through Sidekiq to take
+ # advantage of the middleware. There is a Rails bug that means we
+ # have to do some extra steps to make this happen:
+ # https://github.com/rails/rails/issues/37270#issuecomment-553927324
+ around do |example|
+ descendants = ActiveJob::Base.descendants + [ActiveJob::Base]
+ descendants.each(&:disable_test_adapter)
+ ActiveJob::Base.queue_adapter = :sidekiq
+
+ example.run
+
+ descendants.each { |a| a.queue_adapter = :test }
+ end
+
let(:delivery_job) { MarginaliaTestMailer.first_user.deliver_later }
let(:recorded) do
ActiveRecord::QueryRecorder.new do
- delivery_job.perform_now
+ Sidekiq::Worker.drain_all
end
end
let(:component_map) do
{
- "application" => "sidekiq",
- "jid" => delivery_job.job_id,
- "job_class" => delivery_job.arguments.first
+ "application" => "sidekiq",
+ "endpoint_id" => "ActionMailer::MailDeliveryJob",
+ "jid" => delivery_job.job_id
}
end
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
index 26d1ec32232..0f2711e0b11 100644
--- a/spec/lib/mattermost/command_spec.rb
+++ b/spec/lib/mattermost/command_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Mattermost::Command do
trigger: 'gitlab' }
end
- subject { described_class.new(nil).create(params) }
+ subject { described_class.new(nil).create(params) } # rubocop:disable Rails/SaveBang
context 'for valid trigger word' do
before do
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index 93422b01ca7..67ccb48e3a7 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe Mattermost::Session, type: :request do
context 'with oauth_uri' do
let!(:doorkeeper) do
- Doorkeeper::Application.create(
+ Doorkeeper::Application.create!(
name: 'GitLab Mattermost',
redirect_uri: "#{mattermost_url}/signup/gitlab/complete\n#{mattermost_url}/login/gitlab/complete",
scopes: '')
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index 0870114ca28..e3ef5ff5377 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -71,7 +71,7 @@ RSpec.describe Mattermost::Team do
end
describe '#create' do
- subject { described_class.new(nil).create(name: "devteam", display_name: "Dev Team", type: "O") }
+ subject { described_class.new(nil).create(name: "devteam", display_name: "Dev Team", type: "O") } # rubocop:disable Rails/SaveBang
context 'for a new team' do
let(:response) do
diff --git a/spec/lib/peek/views/active_record_spec.rb b/spec/lib/peek/views/active_record_spec.rb
index dad5a2bf461..9eeeca4de61 100644
--- a/spec/lib/peek/views/active_record_spec.rb
+++ b/spec/lib/peek/views/active_record_spec.rb
@@ -5,14 +5,16 @@ require 'spec_helper'
RSpec.describe Peek::Views::ActiveRecord, :request_store do
subject { Peek.views.find { |v| v.instance_of?(Peek::Views::ActiveRecord) } }
- let(:connection) { double(:connection) }
+ let(:connection_1) { double(:connection) }
+ let(:connection_2) { double(:connection) }
+ let(:connection_3) { double(:connection) }
let(:event_1) do
{
name: 'SQL',
sql: 'SELECT * FROM users WHERE id = 10',
cached: false,
- connection: connection
+ connection: connection_1
}
end
@@ -21,7 +23,7 @@ RSpec.describe Peek::Views::ActiveRecord, :request_store do
name: 'SQL',
sql: 'SELECT * FROM users WHERE id = 10',
cached: true,
- connection: connection
+ connection: connection_2
}
end
@@ -30,12 +32,15 @@ RSpec.describe Peek::Views::ActiveRecord, :request_store do
name: 'SQL',
sql: 'UPDATE users SET admin = true WHERE id = 10',
cached: false,
- connection: connection
+ connection: connection_3
}
end
before do
allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?).and_return(true)
+ allow(connection_1).to receive(:transaction_open?).and_return(false)
+ allow(connection_2).to receive(:transaction_open?).and_return(false)
+ allow(connection_3).to receive(:transaction_open?).and_return(true)
end
it 'subscribes and store data into peek views' do
@@ -46,22 +51,32 @@ RSpec.describe Peek::Views::ActiveRecord, :request_store do
end
expect(subject.results).to match(
- calls: '3 (1 cached)',
+ calls: 3,
+ summary: {
+ "Cached" => 1,
+ "In a transaction" => 1
+ },
duration: '6000.00ms',
warnings: ["active-record duration: 6000.0 over 3000"],
details: contain_exactly(
a_hash_including(
+ start: be_a(Time),
cached: '',
+ transaction: '',
duration: 1000.0,
sql: 'SELECT * FROM users WHERE id = 10'
),
a_hash_including(
- cached: 'cached',
+ start: be_a(Time),
+ cached: 'Cached',
+ transaction: '',
duration: 2000.0,
sql: 'SELECT * FROM users WHERE id = 10'
),
a_hash_including(
+ start: be_a(Time),
cached: '',
+ transaction: 'In a transaction',
duration: 3000.0,
sql: 'UPDATE users SET admin = true WHERE id = 10'
)
diff --git a/spec/lib/peek/views/external_http_spec.rb b/spec/lib/peek/views/external_http_spec.rb
index 98c4f771f33..18ae1326493 100644
--- a/spec/lib/peek/views/external_http_spec.rb
+++ b/spec/lib/peek/views/external_http_spec.rb
@@ -11,6 +11,10 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?).and_return(true)
end
+ around do |example|
+ freeze_time { example.run }
+ end
+
let(:event_1) do
{
method: 'POST', code: "200", duration: 0.03,
@@ -44,9 +48,9 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'returns aggregated results' do
- subscriber.request(double(:event, payload: event_1))
- subscriber.request(double(:event, payload: event_2))
- subscriber.request(double(:event, payload: event_3))
+ subscriber.request(double(:event, payload: event_1, time: Time.current))
+ subscriber.request(double(:event, payload: event_2, time: Time.current))
+ subscriber.request(double(:event, payload: event_3, time: Time.current))
results = subject.results
expect(results[:calls]).to eq(3)
@@ -55,6 +59,7 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
expected = [
{
+ start: be_like_time(Time.current),
duration: 30.0,
label: "POST https://gitlab.com:80/api/v4/projects?current=true",
code: "Response status: 200",
@@ -63,6 +68,7 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
warnings: []
},
{
+ start: be_like_time(Time.current),
duration: 1300,
label: "POST http://gitlab.com:80/api/v4/projects/2/issues?current=true",
code: nil,
@@ -71,6 +77,7 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
warnings: ["1300.0 over 100"]
},
{
+ start: be_like_time(Time.current),
duration: 5.0,
label: "GET http://gitlab.com:80/api/v4/projects/2?current=true",
code: "Response status: 301",
@@ -81,7 +88,7 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
]
expect(
- results[:details].map { |data| data.slice(:duration, :label, :code, :proxy, :error, :warnings) }
+ results[:details].map { |data| data.slice(:start, :duration, :label, :code, :proxy, :error, :warnings) }
).to match_array(expected)
end
@@ -91,10 +98,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays IPv4 in the label' do
- subscriber.request(double(:event, payload: event_1))
+ subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
+ start: be_like_time(Time.current),
duration: 30.0,
label: "POST https://1.2.3.4:80/api/v4/projects?current=true",
code: "Response status: 200",
@@ -112,10 +120,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays IPv6 in the label' do
- subscriber.request(double(:event, payload: event_1))
+ subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
+ start: be_like_time(Time.current),
duration: 30.0,
label: "POST https://[2606:4700:90:0:f22e:fbec:5bed:a9b9]:80/api/v4/projects?current=true",
code: "Response status: 200",
@@ -133,10 +142,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'converts query hash into a query string' do
- subscriber.request(double(:event, payload: event_1))
+ subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
+ start: be_like_time(Time.current),
duration: 30.0,
label: "POST https://gitlab.com:80/api/v4/projects?current=true&item1=string&item2%5B%5D=1&item2%5B%5D=2",
code: "Response status: 200",
@@ -154,10 +164,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays unknown in the label' do
- subscriber.request(double(:event, payload: event_1))
+ subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
+ start: be_like_time(Time.current),
duration: 30.0,
label: "POST unknown",
code: "Response status: 200",
@@ -176,10 +187,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays unknown in the label' do
- subscriber.request(double(:event, payload: event_1))
+ subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
+ start: be_like_time(Time.current),
duration: 30.0,
label: "POST unknown",
code: "Response status: 200",
@@ -198,10 +210,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays unknown in the label' do
- subscriber.request(double(:event, payload: event_1))
+ subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
+ start: be_like_time(Time.current),
duration: 30.0,
label: "POST unknown",
code: "Response status: 200",
diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb
deleted file mode 100644
index 32960cd571b..00000000000
--- a/spec/lib/quality/test_level_spec.rb
+++ /dev/null
@@ -1,225 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Quality::TestLevel do
- describe '#pattern' do
- context 'when level is all' do
- it 'returns a pattern' do
- expect(subject.pattern(:all))
- .to eq("spec/**{,/**/}*_spec.rb")
- end
- end
-
- context 'when level is geo' do
- it 'returns a pattern' do
- expect(subject.pattern(:geo))
- .to eq("spec/**{,/**/}*_spec.rb")
- end
- end
-
- context 'when level is frontend_fixture' do
- it 'returns a pattern' do
- expect(subject.pattern(:frontend_fixture))
- .to eq("spec/{frontend/fixtures}{,/**/}*.rb")
- end
- end
-
- context 'when level is unit' do
- it 'returns a pattern' do
- expect(subject.pattern(:unit))
- .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
- end
- end
-
- context 'when level is migration' do
- it 'returns a pattern' do
- expect(subject.pattern(:migration))
- .to eq("spec/{migrations,lib/gitlab/background_migration,lib/ee/gitlab/background_migration}{,/**/}*_spec.rb")
- end
- end
-
- context 'when level is background_migration' do
- it 'returns a pattern' do
- expect(subject.pattern(:background_migration))
- .to eq("spec/{lib/gitlab/background_migration,lib/ee/gitlab/background_migration}{,/**/}*_spec.rb")
- end
- end
-
- context 'when level is integration' do
- it 'returns a pattern' do
- expect(subject.pattern(:integration))
- .to eq("spec/{controllers,mailers,requests}{,/**/}*_spec.rb")
- end
- end
-
- context 'when level is system' do
- it 'returns a pattern' do
- expect(subject.pattern(:system))
- .to eq("spec/{features}{,/**/}*_spec.rb")
- end
- end
-
- context 'with a prefix' do
- it 'returns a pattern' do
- expect(described_class.new('ee/').pattern(:system))
- .to eq("ee/spec/{features}{,/**/}*_spec.rb")
- end
- end
-
- describe 'performance' do
- it 'memoizes the pattern for a given level' do
- expect(subject.pattern(:system).object_id).to eq(subject.pattern(:system).object_id)
- end
-
- it 'freezes the pattern for a given level' do
- expect(subject.pattern(:system)).to be_frozen
- end
- end
- end
-
- describe '#regexp' do
- context 'when level is all' do
- it 'returns a regexp' do
- expect(subject.regexp(:all))
- .to eq(%r{spec/})
- end
- end
-
- context 'when level is geo' do
- it 'returns a regexp' do
- expect(subject.regexp(:geo))
- .to eq(%r{spec/})
- end
- end
-
- context 'when level is frontend_fixture' do
- it 'returns a regexp' do
- expect(subject.regexp(:frontend_fixture))
- .to eq(%r{spec/(frontend/fixtures)})
- end
- end
-
- context 'when level is unit' do
- it 'returns a regexp' do
- expect(subject.regexp(:unit))
- .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)})
- end
- end
-
- context 'when level is migration' do
- it 'returns a regexp' do
- expect(subject.regexp(:migration))
- .to eq(%r{spec/(migrations|lib/gitlab/background_migration|lib/ee/gitlab/background_migration)})
- end
- end
-
- context 'when level is background_migration' do
- it 'returns a regexp' do
- expect(subject.regexp(:background_migration))
- .to eq(%r{spec/(lib/gitlab/background_migration|lib/ee/gitlab/background_migration)})
- end
- end
-
- context 'when level is integration' do
- it 'returns a regexp' do
- expect(subject.regexp(:integration))
- .to eq(%r{spec/(controllers|mailers|requests)})
- end
- end
-
- context 'when level is system' do
- it 'returns a regexp' do
- expect(subject.regexp(:system))
- .to eq(%r{spec/(features)})
- end
- end
-
- context 'with a prefix' do
- it 'returns a regexp' do
- expect(described_class.new('ee/').regexp(:system))
- .to eq(%r{ee/spec/(features)})
- end
- end
-
- describe 'performance' do
- it 'memoizes the regexp for a given level' do
- expect(subject.regexp(:system).object_id).to eq(subject.regexp(:system).object_id)
- end
-
- it 'freezes the regexp for a given level' do
- expect(subject.regexp(:system)).to be_frozen
- end
- end
- end
-
- describe '#level_for' do
- it 'returns the correct level for a unit test' do
- expect(subject.level_for('spec/models/abuse_report_spec.rb')).to eq(:unit)
- end
-
- it 'returns the correct level for a frontend fixture test' do
- expect(subject.level_for('spec/frontend/fixtures/pipelines.rb')).to eq(:frontend_fixture)
- end
-
- it 'returns the correct level for a tooling test' do
- expect(subject.level_for('spec/tooling/lib/tooling/test_file_finder_spec.rb')).to eq(:unit)
- end
-
- it 'returns the correct level for a migration test' do
- expect(subject.level_for('spec/migrations/add_default_and_free_plans_spec.rb')).to eq(:migration)
- end
-
- it 'returns the correct level for a background migration test' do
- expect(subject.level_for('spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb')).to eq(:migration)
- end
-
- it 'returns the correct level for an EE file without passing a prefix' do
- expect(subject.level_for('ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb')).to eq(:migration)
- end
-
- it 'returns the correct level for a geo migration test' do
- expect(described_class.new('ee/').level_for('ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb')).to eq(:migration)
- end
-
- it 'returns the correct level for a EE-namespaced background migration test' do
- expect(described_class.new('ee/').level_for('ee/spec/lib/ee/gitlab/background_migration/prune_orphaned_geo_events_spec.rb')).to eq(:migration)
- end
-
- it 'returns the correct level for an integration test' do
- expect(subject.level_for('spec/mailers/abuse_report_mailer_spec.rb')).to eq(:integration)
- end
-
- it 'returns the correct level for a system test' do
- expect(subject.level_for('spec/features/abuse_report_spec.rb')).to eq(:system)
- end
-
- it 'raises an error for an unknown level' do
- expect { subject.level_for('spec/unknown/foo_spec.rb') }
- .to raise_error(described_class::UnknownTestLevelError,
- %r{Test level for spec/unknown/foo_spec.rb couldn't be set. Please rename the file properly or change the test level detection regexes in .+/lib/quality/test_level.rb.})
- end
- end
-
- describe '#background_migration?' do
- it 'returns false for a unit test' do
- expect(subject.background_migration?('spec/models/abuse_report_spec.rb')).to be(false)
- end
-
- it 'returns true for a migration test' do
- expect(subject.background_migration?('spec/migrations/add_default_and_free_plans_spec.rb')).to be(false)
- end
-
- it 'returns true for a background migration test' do
- expect(subject.background_migration?('spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb')).to be(true)
- end
-
- it 'returns true for a geo migration test' do
- expect(described_class.new('ee/').background_migration?('ee/spec/migrations/geo/migrate_ci_job_artifacts_to_separate_registry_spec.rb')).to be(false)
- end
-
- it 'returns true for a EE-namespaced background migration test' do
- expect(described_class.new('ee/').background_migration?('ee/spec/lib/ee/gitlab/background_migration/prune_orphaned_geo_events_spec.rb')).to be(true)
- end
- end
-end
diff --git a/spec/lib/rouge/formatters/html_gitlab_spec.rb b/spec/lib/rouge/formatters/html_gitlab_spec.rb
new file mode 100644
index 00000000000..d45c8c2a8c5
--- /dev/null
+++ b/spec/lib/rouge/formatters/html_gitlab_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Rouge::Formatters::HTMLGitlab do
+ describe '#format' do
+ subject { described_class.format(tokens, options) }
+
+ let(:lang) { 'ruby' }
+ let(:lexer) { Rouge::Lexer.find_fancy(lang) }
+ let(:tokens) { lexer.lex("def hello", continue: false) }
+ let(:options) { { tag: lang } }
+
+ it 'returns highlighted ruby code' do
+ code = %q{<span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">hello</span></span>}
+
+ is_expected.to eq(code)
+ end
+
+ context 'when options are empty' do
+ let(:options) { {} }
+
+ it 'returns highlighted code without language' do
+ code = %q{<span id="LC1" class="line" lang=""><span class="k">def</span> <span class="nf">hello</span></span>}
+
+ is_expected.to eq(code)
+ end
+ end
+
+ context 'when line number is provided' do
+ let(:options) { { tag: lang, line_number: 10 } }
+
+ it 'returns highlighted ruby code with correct line number' do
+ code = %q{<span id="LC10" class="line" lang="ruby"><span class="k">def</span> <span class="nf">hello</span></span>}
+
+ is_expected.to eq(code)
+ end
+ end
+ end
+end