summaryrefslogtreecommitdiff
path: root/spec/lib
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib')
-rw-r--r--spec/lib/api/ci/helpers/runner_helpers_spec.rb4
-rw-r--r--spec/lib/api/entities/bulk_imports/entity_spec.rb1
-rw-r--r--spec/lib/api/entities/ci/job_request/image_spec.rb2
-rw-r--r--spec/lib/api/entities/ci/job_request/port_spec.rb2
-rw-r--r--spec/lib/api/entities/ci/job_request/service_spec.rb2
-rw-r--r--spec/lib/api/entities/project_spec.rb13
-rw-r--r--spec/lib/api/helpers/authentication_spec.rb2
-rw-r--r--spec/lib/api/helpers_spec.rb2
-rw-r--r--spec/lib/api/support/git_access_actor_spec.rb30
-rw-r--r--spec/lib/backup/database_spec.rb24
-rw-r--r--spec/lib/backup/gitaly_backup_spec.rb70
-rw-r--r--spec/lib/backup/manager_spec.rb24
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb2
-rw-r--r--spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/commit_trailers_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/task_list_filter_spec.rb34
-rw-r--r--spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb29
-rw-r--r--spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb4
-rw-r--r--spec/lib/banzai/renderer_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/connection_spec.rb2
-rw-r--r--spec/lib/bulk_imports/clients/http_spec.rb66
-rw-r--r--spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb4
-rw-r--r--spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb2
-rw-r--r--spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb8
-rw-r--r--spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb8
-rw-r--r--spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb2
-rw-r--r--spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb10
-rw-r--r--spec/lib/container_registry/gitlab_api_client_spec.rb125
-rw-r--r--spec/lib/container_registry/tag_spec.rb35
-rw-r--r--spec/lib/feature_spec.rb4
-rw-r--r--spec/lib/gitlab/alert_management/payload/base_spec.rb40
-rw-r--r--spec/lib/gitlab/application_context_spec.rb2
-rw-r--r--spec/lib/gitlab/application_rate_limiter_spec.rb71
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb2
-rw-r--r--spec/lib/gitlab/audit/auditor_spec.rb258
-rw-r--r--spec/lib/gitlab/audit/ci_runner_token_author_spec.rb2
-rw-r--r--spec/lib/gitlab/audit/deploy_key_author_spec.rb17
-rw-r--r--spec/lib/gitlab/audit/null_author_spec.rb9
-rw-r--r--spec/lib/gitlab/audit/null_target_spec.rb25
-rw-r--r--spec/lib/gitlab/audit/target_spec.rb47
-rw-r--r--spec/lib/gitlab/auth/auth_finders_spec.rb6
-rw-r--r--spec/lib/gitlab/auth/ip_rate_limiter_spec.rb22
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb30
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb1
-rw-r--r--spec/lib/gitlab/auth_spec.rb71
-rw-r--r--spec/lib/gitlab/background_migration/backfill_ci_namespace_mirrors_spec.rb45
-rw-r--r--spec/lib/gitlab/background_migration/backfill_ci_project_mirrors_spec.rb46
-rw-r--r--spec/lib/gitlab/background_migration/backfill_ci_queuing_tables_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb54
-rw-r--r--spec/lib/gitlab/background_migration/backfill_group_features_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/backfill_namespace_id_of_vulnerability_reads_spec.rb54
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_import_level_spec.rb123
-rw-r--r--spec/lib/gitlab/background_migration/backfill_projects_with_coverage_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb93
-rw-r--r--spec/lib/gitlab/background_migration/batched_migration_job_spec.rb151
-rw-r--r--spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb15
-rw-r--r--spec/lib/gitlab/background_migration/copy_ci_builds_columns_to_security_scans_spec.rb51
-rw-r--r--spec/lib/gitlab/background_migration/copy_column_using_background_migration_job_spec.rb58
-rw-r--r--spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb62
-rw-r--r--spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb66
-rw-r--r--spec/lib/gitlab/background_migration/drop_invalid_security_findings_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb13
-rw-r--r--spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb298
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/remove_all_trace_expiration_dates_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb3
-rw-r--r--spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb51
-rw-r--r--spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb41
-rw-r--r--spec/lib/gitlab/background_task_spec.rb209
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb19
-rw-r--r--spec/lib/gitlab/batch_pop_queueing_spec.rb2
-rw-r--r--spec/lib/gitlab/chat_name_token_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/ansi2json_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/artifacts/logger_spec.rb60
-rw-r--r--spec/lib/gitlab/ci/artifacts/metrics_spec.rb19
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/zip_stream_spec.rb86
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/releaser_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb98
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/imageable_spec.rb81
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/release_spec.rb38
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb27
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/tags_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/normalizer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb88
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb135
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb132
-rw-r--r--spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb599
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb90
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/component_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/report_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/reports_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/reports/sbom/source_spec.rb29
-rw-r--r--spec/lib/gitlab/ci/reports/security/reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/runner_releases_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/runner_upgrade_check_spec.rb45
-rw-r--r--spec/lib/gitlab/ci/status/bridge/common_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/status/build/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/processable/waiting_for_resource_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/remote_checksum_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/variables/builder_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb32
-rw-r--r--spec/lib/gitlab/ci/variables/helpers_spec.rb113
-rw-r--r--spec/lib/gitlab/ci/yaml_processor/result_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb72
-rw-r--r--spec/lib/gitlab/composer/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb4
-rw-r--r--spec/lib/gitlab/data_builder/issuable_spec.rb21
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb1
-rw-r--r--spec/lib/gitlab/database/async_indexes/index_destructor_spec.rb69
-rw-r--r--spec/lib/gitlab/database/async_indexes/migration_helpers_spec.rb38
-rw-r--r--spec/lib/gitlab/database/async_indexes/postgres_async_index_spec.rb17
-rw-r--r--spec/lib/gitlab/database/async_indexes_spec.rb20
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_job_spec.rb21
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb12
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_spec.rb45
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb16
-rw-r--r--spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb2
-rw-r--r--spec/lib/gitlab/database/background_migration/health_status/indicators/write_ahead_log_spec.rb61
-rw-r--r--spec/lib/gitlab/database/background_migration/health_status_spec.rb45
-rw-r--r--spec/lib/gitlab/database/bulk_update_spec.rb3
-rw-r--r--spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb10
-rw-r--r--spec/lib/gitlab/database/load_balancing/rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/database/load_balancing/session_spec.rb6
-rw-r--r--spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/database/load_balancing/sticking_spec.rb2
-rw-r--r--spec/lib/gitlab/database/load_balancing_spec.rb2
-rw-r--r--spec/lib/gitlab/database/lock_writes_manager_spec.rb123
-rw-r--r--spec/lib/gitlab/database/loose_foreign_keys_spec.rb28
-rw-r--r--spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb7
-rw-r--r--spec/lib/gitlab/database/migration_helpers/v2_spec.rb10
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb82
-rw-r--r--spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb6
-rw-r--r--spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb117
-rw-r--r--spec/lib/gitlab/database/migrations/instrumentation_spec.rb6
-rw-r--r--spec/lib/gitlab/database/migrations/lock_retry_mixin_spec.rb12
-rw-r--r--spec/lib/gitlab/database/migrations/runner_spec.rb2
-rw-r--r--spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb15
-rw-r--r--spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb72
-rw-r--r--spec/lib/gitlab/database/partitioning_spec.rb2
-rw-r--r--spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb25
-rw-r--r--spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb2
-rw-r--r--spec/lib/gitlab/database/reindexing_spec.rb21
-rw-r--r--spec/lib/gitlab/database/shared_model_spec.rb2
-rw-r--r--spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb4
-rw-r--r--spec/lib/gitlab/database/with_lock_retries_spec.rb8
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb14
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512_spec.rb36
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb4
-rw-r--r--spec/lib/gitlab/email/handler/service_desk_handler_spec.rb20
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/admin_verify_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/team_short_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/trial_short_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb2
-rw-r--r--spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb2
-rw-r--r--spec/lib/gitlab/error_tracking/logger_spec.rb2
-rw-r--r--spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb20
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb22
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers_spec.rb4
-rw-r--r--spec/lib/gitlab/file_markdown_link_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/form_builders/gitlab_ui_form_builder_spec.rb79
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb3
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb23
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb10
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb4
-rw-r--r--spec/lib/gitlab/git/raw_diff_change_spec.rb2
-rw-r--r--spec/lib/gitlab/git/remote_repository_spec.rb61
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb73
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb58
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb37
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb9
-rw-r--r--spec/lib/gitlab/git_access_spec.rb38
-rw-r--r--spec/lib/gitlab/git_spec.rb1
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb208
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb24
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb42
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb15
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb95
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb14
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb72
-rw-r--r--spec/lib/gitlab/github_import/importer/events/closed_spec.rb16
-rw-r--r--spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb40
-rw-r--r--spec/lib/gitlab/github_import/importer/events/renamed_spec.rb16
-rw-r--r--spec/lib/gitlab/github_import/importer/events/reopened_spec.rb16
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb36
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb122
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_importer_spec.rb22
-rw-r--r--spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/issuable_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/parallel_scheduling_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/representation/issue_event_spec.rb81
-rw-r--r--spec/lib/gitlab/github_import/representation/issue_spec.rb15
-rw-r--r--spec/lib/gitlab/github_import/user_finder_spec.rb66
-rw-r--r--spec/lib/gitlab/global_id/deprecations_spec.rb15
-rw-r--r--spec/lib/gitlab/gpg_spec.rb4
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/token_logger_spec.rb34
-rw-r--r--spec/lib/gitlab/graphql/deprecation_spec.rb69
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb115
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb95
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb415
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb595
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb118
-rw-r--r--spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb135
-rw-r--r--spec/lib/gitlab/graphql/type_name_deprecations_spec.rb52
-rw-r--r--spec/lib/gitlab/graphs/commits_spec.rb2
-rw-r--r--spec/lib/gitlab/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/group_builder_spec.rb3
-rw-r--r--spec/lib/gitlab/hook_data/group_member_builder_spec.rb3
-rw-r--r--spec/lib/gitlab/hook_data/key_builder_spec.rb4
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/project_builder_spec.rb4
-rw-r--r--spec/lib/gitlab/hook_data/project_member_builder_spec.rb3
-rw-r--r--spec/lib/gitlab/hook_data/subgroup_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/user_builder_spec.rb4
-rw-r--r--spec/lib/gitlab/http_io_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb168
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml16
-rw-r--r--spec/lib/gitlab/import_export/base/relation_factory_spec.rb24
-rw-r--r--spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/group/tree_restorer_spec.rb16
-rw-r--r--spec/lib/gitlab/import_export/import_test_coverage_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb48
-rw-r--r--spec/lib/gitlab/import_export/log_util_spec.rb43
-rw-r--r--spec/lib/gitlab/import_export/project/relation_saver_spec.rb125
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb49
-rw-r--r--spec/lib/gitlab/import_export/project/tree_saver_spec.rb27
-rw-r--r--spec/lib/gitlab/import_export/remote_stream_upload_spec.rb232
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml16
-rw-r--r--spec/lib/gitlab/import_export/shared_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/version_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb22
-rw-r--r--spec/lib/gitlab/jira/dvcs_spec.rb6
-rw-r--r--spec/lib/gitlab/jira_import/issues_importer_spec.rb43
-rw-r--r--spec/lib/gitlab/kubernetes/rollout_status_spec.rb2
-rw-r--r--spec/lib/gitlab/mail_room/mail_room_spec.rb4
-rw-r--r--spec/lib/gitlab/memory/jemalloc_spec.rb25
-rw-r--r--spec/lib/gitlab/memory/reports/jemalloc_stats_spec.rb124
-rw-r--r--spec/lib/gitlab/memory/reports_daemon_spec.rb136
-rw-r--r--spec/lib/gitlab/memory/watchdog_spec.rb139
-rw-r--r--spec/lib/gitlab/metrics/background_transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/compressed_json_spec.rb6
-rw-r--r--spec/lib/gitlab/middleware/sidekiq_web_static_spec.rb2
-rw-r--r--spec/lib/gitlab/octokit/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/otp_key_rotator_spec.rb2
-rw-r--r--spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb14
-rw-r--r--spec/lib/gitlab/pagination/keyset_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/response_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/extractor_spec.rb8
-rw-r--r--spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb2
-rw-r--r--spec/lib/gitlab/rack_attack/user_allowlist_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/cache_spec.rb12
-rw-r--r--spec/lib/gitlab/redis/hll_spec.rb8
-rw-r--r--spec/lib/gitlab/redis/multi_store_spec.rb24
-rw-r--r--spec/lib/gitlab/reference_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/regex_spec.rb8
-rw-r--r--spec/lib/gitlab/search/abuse_detection_spec.rb2
-rw-r--r--spec/lib/gitlab/search_context/builder_spec.rb2
-rw-r--r--spec/lib/gitlab/seeder_spec.rb40
-rw-r--r--spec/lib/gitlab/session_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb6
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb16
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb4
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb1
-rw-r--r--spec/lib/gitlab/slash_commands/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/spamcheck/client_spec.rb6
-rw-r--r--spec/lib/gitlab/ssh/commit_spec.rb82
-rw-r--r--spec/lib/gitlab/suggestions/file_suggestion_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb36
-rw-r--r--spec/lib/gitlab/tracking_spec.rb37
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb32
-rw-r--r--spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb4
-rw-r--r--spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb2
-rw-r--r--spec/lib/gitlab/usage/service_ping_report_spec.rb8
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb43
-rw-r--r--spec/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter_spec.rb36
-rw-r--r--spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb21
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb8
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb6
-rw-r--r--spec/lib/gitlab/utils/batch_loader_spec.rb82
-rw-r--r--spec/lib/gitlab/utils/link_header_parser_spec.rb75
-rw-r--r--spec/lib/gitlab/utils/sanitize_node_link_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/strong_memoize_spec.rb139
-rw-r--r--spec/lib/gitlab/utils/usage_data_spec.rb2
-rw-r--r--spec/lib/gitlab/utils_spec.rb4
-rw-r--r--spec/lib/gitlab/verify/uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/version_info_spec.rb19
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb117
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb5
-rw-r--r--spec/lib/marginalia_spec.rb18
-rw-r--r--spec/lib/mattermost/session_spec.rb2
-rw-r--r--spec/lib/microsoft_teams/notifier_spec.rb2
-rw-r--r--spec/lib/release_highlights/validator/entry_spec.rb20
-rw-r--r--spec/lib/release_highlights/validator_spec.rb2
-rw-r--r--spec/lib/security/report_schema_version_matcher_spec.rb44
-rw-r--r--spec/lib/sidebars/groups/menus/group_information_menu_spec.rb8
-rw-r--r--spec/lib/sidebars/menu_spec.rb2
-rw-r--r--spec/lib/tasks/gitlab/metrics_exporter_task_spec.rb2
-rw-r--r--spec/lib/unnested_in_filters/rewriter_spec.rb29
353 files changed, 7888 insertions, 3324 deletions
diff --git a/spec/lib/api/ci/helpers/runner_helpers_spec.rb b/spec/lib/api/ci/helpers/runner_helpers_spec.rb
index c6cdc1732f5..b254c419cbc 100644
--- a/spec/lib/api/ci/helpers/runner_helpers_spec.rb
+++ b/spec/lib/api/ci/helpers/runner_helpers_spec.rb
@@ -71,8 +71,8 @@ RSpec.describe API::Ci::Helpers::Runner do
end
end
- describe '#log_artifact_size' do
- subject { runner_helper.log_artifact_size(artifact) }
+ describe '#log_artifacts_filesize' do
+ subject { runner_helper.log_artifacts_filesize(artifact) }
let(:runner_params) { {} }
let(:artifact) { create(:ci_job_artifact, size: 42) }
diff --git a/spec/lib/api/entities/bulk_imports/entity_spec.rb b/spec/lib/api/entities/bulk_imports/entity_spec.rb
index f91ae1fc5a1..4de85862ab9 100644
--- a/spec/lib/api/entities/bulk_imports/entity_spec.rb
+++ b/spec/lib/api/entities/bulk_imports/entity_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe API::Entities::BulkImports::Entity do
:status,
:source_full_path,
:destination_name,
+ :destination_slug,
:destination_namespace,
:parent_id,
:namespace_id,
diff --git a/spec/lib/api/entities/ci/job_request/image_spec.rb b/spec/lib/api/entities/ci/job_request/image_spec.rb
index 3ab14ffc3ae..fca3b5d3fa9 100644
--- a/spec/lib/api/entities/ci/job_request/image_spec.rb
+++ b/spec/lib/api/entities/ci/job_request/image_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe API::Entities::Ci::JobRequest::Image do
- let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }]}
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }] }
let(:image) { double(name: 'image_name', entrypoint: ['foo'], ports: ports, pull_policy: ['if-not-present']) }
let(:entity) { described_class.new(image) }
diff --git a/spec/lib/api/entities/ci/job_request/port_spec.rb b/spec/lib/api/entities/ci/job_request/port_spec.rb
index 8e0d2cabcfc..3f2ca3275c1 100644
--- a/spec/lib/api/entities/ci/job_request/port_spec.rb
+++ b/spec/lib/api/entities/ci/job_request/port_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe ::API::Entities::Ci::JobRequest::Port do
- let(:port) { double(number: 80, protocol: 'http', name: 'name')}
+ let(:port) { double(number: 80, protocol: 'http', name: 'name') }
let(:entity) { described_class.new(port) }
subject { entity.as_json }
diff --git a/spec/lib/api/entities/ci/job_request/service_spec.rb b/spec/lib/api/entities/ci/job_request/service_spec.rb
index 47c2c4e04c9..86f2120c321 100644
--- a/spec/lib/api/entities/ci/job_request/service_spec.rb
+++ b/spec/lib/api/entities/ci/job_request/service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe API::Entities::Ci::JobRequest::Service do
- let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }]}
+ let(:ports) { [{ number: 80, protocol: 'http', name: 'name' }] }
let(:service) do
instance_double(
::Gitlab::Ci::Build::Image,
diff --git a/spec/lib/api/entities/project_spec.rb b/spec/lib/api/entities/project_spec.rb
index 6b542278fa6..f4073683919 100644
--- a/spec/lib/api/entities/project_spec.rb
+++ b/spec/lib/api/entities/project_spec.rb
@@ -13,6 +13,19 @@ RSpec.describe ::API::Entities::Project do
subject(:json) { entity.as_json }
+ context 'without project feature' do
+ before do
+ project.project_feature.destroy!
+ project.reload
+ end
+
+ it 'returns a response' do
+ expect(json[:issues_access_level]).to be_nil
+ expect(json[:repository_access_level]).to be_nil
+ expect(json[:merge_requests_access_level]).to be_nil
+ end
+ end
+
describe '.service_desk_address' do
before do
allow(project).to receive(:service_desk_enabled?).and_return(true)
diff --git a/spec/lib/api/helpers/authentication_spec.rb b/spec/lib/api/helpers/authentication_spec.rb
index eea5c10d4f8..ac5886fdadd 100644
--- a/spec/lib/api/helpers/authentication_spec.rb
+++ b/spec/lib/api/helpers/authentication_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe API::Helpers::Authentication do
class << cls
def helpers(*modules, &block)
modules.each { |m| include m }
- include Module.new.tap { |m| m.class_eval(&block) } if block_given?
+ include Module.new.tap { |m| m.class_eval(&block) } if block
end
end
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index 23c97e2c0a3..cd41d362d03 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -185,7 +185,7 @@ RSpec.describe API::Helpers do
context 'support for IDs and paths as argument' do
let_it_be(:project) { create(:project) }
- let(:user) { project.first_owner}
+ let(:user) { project.first_owner }
before do
allow(helper).to receive(:current_user).and_return(user)
diff --git a/spec/lib/api/support/git_access_actor_spec.rb b/spec/lib/api/support/git_access_actor_spec.rb
index a09cabf4cd7..e1c800d25a7 100644
--- a/spec/lib/api/support/git_access_actor_spec.rb
+++ b/spec/lib/api/support/git_access_actor_spec.rb
@@ -83,6 +83,36 @@ RSpec.describe API::Support::GitAccessActor do
end
end
+ describe '#deploy_key_or_user' do
+ it 'returns a deploy key when the params contains deploy key' do
+ key = create(:deploy_key)
+ params = { key_id: key.id }
+
+ expect(described_class.from_params(params).deploy_key_or_user).to eq(key)
+ end
+
+ it 'returns a user when the params contains personal key' do
+ key = create(:key)
+ params = { key_id: key.id }
+
+ expect(described_class.from_params(params).deploy_key_or_user).to eq(key.user)
+ end
+
+ it 'returns a user when the params contains user id' do
+ user = create(:user)
+ params = { user_id: user.id }
+
+ expect(described_class.from_params(params).deploy_key_or_user).to eq(user)
+ end
+
+ it 'returns a user when the params contains user name' do
+ user = create(:user)
+ params = { username: user.username }
+
+ expect(described_class.from_params(params).deploy_key_or_user).to eq(user)
+ end
+ end
+
describe '#username' do
context 'when initialized with a User' do
let(:user) { build(:user) }
diff --git a/spec/lib/backup/database_spec.rb b/spec/lib/backup/database_spec.rb
index 53db7f0f149..ed5b34b7f8c 100644
--- a/spec/lib/backup/database_spec.rb
+++ b/spec/lib/backup/database_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Backup::Database do
let(:data) { Rails.root.join("spec/fixtures/pages_empty.tar.gz").to_s }
let(:force) { true }
- subject { described_class.new(progress, force: force) }
+ subject { described_class.new(Gitlab::Database::MAIN_DATABASE_NAME.to_sym, progress, force: force) }
before do
allow(subject).to receive(:pg_restore_cmd).and_return(cmd)
@@ -68,7 +68,7 @@ RSpec.describe Backup::Database do
context 'when the restore command prints errors' do
let(:visible_error) { "This is a test error\n" }
- let(:noise) { "Table projects does not exist\nmust be owner of extension pg_trgm\nWARNING: no privileges could be revoked for public\n" }
+ let(:noise) { "must be owner of extension pg_trgm\nWARNING: no privileges could be revoked for public\n" }
let(:cmd) { %W[#{Gem.ruby} -e $stderr.write("#{noise}#{visible_error}")] }
it 'filters out noise from errors and has a post restore warning' do
@@ -105,5 +105,25 @@ RSpec.describe Backup::Database do
expect(output).to include(%("PGUSER"=>"#{config['username']}")) if config['username']
end
end
+
+ context 'when the source file is missing' do
+ let(:main_database) { described_class.new(Gitlab::Database::MAIN_DATABASE_NAME.to_sym, progress, force: force) }
+ let(:ci_database) { described_class.new(Gitlab::Database::CI_DATABASE_NAME.to_sym, progress, force: force) }
+ let(:missing_file) { Rails.root.join("spec/fixtures/missing_file.tar.gz").to_s }
+
+ it 'main database raises an error about missing source file' do
+ expect(Rake::Task['gitlab:db:drop_tables']).not_to receive(:invoke)
+
+ expect do
+ main_database.restore(missing_file)
+ end.to raise_error(Backup::Error, /Source database file does not exist/)
+ end
+
+ it 'ci database tolerates missing source file' do
+ expect(Rake::Task['gitlab:db:drop_tables']).not_to receive(:invoke)
+ skip_if_multiple_databases_not_setup
+ expect { ci_database.restore(missing_file) }.not_to raise_error
+ end
+ end
end
end
diff --git a/spec/lib/backup/gitaly_backup_spec.rb b/spec/lib/backup/gitaly_backup_spec.rb
index 3a9c4dfe3fb..d427e41026e 100644
--- a/spec/lib/backup/gitaly_backup_spec.rb
+++ b/spec/lib/backup/gitaly_backup_spec.rb
@@ -73,7 +73,7 @@ RSpec.describe Backup::GitalyBackup do
let(:max_parallelism) { 3 }
it 'passes parallel option through' do
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-parallel', '3', '-layout', 'pointer', '-id', backup_id).and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-layout', 'pointer', '-parallel', '3', '-id', backup_id).and_call_original
subject.start(:create, destination, backup_id: backup_id)
subject.finish!
@@ -84,7 +84,7 @@ RSpec.describe Backup::GitalyBackup do
let(:storage_parallelism) { 3 }
it 'passes parallel option through' do
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-parallel-storage', '3', '-layout', 'pointer', '-id', backup_id).and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything, '-layout', 'pointer', '-parallel-storage', '3', '-id', backup_id).and_call_original
subject.start(:create, destination, backup_id: backup_id)
subject.finish!
@@ -103,36 +103,6 @@ RSpec.describe Backup::GitalyBackup do
expect { subject.start(:create, destination, backup_id: backup_id) }.to raise_error(::Backup::Error, 'gitaly-backup binary not found and gitaly_backup_path is not configured')
end
-
- context 'feature flag incremental_repository_backup disabled' do
- before do
- stub_feature_flags(incremental_repository_backup: false)
- end
-
- it 'creates repository bundles', :aggregate_failures do
- # Add data to the wiki, design repositories, and snippets, so they will be included in the dump.
- create(:wiki_page, container: project)
- create(:design, :with_file, issue: create(:issue, project: project))
- project_snippet = create(:project_snippet, :repository, project: project)
- personal_snippet = create(:personal_snippet, :repository, author: project.first_owner)
-
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'create', '-path', anything).and_call_original
-
- subject.start(:create, destination, backup_id: backup_id)
- subject.enqueue(project, Gitlab::GlRepository::PROJECT)
- subject.enqueue(project, Gitlab::GlRepository::WIKI)
- subject.enqueue(project, Gitlab::GlRepository::DESIGN)
- subject.enqueue(personal_snippet, Gitlab::GlRepository::SNIPPET)
- subject.enqueue(project_snippet, Gitlab::GlRepository::SNIPPET)
- subject.finish!
-
- expect(File).to exist(File.join(destination, project.disk_path + '.bundle'))
- expect(File).to exist(File.join(destination, project.disk_path + '.wiki.bundle'))
- expect(File).to exist(File.join(destination, project.disk_path + '.design.bundle'))
- expect(File).to exist(File.join(destination, personal_snippet.disk_path + '.bundle'))
- expect(File).to exist(File.join(destination, project_snippet.disk_path + '.bundle'))
- end
- end
end
context 'hashed storage' do
@@ -208,7 +178,7 @@ RSpec.describe Backup::GitalyBackup do
let(:max_parallelism) { 3 }
it 'passes parallel option through' do
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-parallel', '3', '-layout', 'pointer').and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-parallel', '3').and_call_original
subject.start(:restore, destination, backup_id: backup_id)
subject.finish!
@@ -219,45 +189,13 @@ RSpec.describe Backup::GitalyBackup do
let(:storage_parallelism) { 3 }
it 'passes parallel option through' do
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-parallel-storage', '3', '-layout', 'pointer').and_call_original
+ expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything, '-layout', 'pointer', '-parallel-storage', '3').and_call_original
subject.start(:restore, destination, backup_id: backup_id)
subject.finish!
end
end
- context 'feature flag incremental_repository_backup disabled' do
- before do
- stub_feature_flags(incremental_repository_backup: false)
- end
-
- it 'restores from repository bundles', :aggregate_failures do
- copy_bundle_to_backup_path('project_repo.bundle', project.disk_path + '.bundle')
- copy_bundle_to_backup_path('wiki_repo.bundle', project.disk_path + '.wiki.bundle')
- copy_bundle_to_backup_path('design_repo.bundle', project.disk_path + '.design.bundle')
- copy_bundle_to_backup_path('personal_snippet_repo.bundle', personal_snippet.disk_path + '.bundle')
- copy_bundle_to_backup_path('project_snippet_repo.bundle', project_snippet.disk_path + '.bundle')
-
- expect(Open3).to receive(:popen2).with(expected_env, anything, 'restore', '-path', anything).and_call_original
-
- subject.start(:restore, destination, backup_id: backup_id)
- subject.enqueue(project, Gitlab::GlRepository::PROJECT)
- subject.enqueue(project, Gitlab::GlRepository::WIKI)
- subject.enqueue(project, Gitlab::GlRepository::DESIGN)
- subject.enqueue(personal_snippet, Gitlab::GlRepository::SNIPPET)
- subject.enqueue(project_snippet, Gitlab::GlRepository::SNIPPET)
- subject.finish!
-
- collect_commit_shas = -> (repo) { repo.commits('master', limit: 10).map(&:sha) }
-
- expect(collect_commit_shas.call(project.repository)).to match_array(['393a7d860a5a4c3cc736d7eb00604e3472bb95ec'])
- expect(collect_commit_shas.call(project.wiki.repository)).to match_array(['c74b9948d0088d703ee1fafeddd9ed9add2901ea'])
- expect(collect_commit_shas.call(project.design_repository)).to match_array(['c3cd4d7bd73a51a0f22045c3a4c871c435dc959d'])
- expect(collect_commit_shas.call(personal_snippet.repository)).to match_array(['3b3c067a3bc1d1b695b51e2be30c0f8cf698a06e'])
- expect(collect_commit_shas.call(project_snippet.repository)).to match_array(['6e44ba56a4748be361a841e759c20e421a1651a1'])
- end
- end
-
it 'raises when the exit code not zero' do
expect(subject).to receive(:bin_path).and_return(Gitlab::Utils.which('false'))
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index 519d414f643..f85b005f4d1 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -24,7 +24,17 @@ RSpec.describe Backup::Manager do
describe '#run_create_task' do
let(:enabled) { true }
let(:task) { instance_double(Backup::Task) }
- let(:definitions) { { 'my_task' => Backup::Manager::TaskDefinition.new(task: task, enabled: enabled, destination_path: 'my_task.tar.gz', human_name: 'my task') } }
+ let(:definitions) do
+ {
+ 'my_task' => Backup::Manager::TaskDefinition.new(
+ task: task,
+ enabled: enabled,
+ destination_path: 'my_task.tar.gz',
+ human_name: 'my task',
+ task_group: 'group1'
+ )
+ }
+ end
it 'calls the named task' do
expect(task).to receive(:dump)
@@ -53,6 +63,16 @@ RSpec.describe Backup::Manager do
subject.run_create_task('my_task')
end
end
+
+ describe 'task group skipped' do
+ it 'informs the user' do
+ stub_env('SKIP', 'group1')
+
+ expect(Gitlab::BackupLogger).to receive(:info).with(message: 'Dumping my task ... [SKIPPED]')
+
+ subject.run_create_task('my_task')
+ end
+ end
end
describe '#run_restore_task' do
@@ -164,7 +184,7 @@ RSpec.describe Backup::Manager do
before do
stub_env('INCREMENTAL', incremental_env)
- allow(ActiveRecord::Base.connection).to receive(:reconnect!)
+ allow(ApplicationRecord.connection).to receive(:reconnect!)
allow(Gitlab::BackupLogger).to receive(:info)
allow(Kernel).to receive(:system).and_return(true)
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index e703bbc4927..8748a910003 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Banzai::CrossProjectReference do
let(:including_class) { Class.new.include(described_class).new }
- let(:reference_cache) { Banzai::Filter::References::ReferenceCache.new(including_class, {}, {})}
+ let(:reference_cache) { Banzai::Filter::References::ReferenceCache.new(including_class, {}, {}) }
before do
allow(including_class).to receive(:context).and_return({})
diff --git a/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb b/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb
index a2d35eaa6b6..c581750d2a9 100644
--- a/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb
+++ b/spec/lib/banzai/filter/broadcast_message_placeholders_filter_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Banzai::Filter::BroadcastMessagePlaceholdersFilter do
end
context 'works with empty text' do
- let(:text) {" "}
+ let(:text) { " " }
it { expect(subject).to eq(" ") }
end
@@ -42,13 +42,13 @@ RSpec.describe Banzai::Filter::BroadcastMessagePlaceholdersFilter do
context 'available placeholders' do
context 'replaces the email of the user' do
- let(:text) { "{{email}}"}
+ let(:text) { "{{email}}" }
it { expect(subject).to eq(user.email) }
end
context 'replaces the name of the user' do
- let(:text) { "{{name}}"}
+ let(:text) { "{{name}}" }
it { expect(subject).to eq(user.name) }
end
diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
index f7cb6b92b48..38f9bda57e6 100644
--- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Banzai::Filter::CommitTrailersFilter do
let(:secondary_email) { create(:email, :confirmed) }
let(:user) { create(:user) }
- let(:trailer) { "#{FFaker::Lorem.word}-by:"}
+ let(:trailer) { "#{FFaker::Lorem.word}-by:" }
let(:commit_message) { trailer_line(trailer, user.name, user.email) }
let(:commit_message_html) { commit_html(commit_message) }
diff --git a/spec/lib/banzai/filter/task_list_filter_spec.rb b/spec/lib/banzai/filter/task_list_filter_spec.rb
index c89acd1a643..920904b0f29 100644
--- a/spec/lib/banzai/filter/task_list_filter_spec.rb
+++ b/spec/lib/banzai/filter/task_list_filter_spec.rb
@@ -10,4 +10,38 @@ RSpec.describe Banzai::Filter::TaskListFilter do
expect(doc.xpath('.//li//task-button').count).to eq(2)
end
+
+ describe 'inapplicable list items' do
+ shared_examples 'a valid inapplicable task list item' do |html|
+ it "behaves correctly for `#{html}`" do
+ doc = filter("<ul><li>#{html}</li></ul>")
+
+ expect(doc.css('li.inapplicable input[data-inapplicable]').count).to eq(1)
+ expect(doc.css('li.inapplicable > s').count).to eq(1)
+ end
+ end
+
+ shared_examples 'an invalid inapplicable task list item' do |html|
+ it "does nothing for `#{html}`" do
+ doc = filter("<ul><li>#{html}</li></ul>")
+
+ expect(doc.css('li.inapplicable input[data-inapplicable]').count).to eq(0)
+ end
+ end
+
+ it_behaves_like 'a valid inapplicable task list item', '[~] foobar'
+ it_behaves_like 'a valid inapplicable task list item', '[~] foo <em>bar</em>'
+ it_behaves_like 'an invalid inapplicable task list item', '[ ] foobar'
+ it_behaves_like 'an invalid inapplicable task list item', '[x] foobar'
+ it_behaves_like 'an invalid inapplicable task list item', 'foo [~] bar'
+
+ it 'does not wrap a sublist with <s>' do
+ html = '[~] foo <em>bar</em>\n<ol><li>sublist</li></ol>'
+ doc = filter("<ul><li>#{html}</li></ul>")
+
+ expect(doc.to_html).to include('<s>foo <em>bar</em>\n</s>')
+ expect(doc.css('li.inapplicable input[data-inapplicable]').count).to eq(1)
+ expect(doc.css('li.inapplicable > s').count).to eq(1)
+ end
+ end
end
diff --git a/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb b/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb
index 09d2919c6c4..4bccae04fda 100644
--- a/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb
@@ -10,9 +10,9 @@ RSpec.describe Banzai::Pipeline::IncidentManagement::TimelineEventPipeline do
expect(described_class.filters).to eq(
[
*Banzai::Pipeline::PlainMarkdownPipeline.filters,
+ Banzai::Filter::SanitizationFilter,
*Banzai::Pipeline::GfmPipeline.reference_filters,
Banzai::Filter::EmojiFilter,
- Banzai::Filter::SanitizationFilter,
Banzai::Filter::ExternalLinkFilter,
Banzai::Filter::ImageLinkFilter
]
@@ -62,7 +62,32 @@ RSpec.describe Banzai::Pipeline::IncidentManagement::TimelineEventPipeline do
context 'when markdown contains emojis' do
let(:markdown) { ':+1:👍' }
- it { is_expected.to eq('<p>👍👍</p>') }
+ it 'renders emojis wrapped in <gl-emoji> tag' do
+ # rubocop:disable Layout/LineLength
+ is_expected.to eq(
+ %q(<p><gl-emoji title="thumbs up sign" data-name="thumbsup" data-unicode-version="6.0">👍</gl-emoji><gl-emoji title="thumbs up sign" data-name="thumbsup" data-unicode-version="6.0">👍</gl-emoji></p>)
+ )
+ # rubocop:enable Layout/LineLength
+ end
+ end
+
+ context 'when markdown contains labels' do
+ let(:label) { create(:label, project: project, title: 'backend') }
+ let(:markdown) { %Q(~"#{label.name}" ~unknown) }
+
+ it 'replaces existing label to a link' do
+ # rubocop:disable Layout/LineLength
+ is_expected.to match(
+ %r(<p>.+<a href=\"[\w/]+-/issues\?label_name=#{label.name}\".+style=\"background-color: #\d{6}\".*>#{label.name}</span></a></span> ~unknown</p>)
+ )
+ # rubocop:enable Layout/LineLength
+ end
+ end
+
+ context 'when markdown contains table' do
+ let(:markdown) { '<table><tr><th>table head</th><tr><tr><td>table content</td></tr></table>' }
+
+ it { is_expected.to eq('table headtable content') }
end
context 'when markdown contains a reference to an issue' do
diff --git a/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb b/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb
index 80392fe264f..536f2a67415 100644
--- a/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb
@@ -11,9 +11,9 @@ RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline do
it 'converts all reference punctuation to literals' do
reference_chars = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS
- markdown = reference_chars.split('').map {|char| char.prepend("\\") }.join
+ markdown = reference_chars.split('').map { |char| char.prepend("\\") }.join
punctuation = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS.split('')
- punctuation = punctuation.delete_if {|char| char == '&' }
+ punctuation = punctuation.delete_if { |char| char == '&' }
punctuation << '&amp;'
result = described_class.call(markdown, project: project)
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index d487268da78..ae9cf4c5068 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -104,7 +104,7 @@ RSpec.describe Banzai::Renderer do
describe '#post_process' do
let(:context_options) { {} }
- let(:html) { 'Consequatur aperiam et nesciunt modi aut assumenda quo id. '}
+ let(:html) { 'Consequatur aperiam et nesciunt modi aut assumenda quo id. ' }
let(:post_processed_html) { double(html_safe: 'safe doc') }
let(:doc) { double(to_html: post_processed_html) }
diff --git a/spec/lib/bitbucket_server/connection_spec.rb b/spec/lib/bitbucket_server/connection_spec.rb
index 873eded58d7..ae73955e1d1 100644
--- a/spec/lib/bitbucket_server/connection_spec.rb
+++ b/spec/lib/bitbucket_server/connection_spec.rb
@@ -64,7 +64,7 @@ RSpec.describe BitbucketServer::Connection do
context 'branch API' do
let(:branch_path) { '/projects/foo/repos/bar/branches' }
let(:branch_url) { 'https://test:7990/rest/branch-utils/1.0/projects/foo/repos/bar/branches' }
- let(:path) { }
+ let(:path) {}
it 'returns JSON body' do
WebMock.stub_request(:delete, branch_url).with(headers: headers).to_return(body: payload.to_json, status: 200, headers: headers)
diff --git a/spec/lib/bulk_imports/clients/http_spec.rb b/spec/lib/bulk_imports/clients/http_spec.rb
index c9730e03311..75c5f363b1f 100644
--- a/spec/lib/bulk_imports/clients/http_spec.rb
+++ b/spec/lib/bulk_imports/clients/http_spec.rb
@@ -56,15 +56,17 @@ RSpec.describe BulkImports::Clients::HTTP do
[
'http://gitlab.example/api/v4/resource',
hash_including(
- follow_redirects: false,
query: {
page: described_class::DEFAULT_PAGE,
- per_page: described_class::DEFAULT_PER_PAGE
+ per_page: described_class::DEFAULT_PER_PAGE,
+ private_token: token
},
headers: {
- 'Content-Type' => 'application/json',
- 'Authorization' => "Bearer #{token}"
- }
+ 'Content-Type' => 'application/json'
+ },
+ follow_redirects: true,
+ resend_on_redirect: false,
+ limit: 2
)
]
end
@@ -106,12 +108,13 @@ RSpec.describe BulkImports::Clients::HTTP do
def stub_http_get(path, query, response)
uri = "http://gitlab.example/api/v4/#{path}"
params = {
- follow_redirects: false,
- headers: {
- "Authorization" => "Bearer token",
- "Content-Type" => "application/json"
- }
- }.merge(query: query)
+ headers: { "Content-Type" => "application/json" },
+ query: { private_token: token },
+ follow_redirects: true,
+ resend_on_redirect: false,
+ limit: 2
+ }
+ params[:query] = params[:query].merge(query)
allow(Gitlab::HTTP).to receive(:get).with(uri, params).and_return(response)
end
@@ -127,11 +130,17 @@ RSpec.describe BulkImports::Clients::HTTP do
'http://gitlab.example/api/v4/resource',
hash_including(
body: {},
- follow_redirects: false,
headers: {
- 'Content-Type' => 'application/json',
- 'Authorization' => "Bearer #{token}"
- }
+ 'Content-Type' => 'application/json'
+ },
+ query: {
+ page: described_class::DEFAULT_PAGE,
+ per_page: described_class::DEFAULT_PER_PAGE,
+ private_token: token
+ },
+ follow_redirects: true,
+ resend_on_redirect: false,
+ limit: 2
)
]
end
@@ -146,11 +155,17 @@ RSpec.describe BulkImports::Clients::HTTP do
[
'http://gitlab.example/api/v4/resource',
hash_including(
- follow_redirects: false,
headers: {
- 'Content-Type' => 'application/json',
- 'Authorization' => "Bearer #{token}"
- }
+ 'Content-Type' => 'application/json'
+ },
+ query: {
+ page: described_class::DEFAULT_PAGE,
+ per_page: described_class::DEFAULT_PER_PAGE,
+ private_token: token
+ },
+ follow_redirects: true,
+ resend_on_redirect: false,
+ limit: 2
)
]
end
@@ -164,9 +179,16 @@ RSpec.describe BulkImports::Clients::HTTP do
hash_including(
stream_body: true,
headers: {
- 'Content-Type' => 'application/json',
- 'Authorization' => "Bearer #{token}"
- }
+ 'Content-Type' => 'application/json'
+ },
+ query: {
+ page: described_class::DEFAULT_PAGE,
+ per_page: described_class::DEFAULT_PER_PAGE,
+ private_token: token
+ },
+ follow_redirects: true,
+ resend_on_redirect: false,
+ limit: 2
)
]
diff --git a/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb
index b769aa4af5a..f0b461e518e 100644
--- a/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb
@@ -11,8 +11,8 @@ RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline do
let(:tracker) { create(:bulk_import_tracker, entity: entity) }
let(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:lfs_dir_path) { tmpdir }
- let(:lfs_json_file_path) { File.join(lfs_dir_path, 'lfs_objects.json')}
- let(:lfs_file_path) { File.join(lfs_dir_path, oid)}
+ let(:lfs_json_file_path) { File.join(lfs_dir_path, 'lfs_objects.json') }
+ let(:lfs_file_path) { File.join(lfs_dir_path, oid) }
subject(:pipeline) { described_class.new(context) }
diff --git a/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb
index 9d43bb3ebfb..f650e931dc7 100644
--- a/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline do
let(:tmpdir) { Dir.mktmpdir }
let(:uploads_dir_path) { File.join(tmpdir, '72a497a02fe3ee09edae2ed06d390038') }
- let(:upload_file_path) { File.join(uploads_dir_path, 'upload.txt')}
+ let(:upload_file_path) { File.join(uploads_dir_path, 'upload.txt') }
let(:tracker) { create(:bulk_import_tracker, entity: entity) }
let(:context) { BulkImports::Pipeline::Context.new(tracker) }
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 d775cf6b026..896af865c56 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
@@ -13,7 +13,7 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
:bulk_import_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
- destination_name: 'destination-name-path',
+ destination_slug: 'destination-slug-path',
destination_namespace: parent.full_path
)
end
@@ -41,14 +41,14 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
'name' => 'Name',
'description' => 'Description',
'parent_id' => parent.id,
- 'path' => 'destination-name-path'
+ 'path' => 'destination-slug-path'
})
end
- it 'transforms path from destination_name' do
+ it 'transforms path from destination_slug' do
transformed_data = subject.transform(context, data)
- expect(transformed_data['path']).to eq(entity.destination_name)
+ expect(transformed_data['path']).to eq(entity.destination_slug)
end
it 'removes full path' do
diff --git a/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb
index 2633598b48d..a376cdd712c 100644
--- a/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb
@@ -77,7 +77,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
}
end
- let(:attributes) {{ 'links' => [link] }}
+ let(:attributes) { { 'links' => [link] } }
it 'restores release links' do
pipeline.run
@@ -106,7 +106,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
}
end
- let(:attributes) {{ 'milestone_releases' => [{ 'milestone' => milestone }] }}
+ let(:attributes) { { 'milestone_releases' => [{ 'milestone' => milestone }] } }
it 'restores release milestone' do
pipeline.run
@@ -133,7 +133,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
end
context 'when release is historical' do
- let(:attributes) {{ 'released_at' => '2018-12-26T10:17:14.621Z' }}
+ let(:attributes) { { 'released_at' => '2018-12-26T10:17:14.621Z' } }
it 'does not create release evidence' do
expect(::Releases::CreateEvidenceWorker).not_to receive(:perform_async)
@@ -143,7 +143,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline do
end
context 'when release is upcoming' do
- let(:attributes) {{ 'released_at' => Time.zone.now + 30.days }}
+ let(:attributes) { { 'released_at' => Time.zone.now + 30.days } }
it 'does not create release evidence' do
expect(::Releases::CreateEvidenceWorker).not_to receive(:perform_async)
diff --git a/spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb
index 9897e74ec7b..4d12b49e2c0 100644
--- a/spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe BulkImports::Projects::Pipelines::SnippetsRepositoryPipeline do
let(:project) { create(:project) }
let(:bulk_import) { create(:bulk_import, user: user) }
let(:bulk_import_configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) }
- let!(:matched_snippet) { create(:snippet, project: project, created_at: "1981-12-13T23:59:59Z")}
+ let!(:matched_snippet) { create(:snippet, project: project, created_at: "1981-12-13T23:59:59Z") }
let(:entity) do
create(
:bulk_import_entity,
diff --git a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb
index a1d77b9732d..c1c4d0bf0db 100644
--- a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb
+++ b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
source_type: :project_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
- destination_name: 'Destination Project Name',
+ destination_slug: 'Destination Project Name',
destination_namespace: destination_group.full_path
)
end
@@ -32,12 +32,12 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
subject(:transformed_data) { described_class.new.transform(context, data) }
- it 'transforms name to destination name' do
- expect(transformed_data[:name]).to eq(entity.destination_name)
+ it 'transforms name to destination slug' do
+ expect(transformed_data[:name]).to eq(entity.destination_slug)
end
it 'adds path as parameterized name' do
- expect(transformed_data[:path]).to eq(entity.destination_name.parameterize)
+ expect(transformed_data[:path]).to eq(entity.destination_slug.parameterize)
end
it 'transforms visibility level' do
@@ -65,7 +65,7 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
source_type: :project_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
- destination_name: 'Destination Project Name',
+ destination_slug: 'Destination Project Name',
destination_namespace: ''
)
diff --git a/spec/lib/container_registry/gitlab_api_client_spec.rb b/spec/lib/container_registry/gitlab_api_client_spec.rb
index f2c627734a3..7836d8706f6 100644
--- a/spec/lib/container_registry/gitlab_api_client_spec.rb
+++ b/spec/lib/container_registry/gitlab_api_client_spec.rb
@@ -212,6 +212,105 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
end
end
+ describe '#tags' do
+ let(:path) { 'namespace/path/to/repository' }
+ let(:page_size) { 100 }
+ let(:last) { nil }
+ let(:response) do
+ [
+ {
+ name: '0.1.0',
+ digest: 'sha256:1234567890',
+ media_type: 'application/vnd.oci.image.manifest.v1+json',
+ size_bytes: 1234567890,
+ created_at: 5.minutes.ago
+ },
+ {
+ name: 'latest',
+ digest: 'sha256:1234567892',
+ media_type: 'application/vnd.oci.image.manifest.v1+json',
+ size_bytes: 1234567892,
+ created_at: 10.minutes.ago
+ }
+ ]
+ end
+
+ subject { client.tags(path, page_size: page_size, last: last) }
+
+ context 'with valid parameters' do
+ let(:expected) do
+ {
+ pagination: {},
+ response_body: ::Gitlab::Json.parse(response.to_json)
+ }
+ end
+
+ before do
+ stub_tags(path, page_size: page_size, respond_with: response)
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+
+ context 'with a response with a link header' do
+ let(:next_page_url) { 'http://sandbox.org/test?last=b' }
+ let(:expected) do
+ {
+ pagination: { next: { uri: URI(next_page_url) } },
+ response_body: ::Gitlab::Json.parse(response.to_json)
+ }
+ end
+
+ before do
+ stub_tags(path, page_size: page_size, next_page_url: next_page_url, respond_with: response)
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+
+ context 'with a large page size set' do
+ let(:page_size) { described_class::MAX_TAGS_PAGE_SIZE + 1000 }
+
+ let(:expected) do
+ {
+ pagination: {},
+ response_body: ::Gitlab::Json.parse(response.to_json)
+ }
+ end
+
+ before do
+ stub_tags(path, page_size: described_class::MAX_TAGS_PAGE_SIZE, respond_with: response)
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+
+ context 'with a last parameter set' do
+ let(:last) { 'test' }
+
+ let(:expected) do
+ {
+ pagination: {},
+ response_body: ::Gitlab::Json.parse(response.to_json)
+ }
+ end
+
+ before do
+ stub_tags(path, page_size: page_size, last: last, respond_with: response)
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+
+ context 'with non successful response' do
+ before do
+ stub_tags(path, page_size: page_size, status_code: 404)
+ end
+
+ it { is_expected.to eq({}) }
+ end
+ end
+
describe '.supports_gitlab_api?' do
subject { described_class.supports_gitlab_api? }
@@ -389,4 +488,30 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
.with(headers: headers)
.to_return(status: status_code, body: respond_with.to_json, headers: { 'Content-Type' => described_class::JSON_TYPE })
end
+
+ def stub_tags(path, page_size: nil, last: nil, next_page_url: nil, status_code: 200, respond_with: {})
+ params = { n: page_size, last: last }.compact
+
+ url = "#{registry_api_url}/gitlab/v1/repositories/#{path}/tags/list/"
+
+ if params.present?
+ url += "?#{params.map { |param, val| "#{param}=#{val}" }.join('&')}"
+ end
+
+ request_headers = { 'Accept' => described_class::JSON_TYPE }
+ request_headers['Authorization'] = "bearer #{token}" if token
+
+ response_headers = { 'Content-Type' => described_class::JSON_TYPE }
+ if next_page_url
+ response_headers['Link'] = "<#{next_page_url}>; rel=\"next\""
+ end
+
+ stub_request(:get, url)
+ .with(headers: request_headers)
+ .to_return(
+ status: status_code,
+ body: respond_with.to_json,
+ headers: response_headers
+ )
+ end
end
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index 9b931ab6dbc..190ddef0cd5 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -205,6 +205,41 @@ RSpec.describe ContainerRegistry::Tag do
it_behaves_like 'a processable'
end
+
+ describe '#force_created_at_from_iso8601' do
+ subject { tag.force_created_at_from_iso8601(input) }
+
+ shared_examples 'setting and caching the created_at value' do
+ it 'sets and caches the created_at value' do
+ expect(tag).not_to receive(:config)
+
+ subject
+
+ expect(tag.created_at).to eq(expected_value)
+ end
+ end
+
+ context 'with a valid input' do
+ let(:input) { 2.days.ago.iso8601 }
+ let(:expected_value) { DateTime.iso8601(input) }
+
+ it_behaves_like 'setting and caching the created_at value'
+ end
+
+ context 'with a nil input' do
+ let(:input) { nil }
+ let(:expected_value) { nil }
+
+ it_behaves_like 'setting and caching the created_at value'
+ end
+
+ context 'with an invalid input' do
+ let(:input) { 'not a timestamp' }
+ let(:expected_value) { nil }
+
+ it_behaves_like 'setting and caching the created_at value'
+ end
+ end
end
end
end
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 4db3f04717b..56e0b4bca30 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -491,8 +491,8 @@ RSpec.describe Feature, stub_feature_flags: false do
end
shared_examples_for 'logging' do
- let(:expected_action) { }
- let(:expected_extra) { }
+ let(:expected_action) {}
+ let(:expected_extra) {}
it 'logs the event' do
expect(Feature.logger).to receive(:info).with(key: key, action: expected_action, **expected_extra)
diff --git a/spec/lib/gitlab/alert_management/payload/base_spec.rb b/spec/lib/gitlab/alert_management/payload/base_spec.rb
index d3c1a96253c..ad2a3c7b462 100644
--- a/spec/lib/gitlab/alert_management/payload/base_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/base_spec.rb
@@ -228,6 +228,46 @@ RSpec.describe Gitlab::AlertManagement::Payload::Base do
it { is_expected.to eq({ hosts: shortened_hosts, project_id: project.id }) }
end
end
+
+ context 'with present, non-string values for string fields' do
+ let_it_be(:stubs) do
+ {
+ description: { "description" => "description" },
+ monitoring_tool: ['datadog', 5],
+ service: 4356875,
+ title: true
+ }
+ end
+
+ before do
+ allow(parsed_payload).to receive_messages(stubs)
+ end
+
+ it 'casts values to strings' do
+ is_expected.to eq({
+ description: "{\"description\"=>\"description\"}",
+ monitoring_tool: "[\"datadog\", 5]",
+ service: '4356875',
+ project_id: project.id,
+ title: "true"
+ })
+ end
+ end
+
+ context 'with blank values for string fields' do
+ let_it_be(:stubs) do
+ {
+ description: nil,
+ monitoring_tool: '',
+ service: {},
+ title: []
+ }
+ end
+
+ it 'leaves the fields blank' do
+ is_expected.to eq({ project_id: project.id })
+ end
+ end
end
describe '#gitlab_fingerprint' do
diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb
index f9e18a65af4..8b2a228b935 100644
--- a/spec/lib/gitlab/application_context_spec.rb
+++ b/spec/lib/gitlab/application_context_spec.rb
@@ -52,7 +52,7 @@ RSpec.describe Gitlab::ApplicationContext do
end
it 'raises an error when passing invalid options' do
- expect { described_class.push(no: 'option')}.to raise_error(ArgumentError)
+ expect { described_class.push(no: 'option') }.to raise_error(ArgumentError)
end
end
diff --git a/spec/lib/gitlab/application_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb
index 177ce1134d8..41e79f811fa 100644
--- a/spec/lib/gitlab/application_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/application_rate_limiter_spec.rb
@@ -111,23 +111,35 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting
shared_examples 'throttles based on key and scope' do
let(:start_time) { Time.current.beginning_of_hour }
- it 'returns true when threshold is exceeded' do
+ let(:threshold) { nil }
+ let(:interval) { nil }
+
+ it 'returns true when threshold is exceeded', :aggregate_failures do
travel_to(start_time) do
- expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
+ expect(subject.throttled?(
+ :test_action, scope: scope, threshold: threshold, interval: interval)
+ ).to eq(false)
end
travel_to(start_time + 1.minute) do
- expect(subject.throttled?(:test_action, scope: scope)).to eq(true)
+ expect(subject.throttled?(
+ :test_action, scope: scope, threshold: threshold, interval: interval)
+ ).to eq(true)
# Assert that it does not affect other actions or scope
expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
- expect(subject.throttled?(:test_action, scope: [user])).to eq(false)
+
+ expect(subject.throttled?(
+ :test_action, scope: [user], threshold: threshold, interval: interval)
+ ).to eq(false)
end
end
- it 'returns false when interval has elapsed' do
+ it 'returns false when interval has elapsed', :aggregate_failures do
travel_to(start_time) do
- expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
+ expect(subject.throttled?(
+ :test_action, scope: scope, threshold: threshold, interval: interval)
+ ).to eq(false)
# another_action has a threshold of 2 so we simulate 2 requests
expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
@@ -135,21 +147,34 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting
end
travel_to(start_time + 2.minutes) do
- expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
+ expect(subject.throttled?(
+ :test_action, scope: scope, threshold: threshold, interval: interval)
+ ).to eq(false)
# Assert that another_action has its own interval that hasn't elapsed
expect(subject.throttled?(:another_action, scope: scope)).to eq(true)
end
end
- it 'allows peeking at the current state without changing its value' do
+ it 'allows peeking at the current state without changing its value', :aggregate_failures do
travel_to(start_time) do
- expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
+ expect(subject.throttled?(
+ :test_action, scope: scope, threshold: threshold, interval: interval)
+ ).to eq(false)
+
2.times do
- expect(subject.throttled?(:test_action, scope: scope, peek: true)).to eq(false)
+ expect(subject.throttled?(
+ :test_action, scope: scope, threshold: threshold, interval: interval, peek: true)
+ ).to eq(false)
end
- expect(subject.throttled?(:test_action, scope: scope)).to eq(true)
- expect(subject.throttled?(:test_action, scope: scope, peek: true)).to eq(true)
+
+ expect(subject.throttled?(
+ :test_action, scope: scope, threshold: threshold, interval: interval)
+ ).to eq(true)
+
+ expect(subject.throttled?(
+ :test_action, scope: scope, peek: true, threshold: threshold, interval: interval)
+ ).to eq(true)
end
end
end
@@ -165,6 +190,28 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting
it_behaves_like 'throttles based on key and scope'
end
+
+ context 'when threshold and interval get overwritten from rate_limits' do
+ let(:rate_limits) do
+ {
+ test_action: {
+ threshold: 0,
+ interval: 0
+ },
+ another_action: {
+ threshold: -> { 2 },
+ interval: -> { 3.minutes }
+ }
+ }
+ end
+
+ let(:scope) { [user, project] }
+
+ it_behaves_like 'throttles based on key and scope' do
+ let(:threshold) { 1 }
+ let(:interval) { 2.minutes }
+ end
+ end
end
describe '.peek' do
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index bfea1315d90..b2bce2076b0 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -791,7 +791,7 @@ module Gitlab
end
context 'when the file does not exist' do
- it { is_expected.to include("[ERROR: include::#{include_path}[] - unresolved directive]")}
+ it { is_expected.to include("[ERROR: include::#{include_path}[] - unresolved directive]") }
end
end
diff --git a/spec/lib/gitlab/audit/auditor_spec.rb b/spec/lib/gitlab/audit/auditor_spec.rb
new file mode 100644
index 00000000000..fc5917ca583
--- /dev/null
+++ b/spec/lib/gitlab/audit/auditor_spec.rb
@@ -0,0 +1,258 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Audit::Auditor do
+ let(:name) { 'audit_operation' }
+ let(:author) { create(:user) }
+ let(:group) { create(:group) }
+ let(:provider) { 'standard' }
+ let(:context) do
+ { name: name,
+ author: author,
+ scope: group,
+ target: group,
+ authentication_event: true,
+ authentication_provider: provider,
+ message: "Signed in using standard authentication" }
+ end
+
+ let(:logger) { instance_spy(Gitlab::AuditJsonLogger) }
+
+ subject(:auditor) { described_class }
+
+ describe '.audit' do
+ context 'when authentication event' do
+ let(:audit!) { auditor.audit(context) }
+
+ it 'creates an authentication event' do
+ expect(AuthenticationEvent).to receive(:new).with(
+ {
+ user: author,
+ user_name: author.name,
+ ip_address: author.current_sign_in_ip,
+ result: AuthenticationEvent.results[:success],
+ provider: provider
+ }
+ ).and_call_original
+
+ audit!
+ end
+
+ it 'logs audit events to database', :aggregate_failures do
+ freeze_time do
+ audit!
+
+ audit_event = AuditEvent.last
+
+ expect(audit_event.author_id).to eq(author.id)
+ expect(audit_event.entity_id).to eq(group.id)
+ expect(audit_event.entity_type).to eq(group.class.name)
+ expect(audit_event.created_at).to eq(Time.zone.now)
+ expect(audit_event.details[:target_id]).to eq(group.id)
+ expect(audit_event.details[:target_type]).to eq(group.class.name)
+ end
+ end
+
+ it 'logs audit events to file' do
+ expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger)
+
+ audit!
+
+ expect(logger).to have_received(:info).with(
+ hash_including(
+ 'author_id' => author.id,
+ 'author_name' => author.name,
+ 'entity_id' => group.id,
+ 'entity_type' => group.class.name,
+ 'details' => kind_of(Hash)
+ )
+ )
+ end
+
+ context 'when overriding the create datetime' do
+ let(:context) do
+ { name: name,
+ author: author,
+ scope: group,
+ target: group,
+ created_at: 3.weeks.ago,
+ authentication_event: true,
+ authentication_provider: provider,
+ message: "Signed in using standard authentication" }
+ end
+
+ it 'logs audit events to database', :aggregate_failures do
+ freeze_time do
+ audit!
+
+ audit_event = AuditEvent.last
+
+ expect(audit_event.author_id).to eq(author.id)
+ expect(audit_event.entity_id).to eq(group.id)
+ expect(audit_event.entity_type).to eq(group.class.name)
+ expect(audit_event.created_at).to eq(3.weeks.ago)
+ expect(audit_event.details[:target_id]).to eq(group.id)
+ expect(audit_event.details[:target_type]).to eq(group.class.name)
+ end
+ end
+
+ it 'logs audit events to file' do
+ freeze_time do
+ expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger)
+
+ audit!
+
+ expect(logger).to have_received(:info).with(
+ hash_including(
+ 'author_id' => author.id,
+ 'author_name' => author.name,
+ 'entity_id' => group.id,
+ 'entity_type' => group.class.name,
+ 'details' => kind_of(Hash),
+ 'created_at' => 3.weeks.ago.iso8601(3)
+ )
+ )
+ end
+ end
+ end
+
+ context 'when overriding the additional_details' do
+ additional_details = { action: :custom, from: false, to: true }
+ let(:context) do
+ { name: name,
+ author: author,
+ scope: group,
+ target: group,
+ created_at: Time.zone.now,
+ additional_details: additional_details,
+ authentication_event: true,
+ authentication_provider: provider,
+ message: "Signed in using standard authentication" }
+ end
+
+ it 'logs audit events to database' do
+ freeze_time do
+ audit!
+
+ expect(AuditEvent.last.details).to include(additional_details)
+ end
+ end
+
+ it 'logs audit events to file' do
+ freeze_time do
+ expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger)
+
+ audit!
+
+ expect(logger).to have_received(:info).with(
+ hash_including(
+ 'details' => hash_including('action' => 'custom', 'from' => 'false', 'to' => 'true'),
+ 'action' => 'custom',
+ 'from' => 'false',
+ 'to' => 'true'
+ )
+ )
+ end
+ end
+ end
+
+ context 'when overriding the target_details' do
+ target_details = "this is my target details"
+ let(:context) do
+ {
+ name: name,
+ author: author,
+ scope: group,
+ target: group,
+ created_at: Time.zone.now,
+ target_details: target_details,
+ authentication_event: true,
+ authentication_provider: provider,
+ message: "Signed in using standard authentication"
+ }
+ end
+
+ it 'logs audit events to database' do
+ freeze_time do
+ audit!
+
+ audit_event = AuditEvent.last
+ expect(audit_event.details).to include({ target_details: target_details })
+ expect(audit_event.target_details).to eq(target_details)
+ end
+ end
+
+ it 'logs audit events to file' do
+ freeze_time do
+ expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger)
+
+ audit!
+
+ expect(logger).to have_received(:info).with(
+ hash_including(
+ 'details' => hash_including('target_details' => target_details),
+ 'target_details' => target_details
+ )
+ )
+ end
+ end
+ end
+ end
+
+ context 'when authentication event is false' do
+ let(:context) do
+ { name: name, author: author, scope: group,
+ target: group, authentication_event: false, message: "sample message" }
+ end
+
+ it 'does not create an authentication event' do
+ expect { auditor.audit(context) }.not_to change(AuthenticationEvent, :count)
+ end
+ end
+
+ context 'when authentication event is invalid' do
+ let(:audit!) { auditor.audit(context) }
+
+ before do
+ allow(AuthenticationEvent).to receive(:new).and_raise(ActiveRecord::RecordInvalid)
+ allow(Gitlab::ErrorTracking).to receive(:track_exception)
+ end
+
+ it 'tracks error' do
+ audit!
+
+ expect(Gitlab::ErrorTracking).to have_received(:track_exception).with(
+ kind_of(ActiveRecord::RecordInvalid),
+ { audit_operation: name }
+ )
+ end
+
+ it 'does not throw exception' do
+ expect { auditor.audit(context) }.not_to raise_exception
+ end
+ end
+
+ context 'when audit events are invalid' do
+ let(:audit!) { auditor.audit(context) }
+
+ before do
+ allow(AuditEvent).to receive(:bulk_insert!).and_raise(ActiveRecord::RecordInvalid)
+ allow(Gitlab::ErrorTracking).to receive(:track_exception)
+ end
+
+ it 'tracks error' do
+ audit!
+
+ expect(Gitlab::ErrorTracking).to have_received(:track_exception).with(
+ kind_of(ActiveRecord::RecordInvalid),
+ { audit_operation: name }
+ )
+ end
+
+ it 'does not throw exception' do
+ expect { auditor.audit(context) }.not_to raise_exception
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/audit/ci_runner_token_author_spec.rb b/spec/lib/gitlab/audit/ci_runner_token_author_spec.rb
index f55e1b44936..89664c57084 100644
--- a/spec/lib/gitlab/audit/ci_runner_token_author_spec.rb
+++ b/spec/lib/gitlab/audit/ci_runner_token_author_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Audit::CiRunnerTokenAuthor do
describe '.initialize' do
subject { described_class.new(audit_event) }
- let(:details) { }
+ let(:details) {}
let(:audit_event) { instance_double(AuditEvent, details: details, entity_type: 'Project', entity_path: 'd/e') }
context 'with runner_authentication_token' do
diff --git a/spec/lib/gitlab/audit/deploy_key_author_spec.rb b/spec/lib/gitlab/audit/deploy_key_author_spec.rb
new file mode 100644
index 00000000000..72444f77c91
--- /dev/null
+++ b/spec/lib/gitlab/audit/deploy_key_author_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Audit::DeployKeyAuthor do
+ describe '#initialize' do
+ it 'sets correct attributes' do
+ expect(described_class.new(name: 'Lorem deploy key'))
+ .to have_attributes(id: -3, name: 'Lorem deploy key')
+ end
+
+ it 'sets default name when it is not provided' do
+ expect(described_class.new)
+ .to have_attributes(id: -3, name: 'Deploy Key')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/audit/null_author_spec.rb b/spec/lib/gitlab/audit/null_author_spec.rb
index 2045139a5f7..e2f71a34534 100644
--- a/spec/lib/gitlab/audit/null_author_spec.rb
+++ b/spec/lib/gitlab/audit/null_author_spec.rb
@@ -57,6 +57,15 @@ RSpec.describe Gitlab::Audit::NullAuthor do
expect(subject.for(-2, audit_event)).to be_a(Gitlab::Audit::DeployTokenAuthor)
expect(subject.for(-2, audit_event)).to have_attributes(id: -2, name: 'Test deploy token')
end
+
+ it 'returns DeployKeyAuthor when id equals -3', :aggregate_failures do
+ allow(audit_event).to receive(:[]).with(:author_name).and_return('Test deploy key')
+ allow(audit_event).to receive(:details).and_return({})
+ allow(audit_event).to receive(:target_type)
+
+ expect(subject.for(-3, audit_event)).to be_a(Gitlab::Audit::DeployKeyAuthor)
+ expect(subject.for(-3, audit_event)).to have_attributes(id: -3, name: 'Test deploy key')
+ end
end
describe '#current_sign_in_ip' do
diff --git a/spec/lib/gitlab/audit/null_target_spec.rb b/spec/lib/gitlab/audit/null_target_spec.rb
new file mode 100644
index 00000000000..f192e0cd8db
--- /dev/null
+++ b/spec/lib/gitlab/audit/null_target_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Audit::NullTarget do
+ subject { described_class.new }
+
+ describe '#id' do
+ it 'returns nil' do
+ expect(subject.id).to eq(nil)
+ end
+ end
+
+ describe '#type' do
+ it 'returns nil' do
+ expect(subject.type).to eq(nil)
+ end
+ end
+
+ describe '#details' do
+ it 'returns nil' do
+ expect(subject.details).to eq(nil)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/audit/target_spec.rb b/spec/lib/gitlab/audit/target_spec.rb
new file mode 100644
index 00000000000..5c06cd117a9
--- /dev/null
+++ b/spec/lib/gitlab/audit/target_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Audit::Target do
+ let(:object) { double('object') } # rubocop:disable RSpec/VerifiedDoubles
+
+ subject { described_class.new(object) }
+
+ describe '#id' do
+ it 'returns object id' do
+ allow(object).to receive(:id).and_return(object_id)
+
+ expect(subject.id).to eq(object_id)
+ end
+ end
+
+ describe '#type' do
+ it 'returns object class name' do
+ allow(object).to receive_message_chain(:class, :name).and_return('User')
+
+ expect(subject.type).to eq('User')
+ end
+ end
+
+ describe '#details' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:name, :audit_details, :details) do
+ 'jackie' | 'wanderer' | 'jackie'
+ 'jackie' | nil | 'jackie'
+ nil | 'wanderer' | 'wanderer'
+ nil | nil | 'unknown'
+ end
+
+ before do
+ allow(object).to receive(:name).and_return(name) if name
+ allow(object).to receive(:audit_details).and_return(audit_details) if audit_details
+ end
+
+ with_them do
+ it 'returns details' do
+ expect(subject.details).to eq(details)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb
index e985f66bfe9..d0b44135a2f 100644
--- a/spec/lib/gitlab/auth/auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/auth_finders_spec.rb
@@ -127,7 +127,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
let(:doorkeeper_access_token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api') }
before do
- set_bearer_token(doorkeeper_access_token.token)
+ set_bearer_token(doorkeeper_access_token.plaintext_token)
end
it { is_expected.to eq user }
@@ -577,7 +577,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
context 'passed as header' do
before do
- set_bearer_token(doorkeeper_access_token.token)
+ set_bearer_token(doorkeeper_access_token.plaintext_token)
end
it 'returns token if valid oauth_access_token' do
@@ -587,7 +587,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
context 'passed as param' do
it 'returns user if valid oauth_access_token' do
- set_param(:access_token, doorkeeper_access_token.token)
+ set_param(:access_token, doorkeeper_access_token.plaintext_token)
expect(find_oauth_access_token.token).to eq doorkeeper_access_token.token
end
diff --git a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
index f23fdd3fbcb..3d9be4c3489 100644
--- a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
}
end
- subject { described_class.new(ip) }
+ subject(:rate_limiter) { described_class.new(ip) }
before do
stub_rack_attack_setting(options)
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
end
after do
- subject.reset!
+ rate_limiter.reset!
end
describe '#register_fail!' do
@@ -86,7 +86,7 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
end
end
- context 'when IP is whitlisted' do
+ context 'when IP is allow listed' do
let(:ip) { '127.0.0.1' }
it_behaves_like 'skips the rate limiter'
@@ -97,4 +97,20 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
it_behaves_like 'skips the rate limiter'
end
+
+ describe '#trusted_ip?' do
+ subject { rate_limiter.trusted_ip? }
+
+ context 'when ip is in the trusted list' do
+ let(:ip) { '127.0.0.1' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when ip is not in the trusted list' do
+ let(:ip) { '10.0.0.1' }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
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 69068883096..a044094179c 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash do
)
end
+ let(:provider_config) { { 'args' => { 'gitlab_username_claim' => 'first_name' } } }
let(:uid_raw) do
+"CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net"
end
@@ -35,6 +36,7 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash do
let(:email_utf8) { email_ascii.force_encoding(Encoding::UTF_8) }
let(:nickname_utf8) { nickname_ascii.force_encoding(Encoding::UTF_8) }
let(:name_utf8) { name_ascii.force_encoding(Encoding::UTF_8) }
+ let(:first_name_utf8) { first_name_ascii.force_encoding(Encoding::UTF_8) }
let(:info_hash) do
{
@@ -91,6 +93,34 @@ RSpec.describe Gitlab::Auth::OAuth::AuthHash do
end
end
+ context 'custom username field provided' do
+ before do
+ allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for).and_return(provider_config)
+ end
+
+ it 'uses the custom field for the username' do
+ expect(auth_hash.username).to eql first_name_utf8
+ end
+
+ it 'uses the default claim for the username when the custom claim is not found' do
+ provider_config['args']['gitlab_username_claim'] = 'nonexistent'
+
+ expect(auth_hash.username).to eql nickname_utf8
+ end
+
+ it 'uses the default claim for the username when the custom claim is empty' do
+ info_hash[:first_name] = ''
+
+ expect(auth_hash.username).to eql nickname_utf8
+ end
+
+ it 'uses the default claim for the username when the custom claim is nil' do
+ info_hash[:first_name] = nil
+
+ expect(auth_hash.username).to eql nickname_utf8
+ end
+ end
+
context 'auth_hash constructed with ASCII-8BIT encoding' do
it 'forces utf8 encoding on uid' do
expect(auth_hash.uid.encoding).to eql Encoding::UTF_8
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 5f5e7f211f8..b160f322fb8 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -727,6 +727,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
context 'signup with linked omniauth and LDAP account' do
before do
stub_omniauth_config(auto_link_ldap_user: true)
+ stub_ldap_setting(enabled: true)
allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { uid }
allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] }
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 1e869df0988..c2d64aa2fb3 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -87,7 +87,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
context 'when IP is already banned' do
- subject { gl_auth.find_for_git_client('username', 'password', project: nil, ip: 'ip') }
+ subject { gl_auth.find_for_git_client('username-does-not-matter', 'password-does-not-matter', project: nil, ip: 'ip') }
before do
expect_next_instance_of(Gitlab::Auth::IpRateLimiter) do |rate_limiter|
@@ -219,16 +219,16 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
it 'recognizes master passwords' do
- user = create(:user, password: 'password')
+ user = create(:user)
- expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
+ expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
end
include_examples 'user login operation with unique ip limit' do
- let(:user) { create(:user, password: 'password') }
+ let(:user) { create(:user) }
def operation
- expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
+ expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')).to have_attributes(actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities)
end
end
@@ -502,8 +502,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
user = create(
:user,
:blocked,
- username: 'normal_user',
- password: 'my-secret'
+ username: 'normal_user'
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
@@ -512,7 +511,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'when 2fa is enabled globally' do
let_it_be(:user) do
- create(:user, username: 'normal_user', password: 'my-secret', otp_grace_period_started_at: 1.day.ago)
+ create(:user, username: 'normal_user', otp_grace_period_started_at: 1.day.ago)
end
before do
@@ -536,7 +535,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'when 2fa is enabled personally' do
let(:user) do
- create(:user, :two_factor, username: 'normal_user', password: 'my-secret', otp_grace_period_started_at: 1.day.ago)
+ create(:user, :two_factor, username: 'normal_user', otp_grace_period_started_at: 1.day.ago)
end
it 'fails' do
@@ -548,8 +547,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'goes through lfs authentication' do
user = create(
:user,
- username: 'normal_user',
- password: 'my-secret'
+ username: 'normal_user'
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
@@ -559,8 +557,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'goes through oauth authentication when the username is oauth2' do
user = create(
:user,
- username: 'oauth2',
- password: 'my-secret'
+ username: 'oauth2'
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
@@ -635,7 +632,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'when deploy token and user have the same username' do
let(:username) { 'normal_user' }
- let(:user) { create(:user, username: username, password: 'my-secret') }
+ let(:user) { create(:user, username: username) }
let(:deploy_token) { create(:deploy_token, username: username, read_registry: false, projects: [project]) }
it 'succeeds for the token' do
@@ -648,7 +645,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'succeeds for the user' do
auth_success = { actor: user, project: nil, type: :gitlab_or_ldap, authentication_abilities: described_class.full_authentication_abilities }
- expect(gl_auth.find_for_git_client(username, 'my-secret', project: project, ip: 'ip'))
+ expect(gl_auth.find_for_git_client(username, user.password, project: project, ip: 'ip'))
.to have_attributes(auth_success)
end
end
@@ -834,72 +831,64 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
describe 'find_with_user_password' do
- let!(:user) do
- create(:user,
- username: username,
- password: password,
- password_confirmation: password)
- end
-
+ let!(:user) { create(:user, username: username) }
let(:username) { 'John' } # username isn't lowercase, test this
- let(:password) { 'my-secret' }
it "finds user by valid login/password" do
- expect(gl_auth.find_with_user_password(username, password)).to eql user
+ expect(gl_auth.find_with_user_password(username, user.password)).to eql user
end
it 'finds user by valid email/password with case-insensitive email' do
- expect(gl_auth.find_with_user_password(user.email.upcase, password)).to eql user
+ expect(gl_auth.find_with_user_password(user.email.upcase, user.password)).to eql user
end
it 'finds user by valid username/password with case-insensitive username' do
- expect(gl_auth.find_with_user_password(username.upcase, password)).to eql user
+ expect(gl_auth.find_with_user_password(username.upcase, user.password)).to eql user
end
it "does not find user with invalid password" do
- password = 'wrong'
- expect(gl_auth.find_with_user_password(username, password)).not_to eql user
+ expect(gl_auth.find_with_user_password(username, 'incorrect_password')).not_to eql user
end
it "does not find user with invalid login" do
- user = 'wrong'
- expect(gl_auth.find_with_user_password(username, password)).not_to eql user
+ username = 'wrong'
+ expect(gl_auth.find_with_user_password(username, user.password)).not_to eql user
end
include_examples 'user login operation with unique ip limit' do
def operation
- expect(gl_auth.find_with_user_password(username, password)).to eq(user)
+ expect(gl_auth.find_with_user_password(username, user.password)).to eq(user)
end
end
it 'finds the user in deactivated state' do
user.deactivate!
- expect(gl_auth.find_with_user_password(username, password)).to eql user
+ expect(gl_auth.find_with_user_password(username, user.password)).to eql user
end
it "does not find user in blocked state" do
user.block
- expect(gl_auth.find_with_user_password(username, password)).not_to eql user
+ expect(gl_auth.find_with_user_password(username, user.password)).not_to eql user
end
it 'does not find user in locked state' do
user.lock_access!
- expect(gl_auth.find_with_user_password(username, password)).not_to eql user
+ expect(gl_auth.find_with_user_password(username, user.password)).not_to eql user
end
it "does not find user in ldap_blocked state" do
user.ldap_block
- expect(gl_auth.find_with_user_password(username, password)).not_to eql user
+ expect(gl_auth.find_with_user_password(username, user.password)).not_to eql user
end
it 'does not find user in blocked_pending_approval state' do
user.block_pending_approval
- expect(gl_auth.find_with_user_password(username, password)).not_to eql user
+ expect(gl_auth.find_with_user_password(username, user.password)).not_to eql user
end
context 'with increment_failed_attempts' do
@@ -917,7 +906,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
user.save!
expect do
- gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)
+ gl_auth.find_with_user_password(username, user.password, increment_failed_attempts: true)
user.reload
end.to change(user, :failed_attempts).from(2).to(0)
end
@@ -946,7 +935,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
user.save!
expect do
- gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)
+ gl_auth.find_with_user_password(username, user.password, increment_failed_attempts: true)
user.reload
end.not_to change(user, :failed_attempts)
end
@@ -961,7 +950,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it "tries to autheticate with db before ldap" do
expect(Gitlab::Auth::Ldap::Authentication).not_to receive(:login)
- expect(gl_auth.find_with_user_password(username, password)).to eq(user)
+ expect(gl_auth.find_with_user_password(username, user.password)).to eq(user)
end
it "does not find user by using ldap as fallback to for authentication" do
@@ -983,7 +972,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
it "does not find user by valid login/password" do
- expect(gl_auth.find_with_user_password(username, password)).to be_nil
+ expect(gl_auth.find_with_user_password(username, user.password)).to be_nil
end
context "with ldap enabled" do
@@ -992,7 +981,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
it "does not find non-ldap user by valid login/password" do
- expect(gl_auth.find_with_user_password(username, password)).to be_nil
+ expect(gl_auth.find_with_user_password(username, user.password)).to be_nil
end
end
end
diff --git a/spec/lib/gitlab/background_migration/backfill_ci_namespace_mirrors_spec.rb b/spec/lib/gitlab/background_migration/backfill_ci_namespace_mirrors_spec.rb
deleted file mode 100644
index 8980a26932b..00000000000
--- a/spec/lib/gitlab/background_migration/backfill_ci_namespace_mirrors_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::BackgroundMigration::BackfillCiNamespaceMirrors, :migration, schema: 20211208122200 do
- let(:namespaces) { table(:namespaces) }
- let(:ci_namespace_mirrors) { table(:ci_namespace_mirrors) }
-
- subject { described_class.new }
-
- describe '#perform' do
- it 'creates hierarchies for all namespaces in range' do
- namespaces.create!(id: 5, name: 'test1', path: 'test1')
- namespaces.create!(id: 7, name: 'test2', path: 'test2')
- namespaces.create!(id: 8, name: 'test3', path: 'test3')
-
- subject.perform(5, 7)
-
- expect(ci_namespace_mirrors.all).to contain_exactly(
- an_object_having_attributes(namespace_id: 5, traversal_ids: [5]),
- an_object_having_attributes(namespace_id: 7, traversal_ids: [7])
- )
- end
-
- it 'handles existing hierarchies gracefully' do
- namespaces.create!(id: 5, name: 'test1', path: 'test1')
- test2 = namespaces.create!(id: 7, name: 'test2', path: 'test2')
- namespaces.create!(id: 8, name: 'test3', path: 'test3', parent_id: 7)
- namespaces.create!(id: 9, name: 'test4', path: 'test4')
-
- # Simulate a situation where a user has had a chance to move a group to another parent
- # before the background migration has had a chance to run
- test2.update!(parent_id: 5)
- ci_namespace_mirrors.create!(namespace_id: test2.id, traversal_ids: [5, 7])
-
- subject.perform(5, 8)
-
- expect(ci_namespace_mirrors.all).to contain_exactly(
- an_object_having_attributes(namespace_id: 5, traversal_ids: [5]),
- an_object_having_attributes(namespace_id: 7, traversal_ids: [5, 7]),
- an_object_having_attributes(namespace_id: 8, traversal_ids: [5, 7, 8])
- )
- end
- end
-end
diff --git a/spec/lib/gitlab/background_migration/backfill_ci_project_mirrors_spec.rb b/spec/lib/gitlab/background_migration/backfill_ci_project_mirrors_spec.rb
deleted file mode 100644
index 4eec83879e3..00000000000
--- a/spec/lib/gitlab/background_migration/backfill_ci_project_mirrors_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::BackgroundMigration::BackfillCiProjectMirrors, :migration, schema: 20211208122201 do
- let(:namespaces) { table(:namespaces) }
- let(:projects) { table(:projects) }
- let(:ci_project_mirrors) { table(:ci_project_mirrors) }
-
- subject { described_class.new }
-
- describe '#perform' do
- it 'creates ci_project_mirrors for all projects in range' do
- namespaces.create!(id: 10, name: 'namespace1', path: 'namespace1')
- projects.create!(id: 5, namespace_id: 10, name: 'test1', path: 'test1')
- projects.create!(id: 7, namespace_id: 10, name: 'test2', path: 'test2')
- projects.create!(id: 8, namespace_id: 10, name: 'test3', path: 'test3')
-
- subject.perform(5, 7)
-
- expect(ci_project_mirrors.all).to contain_exactly(
- an_object_having_attributes(project_id: 5, namespace_id: 10),
- an_object_having_attributes(project_id: 7, namespace_id: 10)
- )
- end
-
- it 'handles existing ci_project_mirrors gracefully' do
- namespaces.create!(id: 10, name: 'namespace1', path: 'namespace1')
- namespaces.create!(id: 11, name: 'namespace2', path: 'namespace2', parent_id: 10)
- projects.create!(id: 5, namespace_id: 10, name: 'test1', path: 'test1')
- projects.create!(id: 7, namespace_id: 11, name: 'test2', path: 'test2')
- projects.create!(id: 8, namespace_id: 11, name: 'test3', path: 'test3')
-
- # Simulate a situation where a user has had a chance to move a project to another namespace
- # before the background migration has had a chance to run
- ci_project_mirrors.create!(project_id: 7, namespace_id: 10)
-
- subject.perform(5, 7)
-
- expect(ci_project_mirrors.all).to contain_exactly(
- an_object_having_attributes(project_id: 5, namespace_id: 10),
- an_object_having_attributes(project_id: 7, namespace_id: 10)
- )
- end
- end
-end
diff --git a/spec/lib/gitlab/background_migration/backfill_ci_queuing_tables_spec.rb b/spec/lib/gitlab/background_migration/backfill_ci_queuing_tables_spec.rb
index 1aac5970a77..aaf8c124a83 100644
--- a/spec/lib/gitlab/background_migration/backfill_ci_queuing_tables_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_ci_queuing_tables_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::BackfillCiQueuingTables, :migration, schema: 20220208115439 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillCiQueuingTables, :migration,
+ :suppress_gitlab_schemas_validate_connection, schema: 20220208115439 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:ci_cd_settings) { table(:project_ci_cd_settings) }
diff --git a/spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb b/spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb
deleted file mode 100644
index 7c78d8b0305..00000000000
--- a/spec/lib/gitlab/background_migration/backfill_ci_runner_semver_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::BackgroundMigration::BackfillCiRunnerSemver, :migration, schema: 20220601151900 do
- let(:ci_runners) { table(:ci_runners, database: :ci) }
-
- subject do
- described_class.new(
- start_id: 10,
- end_id: 15,
- batch_table: :ci_runners,
- batch_column: :id,
- sub_batch_size: 10,
- pause_ms: 0,
- connection: Ci::ApplicationRecord.connection)
- end
-
- describe '#perform' do
- it 'populates semver column on all runners in range' do
- ci_runners.create!(id: 10, runner_type: 1, version: %q(HEAD-fd84d97))
- ci_runners.create!(id: 11, runner_type: 1, version: %q(v1.2.3))
- ci_runners.create!(id: 12, runner_type: 1, version: %q(2.1.0))
- ci_runners.create!(id: 13, runner_type: 1, version: %q(11.8.0~beta.935.g7f6d2abc))
- ci_runners.create!(id: 14, runner_type: 1, version: %q(13.2.2/1.1.0))
- ci_runners.create!(id: 15, runner_type: 1, version: %q('14.3.4'))
-
- subject.perform
-
- expect(ci_runners.all).to contain_exactly(
- an_object_having_attributes(id: 10, semver: nil),
- an_object_having_attributes(id: 11, semver: '1.2.3'),
- an_object_having_attributes(id: 12, semver: '2.1.0'),
- an_object_having_attributes(id: 13, semver: '11.8.0'),
- an_object_having_attributes(id: 14, semver: '13.2.2'),
- an_object_having_attributes(id: 15, semver: '14.3.4')
- )
- end
-
- it 'skips runners that already have semver value' do
- ci_runners.create!(id: 10, runner_type: 1, version: %q(1.2.4), semver: '1.2.3')
- ci_runners.create!(id: 11, runner_type: 1, version: %q(1.2.5))
- ci_runners.create!(id: 12, runner_type: 1, version: %q(HEAD), semver: '1.2.4')
-
- subject.perform
-
- expect(ci_runners.all).to contain_exactly(
- an_object_having_attributes(id: 10, semver: '1.2.3'),
- an_object_having_attributes(id: 11, semver: '1.2.5'),
- an_object_having_attributes(id: 12, semver: '1.2.4')
- )
- end
- end
-end
diff --git a/spec/lib/gitlab/background_migration/backfill_group_features_spec.rb b/spec/lib/gitlab/background_migration/backfill_group_features_spec.rb
index d84bc479554..e0be5a785b8 100644
--- a/spec/lib/gitlab/background_migration/backfill_group_features_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_group_features_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillGroupFeatures, :migration, s
batch_column: :id,
sub_batch_size: 10,
pause_ms: 0,
+ job_arguments: [4],
connection: ActiveRecord::Base.connection)
end
@@ -27,7 +28,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillGroupFeatures, :migration, s
group_features.create!(id: 1, group_id: 4)
expect(group_features.count).to eq 1
- expect { subject.perform(4) }.to change { group_features.count }.by(2)
+ expect { subject.perform }.to change { group_features.count }.by(2)
expect(group_features.count).to eq 3
expect(group_features.all.pluck(:group_id)).to contain_exactly(1, 3, 4)
diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_id_of_vulnerability_reads_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_id_of_vulnerability_reads_spec.rb
new file mode 100644
index 00000000000..564aa3b8c01
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_namespace_id_of_vulnerability_reads_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceIdOfVulnerabilityReads, schema: 20220722145845 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+ let(:scanners) { table(:vulnerability_scanners) }
+ let(:vulnerabilities) { table(:vulnerabilities) }
+ let(:vulnerability_reads) { table(:vulnerability_reads) }
+
+ let(:namespace) { namespaces.create!(name: 'user', path: 'user') }
+ let(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
+ let(:user) { users.create!(username: 'john_doe', email: 'johndoe@gitlab.com', projects_limit: 10) }
+ let(:scanner) { scanners.create!(project_id: project.id, external_id: 'external_id', name: 'Test Scanner') }
+ let(:vulnerability) do
+ vulnerabilities.create!(
+ project_id: project.id,
+ author_id: user.id,
+ title: 'test',
+ severity: 1,
+ confidence: 1,
+ report_type: 1
+ )
+ end
+
+ let(:vulnerability_read) do
+ vulnerability_reads.create!(
+ project_id: project.id,
+ vulnerability_id: vulnerability.id,
+ scanner_id: scanner.id,
+ severity: 1,
+ report_type: 1,
+ state: 1,
+ uuid: SecureRandom.uuid
+ )
+ end
+
+ subject(:perform_migration) do
+ described_class.new(start_id: vulnerability_read.vulnerability_id,
+ end_id: vulnerability_read.vulnerability_id,
+ batch_table: :vulnerability_reads,
+ batch_column: :vulnerability_id,
+ sub_batch_size: 1,
+ pause_ms: 0,
+ connection: ActiveRecord::Base.connection)
+ .perform
+ end
+
+ it 'sets the namespace_id of existing record' do
+ expect { perform_migration }.to change { vulnerability_read.reload.namespace_id }.from(nil).to(namespace.id)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_import_level_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_import_level_spec.rb
new file mode 100644
index 00000000000..ae296483166
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_project_import_level_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+# rubocop:disable Layout/HashAlignment
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectImportLevel do
+ let(:migration) do
+ described_class.new(
+ start_id: table(:namespaces).minimum(:id),
+ end_id: table(:namespaces).maximum(:id),
+ batch_table: :namespaces,
+ batch_column: :id,
+ sub_batch_size: 2,
+ pause_ms: 0,
+ connection: ApplicationRecord.connection
+ )
+ end
+ # rubocop:enable Layout/HashAlignment
+
+ let(:namespaces_table) { table(:namespaces) }
+ let(:namespace_settings_table) { table(:namespace_settings) }
+
+ let!(:user_namespace) do
+ namespaces_table.create!(
+ name: 'user_namespace',
+ path: 'user_namespace',
+ type: 'User',
+ project_creation_level: 100
+ )
+ end
+
+ let!(:group_namespace_nil) do
+ namespaces_table.create!(
+ name: 'group_namespace_nil',
+ path: 'group_namespace_nil',
+ type: 'Group',
+ project_creation_level: nil
+ )
+ end
+
+ let!(:group_namespace_0) do
+ namespaces_table.create!(
+ name: 'group_namespace_0',
+ path: 'group_namespace_0',
+ type: 'Group',
+ project_creation_level: 0
+ )
+ end
+
+ let!(:group_namespace_1) do
+ namespaces_table.create!(
+ name: 'group_namespace_1',
+ path: 'group_namespace_1',
+ type: 'Group',
+ project_creation_level: 1
+ )
+ end
+
+ let!(:group_namespace_2) do
+ namespaces_table.create!(
+ name: 'group_namespace_2',
+ path: 'group_namespace_2',
+ type: 'Group',
+ project_creation_level: 2
+ )
+ end
+
+ let!(:group_namespace_9999) do
+ namespaces_table.create!(
+ name: 'group_namespace_9999',
+ path: 'group_namespace_9999',
+ type: 'Group',
+ project_creation_level: 9999
+ )
+ end
+
+ subject(:perform_migration) { migration.perform }
+
+ before do
+ namespace_settings_table.create!(namespace_id: user_namespace.id)
+ namespace_settings_table.create!(namespace_id: group_namespace_nil.id)
+ namespace_settings_table.create!(namespace_id: group_namespace_0.id)
+ namespace_settings_table.create!(namespace_id: group_namespace_1.id)
+ namespace_settings_table.create!(namespace_id: group_namespace_2.id)
+ namespace_settings_table.create!(namespace_id: group_namespace_9999.id)
+ end
+
+ describe 'Groups' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:namespace_id, :prev_level, :new_level) do
+ lazy { group_namespace_0.id } | ::Gitlab::Access::OWNER | ::Gitlab::Access::NO_ACCESS
+ lazy { group_namespace_1.id } | ::Gitlab::Access::OWNER | ::Gitlab::Access::MAINTAINER
+ lazy { group_namespace_2.id } | ::Gitlab::Access::OWNER | ::Gitlab::Access::DEVELOPER
+ end
+
+ with_them do
+ it 'backfills the correct project_import_level of Group namespaces' do
+ expect { perform_migration }
+ .to change { namespace_settings_table.find_by(namespace_id: namespace_id).project_import_level }
+ .from(prev_level).to(new_level)
+ end
+ end
+
+ it 'does not update `User` namespaces or values outside range' do
+ expect { perform_migration }
+ .not_to change { namespace_settings_table.find_by(namespace_id: user_namespace.id).project_import_level }
+
+ expect { perform_migration }
+ .not_to change { namespace_settings_table.find_by(namespace_id: group_namespace_9999.id).project_import_level }
+ end
+
+ it 'maintains default import_level if creation_level is nil' do
+ project_import_level = namespace_settings_table.find_by(namespace_id: group_namespace_nil.id).project_import_level
+
+ expect { perform_migration }
+ .not_to change { project_import_level }
+
+ expect(project_import_level).to eq(::Gitlab::Access::OWNER)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_projects_with_coverage_spec.rb b/spec/lib/gitlab/background_migration/backfill_projects_with_coverage_spec.rb
index 49056154744..4a65ecf8c75 100644
--- a/spec/lib/gitlab/background_migration/backfill_projects_with_coverage_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_projects_with_coverage_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::BackfillProjectsWithCoverage, schema: 20210818185845 do
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectsWithCoverage,
+ :suppress_gitlab_schemas_validate_connection, schema: 20210818185845 do
let(:projects) { table(:projects) }
let(:project_ci_feature_usages) { table(:project_ci_feature_usages) }
let(:ci_pipelines) { table(:ci_pipelines) }
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 b5122af5cd4..6f75d3faef3 100644
--- a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migrat
let(:file_name) { 'file_name.rb' }
let(:content) { 'content' }
- let(:ids) { snippets.pluck('MIN(id)', 'MAX(id)').first }
+ let(:ids) { snippets.pick('MIN(id)', 'MAX(id)') }
let(:service) { described_class.new }
subject { service.perform(*ids) }
diff --git a/spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb b/spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb
new file mode 100644
index 00000000000..79699375a8d
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillVulnerabilityReadsClusterAgent, :migration, schema: 20220525221133 do # rubocop:disable Layout/LineLength
+ let(:migration) do
+ described_class.new(start_id: 1, end_id: 10,
+ batch_table: table_name, batch_column: batch_column,
+ sub_batch_size: sub_batch_size, pause_ms: pause_ms,
+ connection: ApplicationRecord.connection)
+ end
+
+ let(:users_table) { table(:users) }
+ let(:vulnerability_reads_table) { table(:vulnerability_reads) }
+ let(:vulnerability_scanners_table) { table(:vulnerability_scanners) }
+ let(:vulnerabilities_table) { table(:vulnerabilities) }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:cluster_agents_table) { table(:cluster_agents) }
+
+ let(:table_name) { 'vulnerability_reads' }
+ let(:batch_column) { :id }
+ let(:sub_batch_size) { 1_000 }
+ let(:pause_ms) { 0 }
+
+ subject(:perform_migration) { migration.perform }
+
+ before do
+ users_table.create!(id: 1, name: 'John Doe', email: 'test@example.com', projects_limit: 5)
+
+ namespaces_table.create!(id: 1, name: 'Namespace 1', path: 'namespace-1')
+ namespaces_table.create!(id: 2, name: 'Namespace 2', path: 'namespace-2')
+
+ projects_table.create!(id: 1, namespace_id: 1, name: 'Project 1', path: 'project-1', project_namespace_id: 1)
+ projects_table.create!(id: 2, namespace_id: 2, name: 'Project 2', path: 'project-2', project_namespace_id: 2)
+
+ cluster_agents_table.create!(id: 1, name: 'Agent 1', project_id: 1)
+ cluster_agents_table.create!(id: 2, name: 'Agent 2', project_id: 2)
+
+ vulnerability_scanners_table.create!(id: 1, project_id: 1, external_id: 'starboard', name: 'Starboard')
+ vulnerability_scanners_table.create!(id: 2, project_id: 2, external_id: 'starboard', name: 'Starboard')
+
+ add_vulnerability_read!(1, project_id: 1, cluster_agent_id: 1, report_type: 7)
+ add_vulnerability_read!(3, project_id: 1, cluster_agent_id: 2, report_type: 7)
+ add_vulnerability_read!(5, project_id: 2, cluster_agent_id: 2, report_type: 5)
+ add_vulnerability_read!(7, project_id: 2, cluster_agent_id: 3, report_type: 7)
+ add_vulnerability_read!(9, project_id: 2, cluster_agent_id: 2, report_type: 7)
+ add_vulnerability_read!(10, project_id: 1, cluster_agent_id: 1, report_type: 7)
+ add_vulnerability_read!(11, project_id: 1, cluster_agent_id: 1, report_type: 7)
+ end
+
+ it 'backfills `casted_cluster_agent_id` for the selected records', :aggregate_failures do
+ queries = ActiveRecord::QueryRecorder.new do
+ perform_migration
+ end
+
+ expect(queries.count).to eq(3)
+ expect(vulnerability_reads_table.where.not(casted_cluster_agent_id: nil).count).to eq 3
+ expect(vulnerability_reads_table.where.not(casted_cluster_agent_id: nil).pluck(:id)).to match_array([1, 9, 10])
+ end
+
+ it 'tracks timings of queries' do
+ expect(migration.batch_metrics.timings).to be_empty
+
+ expect { perform_migration }.to change { migration.batch_metrics.timings }
+ end
+
+ private
+
+ def add_vulnerability_read!(id, project_id:, cluster_agent_id:, report_type:)
+ vulnerabilities_table.create!(
+ id: id,
+ project_id: project_id,
+ author_id: 1,
+ title: "Vulnerability #{id}",
+ severity: 5,
+ confidence: 5,
+ report_type: report_type
+ )
+
+ vulnerability_reads_table.create!(
+ id: id,
+ uuid: SecureRandom.uuid,
+ severity: 5,
+ state: 1,
+ vulnerability_id: id,
+ scanner_id: project_id,
+ cluster_agent_id: cluster_agent_id.to_s,
+ project_id: project_id,
+ report_type: report_type
+ )
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
index 98866bb765f..f03f90ddbbb 100644
--- a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
+++ b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
@@ -3,6 +3,113 @@
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
+ let(:connection) { Gitlab::Database.database_base_models[:main].connection }
+
+ describe '.generic_instance' do
+ it 'defines generic instance with only some of the attributes set' do
+ generic_instance = described_class.generic_instance(
+ batch_table: 'projects', batch_column: 'id',
+ job_arguments: %w(x y), connection: connection
+ )
+
+ expect(generic_instance.send(:batch_table)).to eq('projects')
+ expect(generic_instance.send(:batch_column)).to eq('id')
+ expect(generic_instance.instance_variable_get('@job_arguments')).to eq(%w(x y))
+ expect(generic_instance.send(:connection)).to eq(connection)
+
+ %i(start_id end_id sub_batch_size pause_ms).each do |attr|
+ expect(generic_instance.send(attr)).to eq(0)
+ end
+ end
+ end
+
+ describe '.job_arguments' do
+ let(:job_class) do
+ Class.new(described_class) do
+ job_arguments :value_a, :value_b
+ end
+ end
+
+ subject(:job_instance) do
+ job_class.new(start_id: 1, end_id: 10,
+ batch_table: '_test_table',
+ batch_column: 'id',
+ sub_batch_size: 2,
+ pause_ms: 1000,
+ job_arguments: %w(a b),
+ connection: connection)
+ end
+
+ it 'defines methods' do
+ expect(job_instance.value_a).to eq('a')
+ expect(job_instance.value_b).to eq('b')
+ expect(job_class.job_arguments_count).to eq(2)
+ end
+
+ context 'when no job arguments are defined' do
+ let(:job_class) do
+ Class.new(described_class)
+ end
+
+ it 'job_arguments_count is 0' do
+ expect(job_class.job_arguments_count).to eq(0)
+ end
+ end
+ end
+
+ describe '.scope_to' do
+ subject(:job_instance) do
+ job_class.new(start_id: 1, end_id: 10,
+ batch_table: '_test_table',
+ batch_column: 'id',
+ sub_batch_size: 2,
+ pause_ms: 1000,
+ job_arguments: %w(a b),
+ connection: connection)
+ end
+
+ context 'when additional scoping is defined' do
+ let(:job_class) do
+ Class.new(described_class) do
+ job_arguments :value_a, :value_b
+ scope_to ->(r) { "#{r}-#{value_a}-#{value_b}".upcase }
+ end
+ end
+
+ it 'applies additional scope to the provided relation' do
+ expect(job_instance.filter_batch('relation')).to eq('RELATION-A-B')
+ end
+ end
+
+ context 'when there is no additional scoping defined' do
+ let(:job_class) do
+ Class.new(described_class) do
+ end
+ end
+
+ it 'returns provided relation as is' do
+ expect(job_instance.filter_batch('relation')).to eq('relation')
+ end
+ end
+ end
+
+ describe 'descendants', :eager_load do
+ it 'have the same method signature for #perform' do
+ expected_arity = described_class.instance_method(:perform).arity
+ offences = described_class.descendants.select { |klass| klass.instance_method(:perform).arity != expected_arity }
+
+ expect(offences).to be_empty, "expected no descendants of #{described_class} to accept arguments for " \
+ "'#perform', but some do: #{offences.join(", ")}"
+ end
+
+ it 'do not use .batching_scope' do
+ offences = described_class.descendants.select { |klass| klass.respond_to?(:batching_scope) }
+
+ expect(offences).to be_empty, "expected no descendants of #{described_class} to define '.batching_scope', " \
+ "but some do: #{offences.join(", ")}"
+ end
+ end
+
describe '#perform' do
let(:connection) { Gitlab::Database.database_base_models[:main].connection }
@@ -66,6 +173,30 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
expect(test_table.order(:id).pluck(:to_column)).to contain_exactly(5, 10, nil, 20)
end
+ context 'with additional scoping' do
+ let(:job_class) do
+ Class.new(described_class) do
+ scope_to ->(r) { r.where('mod(id, 2) = 0') }
+
+ def perform(*job_arguments)
+ each_sub_batch(
+ operation_name: :update,
+ batching_arguments: { order_hint: :updated_at },
+ batching_scope: -> (relation) { relation.where.not(bar: nil) }
+ ) do |sub_batch|
+ sub_batch.update_all('to_column = from_column')
+ end
+ end
+ end
+ end
+
+ it 'respects #filter_batch' do
+ expect { perform_job }.to change { test_table.where(to_column: nil).count }.from(4).to(2)
+
+ expect(test_table.order(:id).pluck(:to_column)).to contain_exactly(nil, 10, nil, 20)
+ end
+ end
+
it 'instruments the batch operation' do
expect(job_instance.batch_metrics.affected_rows).to be_empty
@@ -84,7 +215,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
context 'when batching_arguments are given' do
it 'forwards them for batching' do
- expect(job_instance).to receive(:parent_batch_relation).and_return(test_table)
+ expect(job_instance).to receive(:base_relation).and_return(test_table)
expect(test_table).to receive(:each_batch).with(column: 'id', of: 2, order_hint: :updated_at)
@@ -155,6 +286,24 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
expect(job_instance.batch_metrics.affected_rows[:insert]).to contain_exactly(2, 1)
end
+
+ context 'when used in combination with scope_to' do
+ let(:job_class) do
+ Class.new(described_class) do
+ scope_to ->(r) { r.where.not(from_column: 10) }
+
+ def perform(*job_arguments)
+ distinct_each_batch(operation_name: :insert) do |sub_batch|
+ end
+ end
+ end
+ end
+
+ it 'raises an error' do
+ expect { perform_job }.to raise_error RuntimeError,
+ /distinct_each_batch can not be used when additional filters are defined with scope_to/
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb
index 943b5744b64..9fdd7bf8adc 100644
--- a/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb
+++ b/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb
@@ -45,19 +45,16 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi
end
end
- context 'when job_class is provided with a batching_scope' do
+ context 'when job class supports batch scope DSL' do
let(:job_class) do
- Class.new(described_class) do
- def self.batching_scope(relation, job_arguments:)
- min_id = job_arguments.first
-
- relation.where.not(type: 'Project').where('id >= ?', min_id)
- end
+ Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
+ job_arguments :min_id
+ scope_to ->(r) { r.where.not(type: 'Project').where('id >= ?', min_id) }
end
end
- it 'applies the batching scope' do
- expect(job_class).to receive(:batching_scope).and_call_original
+ it 'applies the additional scope' do
+ expect(job_class).to receive(:generic_instance).and_call_original
batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id, batch_size: 3, job_arguments: [1], job_class: job_class)
diff --git a/spec/lib/gitlab/background_migration/copy_ci_builds_columns_to_security_scans_spec.rb b/spec/lib/gitlab/background_migration/copy_ci_builds_columns_to_security_scans_spec.rb
deleted file mode 100644
index db822f36c21..00000000000
--- a/spec/lib/gitlab/background_migration/copy_ci_builds_columns_to_security_scans_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::BackgroundMigration::CopyCiBuildsColumnsToSecurityScans, schema: 20210728174349 do
- let(:migration) { described_class.new }
-
- let_it_be(:namespaces) { table(:namespaces) }
- let_it_be(:projects) { table(:projects) }
- let_it_be(:ci_pipelines) { table(:ci_pipelines) }
- let_it_be(:ci_builds) { table(:ci_builds) }
- let_it_be(:security_scans) { table(:security_scans) }
-
- let!(:namespace) { namespaces.create!(name: 'namespace', path: 'namespace') }
- let!(:project1) { projects.create!(namespace_id: namespace.id) }
- let!(:project2) { projects.create!(namespace_id: namespace.id) }
- let!(:pipeline1) { ci_pipelines.create!(status: "success")}
- let!(:pipeline2) { ci_pipelines.create!(status: "success")}
-
- let!(:build1) { ci_builds.create!(commit_id: pipeline1.id, type: 'Ci::Build', project_id: project1.id) }
- let!(:build2) { ci_builds.create!(commit_id: pipeline2.id, type: 'Ci::Build', project_id: project2.id) }
- let!(:build3) { ci_builds.create!(commit_id: pipeline1.id, type: 'Ci::Build', project_id: project1.id) }
-
- let!(:scan1) { security_scans.create!(build_id: build1.id, scan_type: 1) }
- let!(:scan2) { security_scans.create!(build_id: build2.id, scan_type: 1) }
- let!(:scan3) { security_scans.create!(build_id: build3.id, scan_type: 1) }
-
- subject { migration.perform(scan1.id, scan2.id) }
-
- before do
- stub_const("#{described_class}::UPDATE_BATCH_SIZE", 2)
- end
-
- it 'copies `project_id`, `commit_id` from `ci_builds` to `security_scans`', :aggregate_failures do
- expect(migration).to receive(:mark_job_as_succeeded).with(scan1.id, scan2.id)
-
- subject
-
- scan1.reload
- expect(scan1.project_id).to eq(project1.id)
- expect(scan1.pipeline_id).to eq(pipeline1.id)
-
- scan2.reload
- expect(scan2.project_id).to eq(project2.id)
- expect(scan2.pipeline_id).to eq(pipeline2.id)
-
- scan3.reload
- expect(scan3.project_id).to be_nil
- expect(scan3.pipeline_id).to be_nil
- end
-end
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 78bd1afd8d2..9c33100a0b3 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
@@ -16,6 +16,7 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
ActiveRecord::Migration.new.extend(Gitlab::Database::MigrationHelpers)
end
+ let(:job_arguments) { %w(name name_convert_to_text) }
let(:copy_job) do
described_class.new(start_id: 12,
end_id: 20,
@@ -23,6 +24,7 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
batch_column: 'id',
sub_batch_size: sub_batch_size,
pause_ms: pause_ms,
+ job_arguments: job_arguments,
connection: connection)
end
@@ -53,32 +55,42 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
SQL
end
- it 'copies all primary keys in range' do
- temporary_column = helpers.convert_to_bigint_column(:id)
+ context 'primary keys' do
+ let(:temporary_column) { helpers.convert_to_bigint_column(:id) }
+ let(:job_arguments) { ['id', temporary_column] }
- copy_job.perform('id', temporary_column)
+ it 'copies all in range' do
+ copy_job.perform
- expect(test_table.count).to eq(4)
- expect(test_table.where("id = #{temporary_column}").pluck(:id)).to contain_exactly(12, 15, 19)
- expect(test_table.where(temporary_column => 0).pluck(:id)).to contain_exactly(11)
+ expect(test_table.count).to eq(4)
+ expect(test_table.where("id = #{temporary_column}").pluck(:id)).to contain_exactly(12, 15, 19)
+ expect(test_table.where(temporary_column => 0).pluck(:id)).to contain_exactly(11)
+ end
end
- it 'copies all foreign keys in range' do
- temporary_column = helpers.convert_to_bigint_column(:fk)
+ context 'foreign keys' do
+ let(:temporary_column) { helpers.convert_to_bigint_column(:fk) }
+ let(:job_arguments) { ['fk', temporary_column] }
- copy_job.perform('fk', temporary_column)
+ it 'copies all in range' do
+ copy_job.perform
- expect(test_table.count).to eq(4)
- expect(test_table.where("fk = #{temporary_column}").pluck(:id)).to contain_exactly(12, 15, 19)
- expect(test_table.where(temporary_column => 0).pluck(:id)).to contain_exactly(11)
+ expect(test_table.count).to eq(4)
+ expect(test_table.where("fk = #{temporary_column}").pluck(:id)).to contain_exactly(12, 15, 19)
+ expect(test_table.where(temporary_column => 0).pluck(:id)).to contain_exactly(11)
+ end
end
- it 'copies columns with NULLs' do
- expect { copy_job.perform('name', 'name_convert_to_text') }
- .to change { test_table.where("name_convert_to_text = 'no name'").count }.from(4).to(1)
+ context 'columns with NULLs' do
+ let(:job_arguments) { %w(name name_convert_to_text) }
- expect(test_table.where('name = name_convert_to_text').pluck(:id)).to contain_exactly(12, 19)
- expect(test_table.where('name is NULL and name_convert_to_text is NULL').pluck(:id)).to contain_exactly(15)
+ it 'copies all in range' do
+ expect { copy_job.perform }
+ .to change { test_table.where("name_convert_to_text = 'no name'").count }.from(4).to(1)
+
+ expect(test_table.where('name = name_convert_to_text').pluck(:id)).to contain_exactly(12, 19)
+ expect(test_table.where('name is NULL and name_convert_to_text is NULL').pluck(:id)).to contain_exactly(15)
+ end
end
context 'when multiple columns are given' do
@@ -87,8 +99,10 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
let(:columns_to_copy_from) { %w[id fk] }
let(:columns_to_copy_to) { [id_tmp_column, fk_tmp_column] }
+ let(:job_arguments) { [columns_to_copy_from, columns_to_copy_to] }
+
it 'copies all values in the range' do
- copy_job.perform(columns_to_copy_from, columns_to_copy_to)
+ copy_job.perform
expect(test_table.count).to eq(4)
expect(test_table.where("id = #{id_tmp_column} AND fk = #{fk_tmp_column}").pluck(:id)).to contain_exactly(12, 15, 19)
@@ -100,7 +114,7 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
it 'raises an error' do
expect do
- copy_job.perform(columns_to_copy_from, columns_to_copy_to)
+ copy_job.perform
end.to raise_error(ArgumentError, 'number of source and destination columns must match')
end
end
@@ -109,7 +123,7 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
it 'tracks timings of queries' do
expect(copy_job.batch_metrics.timings).to be_empty
- copy_job.perform('name', 'name_convert_to_text')
+ copy_job.perform
expect(copy_job.batch_metrics.timings[:update_all]).not_to be_empty
end
@@ -120,7 +134,7 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
it 'sleeps for the specified time between sub-batches' do
expect(copy_job).to receive(:sleep).with(0.005)
- copy_job.perform('name', 'name_convert_to_text')
+ copy_job.perform
end
context 'when pause_ms value is negative' do
@@ -129,7 +143,7 @@ RSpec.describe Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJo
it 'treats it as a 0' do
expect(copy_job).to receive(:sleep).with(0)
- copy_job.perform('name', 'name_convert_to_text')
+ copy_job.perform
end
end
end
diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb
new file mode 100644
index 00000000000..d20eaef3650
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForNoIssuesNoRepoProjects,
+ :migration,
+ schema: 20220722084543 do
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:project_settings_table) { table(:project_settings) }
+ let(:project_statistics_table) { table(:project_statistics) }
+ let(:issues_table) { table(:issues) }
+
+ subject(:perform_migration) do
+ described_class.new(start_id: projects_table.minimum(:id),
+ end_id: projects_table.maximum(:id),
+ batch_table: :projects,
+ batch_column: :id,
+ sub_batch_size: 2,
+ pause_ms: 0,
+ connection: ActiveRecord::Base.connection)
+ .perform
+ end
+
+ it 'sets `legacy_open_source_license_available` to false only for public projects with no issues and no repo',
+ :aggregate_failures do
+ project_with_no_issues_no_repo = create_legacy_license_public_project('project-with-no-issues-no-repo')
+ project_with_repo = create_legacy_license_public_project('project-with-repo', repo_size: 1)
+ project_with_issues = create_legacy_license_public_project('project-with-issues', with_issue: true)
+ project_with_issues_and_repo =
+ create_legacy_license_public_project('project-with-issues-and-repo', repo_size: 1, with_issue: true)
+
+ queries = ActiveRecord::QueryRecorder.new { perform_migration }
+
+ expect(queries.count).to eq(7)
+ expect(migrated_attribute(project_with_no_issues_no_repo)).to be_falsey
+ expect(migrated_attribute(project_with_repo)).to be_truthy
+ expect(migrated_attribute(project_with_issues)).to be_truthy
+ expect(migrated_attribute(project_with_issues_and_repo)).to be_truthy
+ end
+
+ def create_legacy_license_public_project(path, repo_size: 0, with_issue: false)
+ namespace = namespaces_table.create!(name: "namespace-#{path}", path: "namespace-#{path}")
+ project_namespace =
+ namespaces_table.create!(name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project')
+ project = projects_table
+ .create!(
+ name: path, path: path, namespace_id: namespace.id,
+ project_namespace_id: project_namespace.id, visibility_level: 20
+ )
+
+ project_statistics_table.create!(project_id: project.id, namespace_id: namespace.id, repository_size: repo_size)
+ issues_table.create!(project_id: project.id) if with_issue
+ project_settings_table.create!(project_id: project.id, legacy_open_source_license_available: true)
+
+ project
+ end
+
+ def migrated_attribute(project)
+ project_settings_table.find(project.id).legacy_open_source_license_available
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb
new file mode 100644
index 00000000000..0dba1d7c8a2
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_one_member_no_repo_projects_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForOneMemberNoRepoProjects,
+ :migration,
+ schema: 20220721031446 do
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:project_settings_table) { table(:project_settings) }
+ let(:project_statistics_table) { table(:project_statistics) }
+ let(:users_table) { table(:users) }
+ let(:project_authorizations_table) { table(:project_authorizations) }
+
+ subject(:perform_migration) do
+ described_class.new(start_id: projects_table.minimum(:id),
+ end_id: projects_table.maximum(:id),
+ batch_table: :projects,
+ batch_column: :id,
+ sub_batch_size: 2,
+ pause_ms: 0,
+ connection: ActiveRecord::Base.connection)
+ .perform
+ end
+
+ it 'sets `legacy_open_source_license_available` to false only for public projects with 1 member and no repo',
+ :aggregate_failures do
+ project_with_no_repo_one_member = create_legacy_license_public_project('project-with-one-member-no-repo')
+ project_with_repo_one_member = create_legacy_license_public_project('project-with-repo', repo_size: 1)
+ project_with_no_repo_two_members = create_legacy_license_public_project('project-with-two-members', members: 2)
+ project_with_repo_two_members =
+ create_legacy_license_public_project('project-with-repo', repo_size: 1, members: 2)
+
+ queries = ActiveRecord::QueryRecorder.new { perform_migration }
+
+ expect(queries.count).to eq(7)
+ expect(migrated_attribute(project_with_no_repo_one_member)).to be_falsey
+ expect(migrated_attribute(project_with_repo_one_member)).to be_truthy
+ expect(migrated_attribute(project_with_no_repo_two_members)).to be_truthy
+ expect(migrated_attribute(project_with_repo_two_members)).to be_truthy
+ end
+
+ def create_legacy_license_public_project(path, repo_size: 0, members: 1)
+ namespace = namespaces_table.create!(name: "namespace-#{path}", path: "namespace-#{path}")
+ project_namespace =
+ namespaces_table.create!(name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project')
+ project = projects_table
+ .create!(
+ name: path, path: path, namespace_id: namespace.id,
+ project_namespace_id: project_namespace.id, visibility_level: 20
+ )
+
+ members.times do |member_id|
+ user = users_table.create!(email: "user#{member_id}-project-#{project.id}@gitlab.com", projects_limit: 100)
+ project_authorizations_table.create!(project_id: project.id, user_id: user.id, access_level: 50)
+ end
+ project_statistics_table.create!(project_id: project.id, namespace_id: namespace.id, repository_size: repo_size)
+ project_settings_table.create!(project_id: project.id, legacy_open_source_license_available: true)
+
+ project
+ end
+
+ def migrated_attribute(project)
+ project_settings_table.find(project.id).legacy_open_source_license_available
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/drop_invalid_security_findings_spec.rb b/spec/lib/gitlab/background_migration/drop_invalid_security_findings_spec.rb
index 7cc64889fc8..5fdd8683d06 100644
--- a/spec/lib/gitlab/background_migration/drop_invalid_security_findings_spec.rb
+++ b/spec/lib/gitlab/background_migration/drop_invalid_security_findings_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::DropInvalidSecurityFindings, schema: 20211108211434 do
+RSpec.describe Gitlab::BackgroundMigration::DropInvalidSecurityFindings, :suppress_gitlab_schemas_validate_connection,
+ schema: 20211108211434 do
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user', type: Namespaces::UserNamespace.sti_name) }
let(:project) { table(:projects).create!(namespace_id: namespace.id) }
diff --git a/spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb b/spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb
index 65d55f85a98..51a09d50a19 100644
--- a/spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/extract_project_topics_into_separate_table_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::ExtractProjectTopicsIntoSeparateTable, schema: 20210730104800 do
+RSpec.describe Gitlab::BackgroundMigration::ExtractProjectTopicsIntoSeparateTable,
+ :suppress_gitlab_schemas_validate_connection, schema: 20210730104800 do
it 'correctly extracts project topics into separate table' do
namespaces = table(:namespaces)
projects = table(:projects)
diff --git a/spec/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics_spec.rb b/spec/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics_spec.rb
index 5e2f32c54be..5495d786a48 100644
--- a/spec/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_project_taggings_context_from_tags_to_topics_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::MigrateProjectTaggingsContextFromTagsToTopics, schema: 20210511095658 do
+RSpec.describe Gitlab::BackgroundMigration::MigrateProjectTaggingsContextFromTagsToTopics,
+ :suppress_gitlab_schemas_validate_connection, schema: 20210511095658 do
it 'correctly migrates project taggings context from tags to topics' do
taggings = table(:taggings)
diff --git a/spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb b/spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb
index e38edfc3643..2f0eef3c399 100644
--- a/spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb
+++ b/spec/lib/gitlab/background_migration/nullify_orphan_runner_id_on_ci_builds_spec.rb
@@ -2,12 +2,13 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::NullifyOrphanRunnerIdOnCiBuilds, :migration, schema: 20220223112304 do
+RSpec.describe Gitlab::BackgroundMigration::NullifyOrphanRunnerIdOnCiBuilds,
+ :suppress_gitlab_schemas_validate_connection, migration: :gitlab_ci, schema: 20220223112304 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
- let(:ci_runners) { table(:ci_runners, database: :ci) }
- let(:ci_pipelines) { table(:ci_pipelines, database: :ci) }
- let(:ci_builds) { table(:ci_builds, database: :ci) }
+ let(:ci_runners) { table(:ci_runners) }
+ let(:ci_pipelines) { table(:ci_pipelines) }
+ let(:ci_builds) { table(:ci_builds) }
subject { described_class.new }
@@ -20,7 +21,9 @@ RSpec.describe Gitlab::BackgroundMigration::NullifyOrphanRunnerIdOnCiBuilds, :mi
end
after do
- helpers.add_concurrent_foreign_key(:ci_builds, :ci_runners, column: :runner_id, on_delete: :nullify, validate: false)
+ helpers.add_concurrent_foreign_key(
+ :ci_builds, :ci_runners, column: :runner_id, on_delete: :nullify, validate: false
+ )
end
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb b/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb
index 2ad561ead87..bff803e2035 100644
--- a/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb
+++ b/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb
@@ -5,199 +5,211 @@ require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNamespaces, :migration, schema: 20220326161803 do
include MigrationsHelpers
- context 'when migrating data', :aggregate_failures do
- let(:projects) { table(:projects) }
- let(:namespaces) { table(:namespaces) }
+ RSpec.shared_examples 'backfills project namespaces' do
+ context 'when migrating data', :aggregate_failures do
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
- let(:parent_group1) { namespaces.create!(name: 'parent_group1', path: 'parent_group1', visibility_level: 20, type: 'Group') }
- let(:parent_group2) { namespaces.create!(name: 'test1', path: 'test1', runners_token: 'my-token1', project_creation_level: 1, visibility_level: 20, type: 'Group') }
+ let(:parent_group1) { namespaces.create!(name: 'parent_group1', path: 'parent_group1', visibility_level: 20, type: 'Group') }
+ let(:parent_group2) { namespaces.create!(name: 'test1', path: 'test1', runners_token: 'my-token1', project_creation_level: 1, visibility_level: 20, type: 'Group') }
- let(:parent_group1_project) { projects.create!(name: 'parent_group1_project', path: 'parent_group1_project', namespace_id: parent_group1.id, visibility_level: 20) }
- let(:parent_group2_project) { projects.create!(name: 'parent_group2_project', path: 'parent_group2_project', namespace_id: parent_group2.id, visibility_level: 20) }
+ let(:parent_group1_project) { projects.create!(name: 'parent_group1_project', path: 'parent_group1_project', namespace_id: parent_group1.id, visibility_level: 20) }
+ let(:parent_group2_project) { projects.create!(name: 'parent_group2_project', path: 'parent_group2_project', namespace_id: parent_group2.id, visibility_level: 20) }
- let(:child_nodes_count) { 2 }
- let(:tree_depth) { 3 }
+ let(:child_nodes_count) { 2 }
+ let(:tree_depth) { 3 }
- let(:backfilled_namespace) { nil }
+ let(:backfilled_namespace) { nil }
- before do
- BackfillProjectNamespaces::TreeGenerator.new(namespaces, projects, [parent_group1, parent_group2], child_nodes_count, tree_depth).build_tree
- end
-
- describe '#up' do
- shared_examples 'back-fill project namespaces' do
- it 'back-fills all project namespaces' do
- start_id = ::Project.minimum(:id)
- end_id = ::Project.maximum(:id)
- projects_count = ::Project.count
- batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
- project_namespaces_count = ::Namespace.where(type: 'Project').count
- migration = described_class.new
-
- expect(projects_count).not_to eq(project_namespaces_count)
- expect(migration).to receive(:batch_insert_namespaces).exactly(batches_count).and_call_original
- expect(migration).to receive(:batch_update_projects).exactly(batches_count).and_call_original
- expect(migration).to receive(:batch_update_project_namespaces_traversal_ids).exactly(batches_count).and_call_original
-
- expect { migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up') }.to change(Namespace.where(type: 'Project'), :count)
-
- expect(projects_count).to eq(::Namespace.where(type: 'Project').count)
- check_projects_in_sync_with(Namespace.where(type: 'Project'))
- end
-
- context 'when passing specific group as parameter' do
- let(:backfilled_namespace) { parent_group1 }
-
- it 'back-fills project namespaces for the specified group hierarchy' do
- backfilled_namespace_projects = base_ancestor(backfilled_namespace).first.all_projects
- start_id = backfilled_namespace_projects.minimum(:id)
- end_id = backfilled_namespace_projects.maximum(:id)
- group_projects_count = backfilled_namespace_projects.count
- batches_count = (group_projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
- project_namespaces_in_hierarchy = project_namespaces_in_hierarchy(base_ancestor(backfilled_namespace))
+ before do
+ BackfillProjectNamespaces::TreeGenerator.new(namespaces, projects, [parent_group1, parent_group2], child_nodes_count, tree_depth).build_tree
+ end
+ describe '#up' do
+ shared_examples 'back-fill project namespaces' do
+ it 'back-fills all project namespaces' do
+ start_id = ::Project.minimum(:id)
+ end_id = ::Project.maximum(:id)
+ projects_count = ::Project.count
+ batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
+ project_namespaces_count = ::Namespace.where(type: 'Project').count
migration = described_class.new
- expect(project_namespaces_in_hierarchy.count).to eq(0)
+ expect(projects_count).not_to eq(project_namespaces_count)
expect(migration).to receive(:batch_insert_namespaces).exactly(batches_count).and_call_original
expect(migration).to receive(:batch_update_projects).exactly(batches_count).and_call_original
expect(migration).to receive(:batch_update_project_namespaces_traversal_ids).exactly(batches_count).and_call_original
- expect(group_projects_count).to eq(14)
- expect(project_namespaces_in_hierarchy.count).to eq(0)
-
- migration.perform(start_id, end_id, nil, nil, nil, nil, backfilled_namespace.id, 'up')
+ expect { migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up') }.to change(Namespace.where(type: 'Project'), :count)
- expect(project_namespaces_in_hierarchy.count).to eq(14)
- check_projects_in_sync_with(project_namespaces_in_hierarchy)
+ expect(projects_count).to eq(::Namespace.where(type: 'Project').count)
+ check_projects_in_sync_with(Namespace.where(type: 'Project'))
end
- end
- context 'when projects already have project namespaces' do
- before do
- hierarchy1_projects = base_ancestor(parent_group1).first.all_projects
- start_id = hierarchy1_projects.minimum(:id)
- end_id = hierarchy1_projects.maximum(:id)
+ context 'when passing specific group as parameter' do
+ let(:backfilled_namespace) { parent_group1 }
- described_class.new.perform(start_id, end_id, nil, nil, nil, nil, parent_group1.id, 'up')
- end
+ it 'back-fills project namespaces for the specified group hierarchy' do
+ backfilled_namespace_projects = base_ancestor(backfilled_namespace).first.all_projects
+ start_id = backfilled_namespace_projects.minimum(:id)
+ end_id = backfilled_namespace_projects.maximum(:id)
+ group_projects_count = backfilled_namespace_projects.count
+ batches_count = (group_projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
+ project_namespaces_in_hierarchy = project_namespaces_in_hierarchy(base_ancestor(backfilled_namespace))
- it 'does not duplicate project namespaces' do
- # check there are already some project namespaces but not for all
- projects_count = ::Project.count
- start_id = ::Project.minimum(:id)
- end_id = ::Project.maximum(:id)
- batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
- project_namespaces = ::Namespace.where(type: 'Project')
- migration = described_class.new
+ migration = described_class.new
- expect(project_namespaces_in_hierarchy(base_ancestor(parent_group1)).count).to be >= 14
- expect(project_namespaces_in_hierarchy(base_ancestor(parent_group2)).count).to eq(0)
- expect(projects_count).not_to eq(project_namespaces.count)
+ expect(project_namespaces_in_hierarchy.count).to eq(0)
+ expect(migration).to receive(:batch_insert_namespaces).exactly(batches_count).and_call_original
+ expect(migration).to receive(:batch_update_projects).exactly(batches_count).and_call_original
+ expect(migration).to receive(:batch_update_project_namespaces_traversal_ids).exactly(batches_count).and_call_original
- # run migration again to test we do not generate extra project namespaces
- expect(migration).to receive(:batch_insert_namespaces).exactly(batches_count).and_call_original
- expect(migration).to receive(:batch_update_projects).exactly(batches_count).and_call_original
- expect(migration).to receive(:batch_update_project_namespaces_traversal_ids).exactly(batches_count).and_call_original
+ expect(group_projects_count).to eq(14)
+ expect(project_namespaces_in_hierarchy.count).to eq(0)
- expect { migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up') }.to change(project_namespaces, :count).by(14)
+ migration.perform(start_id, end_id, nil, nil, nil, nil, backfilled_namespace.id, 'up')
- expect(projects_count).to eq(project_namespaces.count)
+ expect(project_namespaces_in_hierarchy.count).to eq(14)
+ check_projects_in_sync_with(project_namespaces_in_hierarchy)
+ end
end
- end
- end
- it 'checks no project namespaces exist in the defined hierarchies' do
- hierarchy1_project_namespaces = project_namespaces_in_hierarchy(base_ancestor(parent_group1))
- hierarchy2_project_namespaces = project_namespaces_in_hierarchy(base_ancestor(parent_group2))
- hierarchy1_projects_count = base_ancestor(parent_group1).first.all_projects.count
- hierarchy2_projects_count = base_ancestor(parent_group2).first.all_projects.count
+ context 'when projects already have project namespaces' do
+ before do
+ hierarchy1_projects = base_ancestor(parent_group1).first.all_projects
+ start_id = hierarchy1_projects.minimum(:id)
+ end_id = hierarchy1_projects.maximum(:id)
+
+ described_class.new.perform(start_id, end_id, nil, nil, nil, nil, parent_group1.id, 'up')
+ end
+
+ it 'does not duplicate project namespaces' do
+ # check there are already some project namespaces but not for all
+ projects_count = ::Project.count
+ start_id = ::Project.minimum(:id)
+ end_id = ::Project.maximum(:id)
+ batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
+ project_namespaces = ::Namespace.where(type: 'Project')
+ migration = described_class.new
+
+ expect(project_namespaces_in_hierarchy(base_ancestor(parent_group1)).count).to be >= 14
+ expect(project_namespaces_in_hierarchy(base_ancestor(parent_group2)).count).to eq(0)
+ expect(projects_count).not_to eq(project_namespaces.count)
+
+ # run migration again to test we do not generate extra project namespaces
+ expect(migration).to receive(:batch_insert_namespaces).exactly(batches_count).and_call_original
+ expect(migration).to receive(:batch_update_projects).exactly(batches_count).and_call_original
+ expect(migration).to receive(:batch_update_project_namespaces_traversal_ids).exactly(batches_count).and_call_original
+
+ expect { migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up') }.to change(project_namespaces, :count).by(14)
+
+ expect(projects_count).to eq(project_namespaces.count)
+ end
+ end
+ end
- expect(hierarchy1_project_namespaces).to be_empty
- expect(hierarchy2_project_namespaces).to be_empty
- expect(hierarchy1_projects_count).to eq(14)
- expect(hierarchy2_projects_count).to eq(14)
- end
+ it 'checks no project namespaces exist in the defined hierarchies' do
+ hierarchy1_project_namespaces = project_namespaces_in_hierarchy(base_ancestor(parent_group1))
+ hierarchy2_project_namespaces = project_namespaces_in_hierarchy(base_ancestor(parent_group2))
+ hierarchy1_projects_count = base_ancestor(parent_group1).first.all_projects.count
+ hierarchy2_projects_count = base_ancestor(parent_group2).first.all_projects.count
- context 'back-fill project namespaces in a single batch' do
- it_behaves_like 'back-fill project namespaces'
- end
+ expect(hierarchy1_project_namespaces).to be_empty
+ expect(hierarchy2_project_namespaces).to be_empty
+ expect(hierarchy1_projects_count).to eq(14)
+ expect(hierarchy2_projects_count).to eq(14)
+ end
- context 'back-fill project namespaces in batches' do
- before do
- stub_const("#{described_class.name}::SUB_BATCH_SIZE", 2)
+ context 'back-fill project namespaces in a single batch' do
+ it_behaves_like 'back-fill project namespaces'
end
- it_behaves_like 'back-fill project namespaces'
- end
- end
+ context 'back-fill project namespaces in batches' do
+ before do
+ stub_const("#{described_class.name}::SUB_BATCH_SIZE", 2)
+ end
- describe '#down' do
- before do
- start_id = ::Project.minimum(:id)
- end_id = ::Project.maximum(:id)
- # back-fill first
- described_class.new.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up')
+ it_behaves_like 'back-fill project namespaces'
+ end
end
- shared_examples 'cleanup project namespaces' do
- it 'removes project namespaces' do
- projects_count = ::Project.count
+ describe '#down' do
+ before do
start_id = ::Project.minimum(:id)
end_id = ::Project.maximum(:id)
- migration = described_class.new
- batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
+ # back-fill first
+ described_class.new.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up')
+ end
- expect(projects_count).to be > 0
- expect(projects_count).to eq(::Namespace.where(type: 'Project').count)
+ shared_examples 'cleanup project namespaces' do
+ it 'removes project namespaces' do
+ projects_count = ::Project.count
+ start_id = ::Project.minimum(:id)
+ end_id = ::Project.maximum(:id)
+ migration = described_class.new
+ batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
- expect(migration).to receive(:nullify_project_namespaces_in_projects).exactly(batches_count).and_call_original
- expect(migration).to receive(:delete_project_namespace_records).exactly(batches_count).and_call_original
+ expect(projects_count).to be > 0
+ expect(projects_count).to eq(::Namespace.where(type: 'Project').count)
- migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'down')
+ expect(migration).to receive(:nullify_project_namespaces_in_projects).exactly(batches_count).and_call_original
+ expect(migration).to receive(:delete_project_namespace_records).exactly(batches_count).and_call_original
- expect(::Project.count).to be > 0
- expect(::Namespace.where(type: 'Project').count).to eq(0)
- end
+ migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'down')
+
+ expect(::Project.count).to be > 0
+ expect(::Namespace.where(type: 'Project').count).to eq(0)
+ end
- context 'when passing specific group as parameter' do
- let(:backfilled_namespace) { parent_group1 }
+ context 'when passing specific group as parameter' do
+ let(:backfilled_namespace) { parent_group1 }
- it 'removes project namespaces only for the specific group hierarchy' do
- backfilled_namespace_projects = base_ancestor(backfilled_namespace).first.all_projects
- start_id = backfilled_namespace_projects.minimum(:id)
- end_id = backfilled_namespace_projects.maximum(:id)
- group_projects_count = backfilled_namespace_projects.count
- batches_count = (group_projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
- project_namespaces_in_hierarchy = project_namespaces_in_hierarchy(base_ancestor(backfilled_namespace))
- migration = described_class.new
+ it 'removes project namespaces only for the specific group hierarchy' do
+ backfilled_namespace_projects = base_ancestor(backfilled_namespace).first.all_projects
+ start_id = backfilled_namespace_projects.minimum(:id)
+ end_id = backfilled_namespace_projects.maximum(:id)
+ group_projects_count = backfilled_namespace_projects.count
+ batches_count = (group_projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
+ project_namespaces_in_hierarchy = project_namespaces_in_hierarchy(base_ancestor(backfilled_namespace))
+ migration = described_class.new
- expect(project_namespaces_in_hierarchy.count).to eq(14)
- expect(migration).to receive(:nullify_project_namespaces_in_projects).exactly(batches_count).and_call_original
- expect(migration).to receive(:delete_project_namespace_records).exactly(batches_count).and_call_original
+ expect(project_namespaces_in_hierarchy.count).to eq(14)
+ expect(migration).to receive(:nullify_project_namespaces_in_projects).exactly(batches_count).and_call_original
+ expect(migration).to receive(:delete_project_namespace_records).exactly(batches_count).and_call_original
- migration.perform(start_id, end_id, nil, nil, nil, nil, backfilled_namespace.id, 'down')
+ migration.perform(start_id, end_id, nil, nil, nil, nil, backfilled_namespace.id, 'down')
- expect(::Namespace.where(type: 'Project').count).to be > 0
- expect(project_namespaces_in_hierarchy.count).to eq(0)
+ expect(::Namespace.where(type: 'Project').count).to be > 0
+ expect(project_namespaces_in_hierarchy.count).to eq(0)
+ end
end
end
- end
- context 'cleanup project namespaces in a single batch' do
- it_behaves_like 'cleanup project namespaces'
- end
-
- context 'cleanup project namespaces in batches' do
- before do
- stub_const("#{described_class.name}::SUB_BATCH_SIZE", 2)
+ context 'cleanup project namespaces in a single batch' do
+ it_behaves_like 'cleanup project namespaces'
end
- it_behaves_like 'cleanup project namespaces'
+ context 'cleanup project namespaces in batches' do
+ before do
+ stub_const("#{described_class.name}::SUB_BATCH_SIZE", 2)
+ end
+
+ it_behaves_like 'cleanup project namespaces'
+ end
end
end
end
+ it_behaves_like 'backfills project namespaces'
+
+ context 'when namespaces.id is bigint' do
+ before do
+ namespaces.connection.execute("ALTER TABLE namespaces ALTER COLUMN id TYPE bigint")
+ end
+
+ it_behaves_like 'backfills project namespaces'
+ end
+
def base_ancestor(ancestor)
::Namespace.where(id: ancestor.id)
end
@@ -209,7 +221,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
def check_projects_in_sync_with(namespaces)
project_namespaces_attrs = namespaces.order(:id).pluck(:id, :name, :path, :parent_id, :visibility_level, :shared_runners_enabled)
corresponding_projects_attrs = Project.where(project_namespace_id: project_namespaces_attrs.map(&:first))
- .order(:project_namespace_id).pluck(:project_namespace_id, :name, :path, :namespace_id, :visibility_level, :shared_runners_enabled)
+ .order(:project_namespace_id).pluck(:project_namespace_id, :name, :path, :namespace_id, :visibility_level, :shared_runners_enabled)
expect(project_namespaces_attrs).to eq(corresponding_projects_attrs)
end
diff --git a/spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb b/spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb
index 8d71b117107..a609227be05 100644
--- a/spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb
+++ b/spec/lib/gitlab/background_migration/recalculate_vulnerabilities_occurrences_uuid_spec.rb
@@ -20,7 +20,7 @@ def create_background_migration_job(ids, status)
)
end
-RSpec.describe Gitlab::BackgroundMigration::RecalculateVulnerabilitiesOccurrencesUuid, schema: 20211124132705 do
+RSpec.describe Gitlab::BackgroundMigration::RecalculateVulnerabilitiesOccurrencesUuid, :suppress_gitlab_schemas_validate_connection, schema: 20211124132705 do
let(:background_migration_jobs) { table(:background_migration_jobs) }
let(:pending_jobs) { background_migration_jobs.where(status: Gitlab::Database::BackgroundMigrationJob.statuses['pending']) }
let(:succeeded_jobs) { background_migration_jobs.where(status: Gitlab::Database::BackgroundMigrationJob.statuses['succeeded']) }
diff --git a/spec/lib/gitlab/background_migration/remove_all_trace_expiration_dates_spec.rb b/spec/lib/gitlab/background_migration/remove_all_trace_expiration_dates_spec.rb
index 8cdcec9621c..eabc012f98b 100644
--- a/spec/lib/gitlab/background_migration/remove_all_trace_expiration_dates_spec.rb
+++ b/spec/lib/gitlab/background_migration/remove_all_trace_expiration_dates_spec.rb
@@ -2,7 +2,8 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::RemoveAllTraceExpirationDates, :migration, schema: 20220131000001 do
+RSpec.describe Gitlab::BackgroundMigration::RemoveAllTraceExpirationDates, :migration,
+ :suppress_gitlab_schemas_validate_connection, schema: 20220131000001 do
subject(:perform) { migration.perform(1, 99) }
let(:migration) { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb b/spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb
index 07cff32304e..33ad74fbee8 100644
--- a/spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb
+++ b/spec/lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::RemoveOccurrencePipelinesAndDuplicateVulnerabilitiesFindings, :migration, schema: 20220326161803 do
+RSpec.describe Gitlab::BackgroundMigration::RemoveOccurrencePipelinesAndDuplicateVulnerabilitiesFindings, :migration,
+ :suppress_gitlab_schemas_validate_connection, schema: 20220326161803 do
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:users) { table(:users) }
let(:user) { create_user! }
diff --git a/spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb b/spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb
index 035ea6eadcf..e9f73672144 100644
--- a/spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_legacy_open_source_license_available_for_non_public_projects_spec.rb
@@ -4,14 +4,14 @@ require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::SetLegacyOpenSourceLicenseAvailableForNonPublicProjects,
:migration,
- schema: 20220520040416 do
+ schema: 20220722110026 do
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
let(:project_settings_table) { table(:project_settings) }
subject(:perform_migration) do
- described_class.new(start_id: 1,
- end_id: 30,
+ described_class.new(start_id: projects_table.minimum(:id),
+ end_id: projects_table.maximum(:id),
batch_table: :projects,
batch_column: :id,
sub_batch_size: 2,
@@ -20,35 +20,34 @@ RSpec.describe Gitlab::BackgroundMigration::SetLegacyOpenSourceLicenseAvailableF
.perform
end
- let(:queries) { ActiveRecord::QueryRecorder.new { perform_migration } }
+ it 'sets `legacy_open_source_license_available` attribute to false for non-public projects', :aggregate_failures do
+ private_project = create_legacy_license_project('private-project', visibility_level: 0)
+ internal_project = create_legacy_license_project('internal-project', visibility_level: 10)
+ public_project = create_legacy_license_project('public-project', visibility_level: 20)
- before do
- namespaces_table.create!(id: 1, name: 'namespace', path: 'namespace-path-1')
- namespaces_table.create!(id: 2, name: 'namespace', path: 'namespace-path-2', type: 'Project')
- namespaces_table.create!(id: 3, name: 'namespace', path: 'namespace-path-3', type: 'Project')
- namespaces_table.create!(id: 4, name: 'namespace', path: 'namespace-path-4', type: 'Project')
+ queries = ActiveRecord::QueryRecorder.new { perform_migration }
- projects_table
- .create!(id: 11, name: 'proj-1', path: 'path-1', namespace_id: 1, project_namespace_id: 2, visibility_level: 0)
- projects_table
- .create!(id: 12, name: 'proj-2', path: 'path-2', namespace_id: 1, project_namespace_id: 3, visibility_level: 10)
- projects_table
- .create!(id: 13, name: 'proj-3', path: 'path-3', namespace_id: 1, project_namespace_id: 4, visibility_level: 20)
+ expect(queries.count).to eq(5)
- project_settings_table.create!(project_id: 11, legacy_open_source_license_available: true)
- project_settings_table.create!(project_id: 12, legacy_open_source_license_available: true)
- project_settings_table.create!(project_id: 13, legacy_open_source_license_available: true)
+ expect(migrated_attribute(private_project)).to be_falsey
+ expect(migrated_attribute(internal_project)).to be_falsey
+ expect(migrated_attribute(public_project)).to be_truthy
end
- it 'sets `legacy_open_source_license_available` attribute to false for non-public projects', :aggregate_failures do
- expect(queries.count).to eq(3)
-
- expect(migrated_attribute(11)).to be_falsey
- expect(migrated_attribute(12)).to be_falsey
- expect(migrated_attribute(13)).to be_truthy
+ def create_legacy_license_project(path, visibility_level:)
+ namespace = namespaces_table.create!(name: "namespace-#{path}", path: "namespace-#{path}")
+ project_namespace = namespaces_table.create!(name: "project-namespace-#{path}", path: path, type: 'Project')
+ project = projects_table.create!(name: path,
+ path: path,
+ namespace_id: namespace.id,
+ project_namespace_id: project_namespace.id,
+ visibility_level: visibility_level)
+ project_settings_table.create!(project_id: project.id, legacy_open_source_license_available: true)
+
+ project
end
- def migrated_attribute(project_id)
- project_settings_table.find(project_id).legacy_open_source_license_available
+ def migrated_attribute(project)
+ project_settings_table.find(project.id).legacy_open_source_license_available
end
end
diff --git a/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb b/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb
index b96d3f7f0b5..c090c1df424 100644
--- a/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb
+++ b/spec/lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url_spec.rb
@@ -2,10 +2,26 @@
require 'spec_helper'
-RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeBasedOnUrl, schema: 20210421163509 do
- let(:services_table) { table(:services) }
- let(:service_jira_cloud) { services_table.create!(id: 1, type: 'JiraService') }
- let(:service_jira_server) { services_table.create!(id: 2, type: 'JiraService') }
+RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeBasedOnUrl do
+ let(:integrations_table) { table(:integrations) }
+ let(:service_jira_cloud) { integrations_table.create!(id: 1, type_new: 'JiraService') }
+ let(:service_jira_server) { integrations_table.create!(id: 2, type_new: 'JiraService') }
+ let(:service_jira_unknown) { integrations_table.create!(id: 3, type_new: 'JiraService') }
+
+ let(:table_name) { :jira_tracker_data }
+ let(:batch_column) { :id }
+ let(:sub_batch_size) { 1 }
+ let(:pause_ms) { 0 }
+ let(:migration) do
+ described_class.new(start_id: 1, end_id: 10,
+ batch_table: table_name, batch_column: batch_column,
+ sub_batch_size: sub_batch_size, pause_ms: pause_ms,
+ connection: ApplicationRecord.connection)
+ end
+
+ subject(:perform_migration) do
+ migration.perform
+ end
before do
jira_tracker_data = Class.new(ApplicationRecord) do
@@ -27,18 +43,21 @@ RSpec.describe Gitlab::BackgroundMigration::UpdateJiraTrackerDataDeploymentTypeB
end
stub_const('JiraTrackerData', jira_tracker_data)
- end
- let!(:tracker_data_cloud) { JiraTrackerData.create!(id: 1, service_id: service_jira_cloud.id, url: "https://test-domain.atlassian.net", deployment_type: 0) }
- let!(:tracker_data_server) { JiraTrackerData.create!(id: 2, service_id: service_jira_server.id, url: "http://totally-not-jira-server.company.org", deployment_type: 0) }
+ stub_const('UNKNOWN', 0)
+ stub_const('SERVER', 1)
+ stub_const('CLOUD', 2)
+ end
- subject { described_class.new.perform(tracker_data_cloud.id, tracker_data_server.id) }
+ let!(:tracker_data_cloud) { JiraTrackerData.create!(id: 1, integration_id: service_jira_cloud.id, url: "https://test-domain.atlassian.net", deployment_type: UNKNOWN) }
+ let!(:tracker_data_server) { JiraTrackerData.create!(id: 2, integration_id: service_jira_server.id, url: "http://totally-not-jira-server.company.org", deployment_type: UNKNOWN) }
+ let!(:tracker_data_unknown) { JiraTrackerData.create!(id: 3, integration_id: service_jira_unknown.id, url: "", deployment_type: UNKNOWN) }
it "changes unknown deployment_types based on URL" do
- expect(JiraTrackerData.pluck(:deployment_type)).to eq([0, 0])
+ expect(JiraTrackerData.pluck(:deployment_type)).to match_array([UNKNOWN, UNKNOWN, UNKNOWN])
- subject
+ perform_migration
- expect(JiraTrackerData.pluck(:deployment_type)).to eq([2, 1])
+ expect(JiraTrackerData.order(:id).pluck(:deployment_type)).to match_array([CLOUD, SERVER, UNKNOWN])
end
end
diff --git a/spec/lib/gitlab/background_task_spec.rb b/spec/lib/gitlab/background_task_spec.rb
new file mode 100644
index 00000000000..102556b6b2f
--- /dev/null
+++ b/spec/lib/gitlab/background_task_spec.rb
@@ -0,0 +1,209 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+# We need to capture task state from a closure, which requires instance variables.
+# rubocop: disable RSpec/InstanceVariable
+RSpec.describe Gitlab::BackgroundTask do
+ let(:options) { {} }
+ let(:task) do
+ proc do
+ @task_run = true
+ @task_thread = Thread.current
+ end
+ end
+
+ subject(:background_task) { described_class.new(task, **options) }
+
+ def expect_condition
+ Timeout.timeout(3) do
+ sleep 0.1 until yield
+ end
+ end
+
+ context 'when stopped' do
+ it 'is not running' do
+ expect(background_task).not_to be_running
+ end
+
+ describe '#start' do
+ it 'runs the given task on a background thread' do
+ test_thread = Thread.current
+
+ background_task.start
+
+ expect_condition { @task_run == true }
+ expect_condition { @task_thread != test_thread }
+ expect(background_task).to be_running
+ end
+
+ it 'returns self' do
+ expect(background_task.start).to be(background_task)
+ end
+
+ context 'when installing exit handler' do
+ it 'stops a running background task' do
+ expect(background_task).to receive(:at_exit).and_yield
+
+ background_task.start
+
+ expect(background_task).not_to be_running
+ end
+ end
+
+ context 'when task responds to start' do
+ let(:task_class) do
+ Struct.new(:started, :start_retval, :run) do
+ def start
+ self.started = true
+ self.start_retval
+ end
+
+ def call
+ self.run = true
+ end
+ end
+ end
+
+ let(:task) { task_class.new }
+
+ it 'calls start' do
+ background_task.start
+
+ expect_condition { task.started == true }
+ end
+
+ context 'when start returns true' do
+ it 'runs the task' do
+ task.start_retval = true
+
+ background_task.start
+
+ expect_condition { task.run == true }
+ end
+ end
+
+ context 'when start returns false' do
+ it 'does not run the task' do
+ task.start_retval = false
+
+ background_task.start
+
+ expect_condition { task.run.nil? }
+ end
+ end
+ end
+
+ context 'when synchronous is set to true' do
+ let(:options) { { synchronous: true } }
+
+ it 'calls join on the thread' do
+ # Thread has to be run in a block, expect_next_instance_of does not support this.
+ allow_any_instance_of(Thread).to receive(:join) # rubocop:disable RSpec/AnyInstanceOf
+
+ background_task.start
+
+ expect_condition { @task_run == true }
+ expect(@task_thread).to have_received(:join)
+ end
+ end
+ end
+
+ describe '#stop' do
+ it 'is a no-op' do
+ expect { background_task.stop }.not_to change { subject.running? }
+ expect_condition { @task_run.nil? }
+ end
+ end
+ end
+
+ context 'when running' do
+ before do
+ background_task.start
+ end
+
+ describe '#start' do
+ it 'raises an error' do
+ expect { background_task.start }.to raise_error(described_class::AlreadyStartedError)
+ end
+ end
+
+ describe '#stop' do
+ it 'stops running' do
+ expect { background_task.stop }.to change { subject.running? }.from(true).to(false)
+ end
+
+ context 'when task responds to stop' do
+ let(:task_class) do
+ Struct.new(:stopped, :call) do
+ def stop
+ self.stopped = true
+ end
+ end
+ end
+
+ let(:task) { task_class.new }
+
+ it 'calls stop' do
+ background_task.stop
+
+ expect_condition { task.stopped == true }
+ end
+ end
+
+ context 'when task stop raises an error' do
+ let(:error) { RuntimeError.new('task error') }
+ let(:options) { { name: 'test_background_task' } }
+
+ let(:task_class) do
+ Struct.new(:call, :error, keyword_init: true) do
+ def stop
+ raise error
+ end
+ end
+ end
+
+ let(:task) { task_class.new(error: error) }
+
+ it 'stops gracefully' do
+ expect { background_task.stop }.not_to raise_error
+ expect(background_task).not_to be_running
+ end
+
+ it 'reports the error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ error, { extra: { reported_by: 'test_background_task' } }
+ )
+
+ background_task.stop
+ end
+ end
+ end
+
+ context 'when task run raises exception' do
+ let(:error) { RuntimeError.new('task error') }
+ let(:options) { { name: 'test_background_task' } }
+ let(:task) do
+ proc do
+ @task_run = true
+ raise error
+ end
+ end
+
+ it 'stops gracefully' do
+ expect_condition { @task_run == true }
+ expect { background_task.stop }.not_to raise_error
+ expect(background_task).not_to be_running
+ end
+
+ it 'reports the error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ error, { extra: { reported_by: 'test_background_task' } }
+ )
+
+ background_task.stop
+ end
+ end
+ end
+end
+# rubocop: enable RSpec/InstanceVariable
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index d29447ee376..becfdced5fb 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -54,16 +54,16 @@ RSpec.describe ::Gitlab::BareRepositoryImport::Repository do
end
context 'hashed storage' do
- let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' }
let(:hashed_path) { "@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" }
let(:root_path) { TestEnv.repos_path }
let(:repo_path) { File.join(root_path, "#{hashed_path}.git") }
let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") }
let(:raw_repository) { Gitlab::Git::Repository.new('default', "#{hashed_path}.git", nil, nil) }
+ let(:full_path) { 'to/repo' }
before do
raw_repository.create_repository
- raw_repository.set_full_path(full_path: 'to/repo')
+ raw_repository.set_full_path(full_path: full_path) if full_path
end
after do
@@ -95,16 +95,17 @@ RSpec.describe ::Gitlab::BareRepositoryImport::Repository do
expect(subject).not_to be_processable
end
- it 'returns false when group and project name are missing' do
- repository = Rugged::Repository.new(repo_path)
- repository.config.delete('gitlab.fullpath')
-
- expect(subject).not_to be_processable
- end
-
it 'returns true when group path and project name are present' do
expect(subject).to be_processable
end
+
+ context 'group and project name are missing' do
+ let(:full_path) { nil }
+
+ it 'returns false' do
+ expect(subject).not_to be_processable
+ end
+ end
end
describe '#project_full_path' do
diff --git a/spec/lib/gitlab/batch_pop_queueing_spec.rb b/spec/lib/gitlab/batch_pop_queueing_spec.rb
index 41efc5417e4..5af78ddabe7 100644
--- a/spec/lib/gitlab/batch_pop_queueing_spec.rb
+++ b/spec/lib/gitlab/batch_pop_queueing_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::BatchPopQueueing do
context 'when the queue key does not exist in Redis' do
before do
- allow(queue).to receive(:enqueue) { }
+ allow(queue).to receive(:enqueue) {}
end
it 'yields empty array' do
diff --git a/spec/lib/gitlab/chat_name_token_spec.rb b/spec/lib/gitlab/chat_name_token_spec.rb
index 906c02d54db..8d5702a6b27 100644
--- a/spec/lib/gitlab/chat_name_token_spec.rb
+++ b/spec/lib/gitlab/chat_name_token_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::ChatNameToken do
context 'when using unknown token' do
- let(:token) { }
+ let(:token) {}
subject { described_class.new(token).get }
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index 27c2b005a93..30359a7170f 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -210,8 +210,8 @@ RSpec.describe Gitlab::Ci::Ansi2html do
let(:section_start_time) { Time.new(2017, 9, 20).utc }
let(:section_duration) { 3.seconds }
let(:section_end_time) { section_start_time + section_duration }
- let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"}
- let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"}
+ let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K" }
+ let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K" }
let(:section_start_html) do
'<div class="section-start"' \
" data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{class_name(section_name)}\"" \
@@ -258,13 +258,13 @@ RSpec.describe Gitlab::Ci::Ansi2html do
it_behaves_like 'a legit section'
context 'section name includes $' do
- let(:section_name) { 'my_$ection'}
+ let(:section_name) { 'my_$ection' }
it_behaves_like 'forbidden char in section_name'
end
context 'section name includes <' do
- let(:section_name) { '<a_tag>'}
+ let(:section_name) { '<a_tag>' }
it_behaves_like 'forbidden char in section_name'
end
diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb
index f9d23ff97bc..4b3b049176f 100644
--- a/spec/lib/gitlab/ci/ansi2json_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2json_spec.rb
@@ -78,8 +78,8 @@ RSpec.describe Gitlab::Ci::Ansi2json do
let(:section_duration) { 63.seconds }
let(:section_start_time) { Time.new(2019, 9, 17).utc }
let(:section_end_time) { section_start_time + section_duration }
- let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"}
- let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"}
+ let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K" }
+ let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K" }
it 'marks the first line of the section as header' do
expect(convert_json("Hello#{section_start}world!")).to eq([
@@ -258,8 +258,8 @@ RSpec.describe Gitlab::Ci::Ansi2json do
let(:nested_section_duration) { 2.seconds }
let(:nested_section_start_time) { Time.new(2019, 9, 17).utc }
let(:nested_section_end_time) { nested_section_start_time + nested_section_duration }
- let(:nested_section_start) { "section_start:#{nested_section_start_time.to_i}:#{nested_section_name}\r\033[0K"}
- let(:nested_section_end) { "section_end:#{nested_section_end_time.to_i}:#{nested_section_name}\r\033[0K"}
+ let(:nested_section_start) { "section_start:#{nested_section_start_time.to_i}:#{nested_section_name}\r\033[0K" }
+ let(:nested_section_end) { "section_end:#{nested_section_end_time.to_i}:#{nested_section_name}\r\033[0K" }
it 'adds multiple sections to the lines inside the nested section' do
trace = "Hello#{section_start}foo#{nested_section_start}bar#{nested_section_end}baz#{section_end}world"
@@ -342,7 +342,7 @@ RSpec.describe Gitlab::Ci::Ansi2json do
end
context 'with section options' do
- let(:option_section_start) { "section_start:#{section_start_time.to_i}:#{section_name}[collapsed=true,unused_option=123]\r\033[0K"}
+ let(:option_section_start) { "section_start:#{section_start_time.to_i}:#{section_name}[collapsed=true,unused_option=123]\r\033[0K" }
it 'provides section options when set' do
trace = "#{option_section_start}hello#{section_end}"
@@ -463,8 +463,8 @@ RSpec.describe Gitlab::Ci::Ansi2json do
let(:section_duration) { 63.seconds }
let(:section_start_time) { Time.new(2019, 9, 17).utc }
let(:section_end_time) { section_start_time + section_duration }
- let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"}
- let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"}
+ let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K" }
+ let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K" }
context 'with split section body' do
let(:pre_text) { "#{section_start}this is a header\nand " }
diff --git a/spec/lib/gitlab/ci/artifacts/logger_spec.rb b/spec/lib/gitlab/ci/artifacts/logger_spec.rb
new file mode 100644
index 00000000000..7753cb0d25e
--- /dev/null
+++ b/spec/lib/gitlab/ci/artifacts/logger_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Artifacts::Logger do
+ before do
+ Gitlab::ApplicationContext.push(feature_category: 'test', caller_id: 'caller')
+ end
+
+ describe '.log_created' do
+ it 'logs information about created artifact' do
+ artifact = create(:ci_job_artifact, :archive)
+
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Artifact created',
+ job_artifact_id: artifact.id,
+ size: artifact.size,
+ type: artifact.file_type,
+ build_id: artifact.job_id,
+ project_id: artifact.project_id,
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
+ )
+
+ described_class.log_created(artifact)
+ end
+ end
+
+ describe '.log_deleted' do
+ it 'logs information about deleted artifacts' do
+ artifact_1 = create(:ci_job_artifact, :archive, :expired)
+ artifact_2 = create(:ci_job_artifact, :archive)
+ artifacts = [artifact_1, artifact_2]
+ method = 'Foo#method'
+
+ artifacts.each do |artifact|
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ message: 'Artifact deleted',
+ job_artifact_id: artifact.id,
+ expire_at: artifact.expire_at,
+ size: artifact.size,
+ type: artifact.file_type,
+ build_id: artifact.job_id,
+ project_id: artifact.project_id,
+ method: method,
+ 'correlation_id' => an_instance_of(String),
+ 'meta.feature_category' => 'test',
+ 'meta.caller_id' => 'caller'
+ )
+ )
+ end
+
+ described_class.log_deleted(artifacts, method)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/artifacts/metrics_spec.rb b/spec/lib/gitlab/ci/artifacts/metrics_spec.rb
index 0ce76285b03..39e440f09e1 100644
--- a/spec/lib/gitlab/ci/artifacts/metrics_spec.rb
+++ b/spec/lib/gitlab/ci/artifacts/metrics_spec.rb
@@ -5,6 +5,25 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Artifacts::Metrics, :prometheus do
let(:metrics) { described_class.new }
+ describe '.build_completed_report_type_counter' do
+ context 'when incrementing by more than one' do
+ let(:sast_counter) { described_class.send(:build_completed_report_type_counter, :sast) }
+ let(:dast_counter) { described_class.send(:build_completed_report_type_counter, :dast) }
+
+ it 'increments a single counter' do
+ [dast_counter, sast_counter].each do |counter|
+ counter.increment(status: 'success')
+ counter.increment(status: 'success')
+ counter.increment(status: 'failed')
+
+ expect(counter.get(status: 'success')).to eq 2.0
+ expect(counter.get(status: 'failed')).to eq 1.0
+ expect(counter.values.count).to eq 2
+ end
+ end
+ end
+ end
+
describe '#increment_destroyed_artifacts' do
context 'when incrementing by more than one' do
let(:counter) { metrics.send(:destroyed_artifacts_counter) }
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/zip_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/zip_stream_spec.rb
new file mode 100644
index 00000000000..2c236ba3726
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/zip_stream_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Build::Artifacts::Adapters::ZipStream do
+ let(:file_name) { 'single_file.zip' }
+ let(:fixture_path) { "lib/gitlab/ci/build/artifacts/adapters/zip_stream/#{file_name}" }
+ let(:stream) { File.open(expand_fixture_path(fixture_path), 'rb') }
+
+ describe '#initialize' do
+ it 'initializes when stream is passed' do
+ expect { described_class.new(stream) }.not_to raise_error
+ end
+
+ context 'when stream is not passed' do
+ let(:stream) { nil }
+
+ it 'raises an error' do
+ expect { described_class.new(stream) }.to raise_error(described_class::InvalidStreamError)
+ end
+ end
+ end
+
+ describe '#each_blob' do
+ let(:adapter) { described_class.new(stream) }
+
+ context 'when stream is a zip file' do
+ it 'iterates file content when zip file contains one file' do
+ expect { |b| adapter.each_blob(&b) }
+ .to yield_with_args("file 1 content\n")
+ end
+
+ context 'when zip file contains multiple files' do
+ let(:file_name) { 'multiple_files.zip' }
+
+ it 'iterates content of all files' do
+ expect { |b| adapter.each_blob(&b) }
+ .to yield_successive_args("file 1 content\n", "file 2 content\n")
+ end
+ end
+
+ context 'when zip file includes files in a directory' do
+ let(:file_name) { 'with_directory.zip' }
+
+ it 'iterates contents from files only' do
+ expect { |b| adapter.each_blob(&b) }
+ .to yield_successive_args("file 1 content\n", "file 2 content\n")
+ end
+ end
+
+ context 'when zip contains a file which decompresses beyond the size limit' do
+ let(:file_name) { '200_mb_decompressed.zip' }
+
+ it 'does not read the file' do
+ expect { |b| adapter.each_blob(&b) }.not_to yield_control
+ end
+ end
+
+ context 'when the zip contains too many files' do
+ let(:file_name) { '100_files.zip' }
+
+ it 'stops processing when the limit is reached' do
+ expect { |b| adapter.each_blob(&b) }
+ .to yield_control.exactly(described_class::MAX_FILES_PROCESSED).times
+ end
+ end
+
+ context 'when stream is a zipbomb' do
+ let(:file_name) { 'zipbomb.zip' }
+
+ it 'does not read the file' do
+ expect { |b| adapter.each_blob(&b) }.not_to yield_control
+ end
+ end
+ end
+
+ context 'when stream is not a zip file' do
+ let(:stream) { File.open(expand_fixture_path('junit/junit.xml.gz'), 'rb') }
+
+ it 'does not yield any data' do
+ expect { |b| adapter.each_blob(&b) }.not_to yield_control
+ expect { adapter.each_blob { |b| b } }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
index c8ace28108b..7b35c9ba483 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -67,6 +67,7 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
subject { |example| path(example).children }
it { is_expected.to all(be_an_instance_of(described_class)) }
+
it do
is_expected.to contain_exactly entry('path/dir_1/file_1'),
entry('path/dir_1/file_b'),
@@ -79,6 +80,7 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
it { is_expected.to all(be_file) }
it { is_expected.to all(be_an_instance_of(described_class)) }
+
it do
is_expected.to contain_exactly entry('path/dir_1/file_1'),
entry('path/dir_1/file_b')
@@ -99,6 +101,7 @@ RSpec.describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
it { is_expected.to all(be_directory) }
it { is_expected.to all(be_an_instance_of(described_class)) }
+
it do
is_expected.to contain_exactly entry('path/dir_1/subdir/'),
entry('path/')
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
index 94c14cfa479..baabab73ea2 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -74,7 +74,7 @@ RSpec.describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
context 'kubernetes namespace does not exist' do
- let(:namespace_builder) { double(execute: kubernetes_namespace)}
+ let(:namespace_builder) { double(execute: kubernetes_namespace) }
before do
allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
diff --git a/spec/lib/gitlab/ci/build/releaser_spec.rb b/spec/lib/gitlab/ci/build/releaser_spec.rb
index 435f70e9ac5..ffa7073818a 100644
--- a/spec/lib/gitlab/ci/build/releaser_spec.rb
+++ b/spec/lib/gitlab/ci/build/releaser_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe Gitlab::Ci::Build::Releaser do
name: 'Release $CI_COMMIT_SHA',
description: 'Created using the release-cli $EXTRA_DESCRIPTION',
tag_name: 'release-$CI_COMMIT_SHA',
+ tag_message: 'Annotated tag message',
ref: '$CI_COMMIT_SHA',
milestones: %w[m1 m2 m3],
released_at: '2020-07-15T08:00:00Z',
@@ -27,7 +28,7 @@ RSpec.describe Gitlab::Ci::Build::Releaser do
end
it 'generates the script' do
- expect(subject).to eq(['release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA" --released-at "2020-07-15T08:00:00Z" --milestone "m1" --milestone "m2" --milestone "m3" --assets-link "{\"name\":\"asset1\",\"url\":\"https://example.com/assets/1\",\"link_type\":\"other\",\"filepath\":\"/pretty/asset/1\"}" --assets-link "{\"name\":\"asset2\",\"url\":\"https://example.com/assets/2\"}"'])
+ expect(subject).to eq(['release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --tag-message "Annotated tag message" --ref "$CI_COMMIT_SHA" --released-at "2020-07-15T08:00:00Z" --milestone "m1" --milestone "m2" --milestone "m3" --assets-link "{\"name\":\"asset1\",\"url\":\"https://example.com/assets/1\",\"link_type\":\"other\",\"filepath\":\"/pretty/asset/1\"}" --assets-link "{\"name\":\"asset2\",\"url\":\"https://example.com/assets/2\"}"'])
end
end
@@ -39,6 +40,7 @@ RSpec.describe Gitlab::Ci::Build::Releaser do
:name | 'Release $CI_COMMIT_SHA' | 'release-cli create --name "Release $CI_COMMIT_SHA"'
:description | 'Release-cli $EXTRA_DESCRIPTION' | 'release-cli create --description "Release-cli $EXTRA_DESCRIPTION"'
:tag_name | 'release-$CI_COMMIT_SHA' | 'release-cli create --tag-name "release-$CI_COMMIT_SHA"'
+ :tag_message | 'Annotated tag message' | 'release-cli create --tag-message "Annotated tag message"'
:ref | '$CI_COMMIT_SHA' | 'release-cli create --ref "$CI_COMMIT_SHA"'
:milestones | %w[m1 m2 m3] | 'release-cli create --milestone "m1" --milestone "m2" --milestone "m3"'
:released_at | '2020-07-15T08:00:00Z' | 'release-cli create --released-at "2020-07-15T08:00:00Z"'
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
index 3892b88598a..234ba68d627 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/changes_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
describe '#satisfied_by?' do
- subject { described_class.new(globs).satisfied_by?(pipeline, context) }
+ let(:context) { instance_double(Gitlab::Ci::Build::Context::Base) }
+
+ subject(:satisfied_by) { described_class.new(globs).satisfied_by?(pipeline, context) }
context 'a glob matching rule' do
using RSpec::Parameterized::TableSyntax
@@ -18,11 +20,9 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
# rubocop:disable Layout/LineLength
where(:case_name, :globs, :files, :satisfied) do
- 'exact top-level match' | ['Dockerfile'] | { 'Dockerfile' => '', 'Gemfile' => '' } | true
'exact top-level match' | { paths: ['Dockerfile'] } | { 'Dockerfile' => '', 'Gemfile' => '' } | true
'exact top-level no match' | { paths: ['Dockerfile'] } | { 'Gemfile' => '' } | false
'pattern top-level match' | { paths: ['Docker*'] } | { 'Dockerfile' => '', 'Gemfile' => '' } | true
- 'pattern top-level no match' | ['Docker*'] | { 'Gemfile' => '' } | false
'pattern top-level no match' | { paths: ['Docker*'] } | { 'Gemfile' => '' } | false
'exact nested match' | { paths: ['project/build.properties'] } | { 'project/build.properties' => '' } | true
'exact nested no match' | { paths: ['project/build.properties'] } | { 'project/README.md' => '' } | false
@@ -92,5 +92,97 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Changes do
it { is_expected.to be_truthy }
end
end
+
+ context 'when using compare_to' do
+ let_it_be(:project) do
+ create(:project, :custom_repo,
+ files: { 'README.md' => 'readme' })
+ end
+
+ let_it_be(:user) { project.owner }
+
+ before_all do
+ project.repository.add_branch(user, 'feature_1', 'master')
+
+ project.repository.create_file(
+ user, 'file1.txt', 'file 1', message: 'Create file1.txt', branch_name: 'feature_1'
+ )
+ project.repository.add_tag(user, 'tag_1', 'feature_1')
+
+ project.repository.create_file(
+ user, 'file2.txt', 'file 2', message: 'Create file2.txt', branch_name: 'feature_1'
+ )
+ project.repository.add_branch(user, 'feature_2', 'feature_1')
+
+ project.repository.update_file(
+ user, 'file2.txt', 'file 2 updated', message: 'Update file2.txt', branch_name: 'feature_2'
+ )
+ end
+
+ context 'when compare_to is branch or tag' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:pipeline_ref, :compare_to, :paths, :ff, :result) do
+ 'feature_1' | 'master' | ['file1.txt'] | true | true
+ 'feature_1' | 'master' | ['README.md'] | true | false
+ 'feature_1' | 'master' | ['xyz.md'] | true | false
+ 'feature_2' | 'master' | ['file1.txt'] | true | true
+ 'feature_2' | 'master' | ['file2.txt'] | true | true
+ 'feature_2' | 'feature_1' | ['file1.txt'] | true | false
+ 'feature_2' | 'feature_1' | ['file1.txt'] | false | true
+ 'feature_2' | 'feature_1' | ['file2.txt'] | true | true
+ 'feature_1' | 'tag_1' | ['file1.txt'] | true | false
+ 'feature_1' | 'tag_1' | ['file1.txt'] | false | true
+ 'feature_1' | 'tag_1' | ['file2.txt'] | true | true
+ 'feature_2' | 'tag_1' | ['file2.txt'] | true | true
+ end
+
+ with_them do
+ let(:globs) { { paths: paths, compare_to: compare_to } }
+
+ let(:pipeline) do
+ build(:ci_pipeline, project: project, ref: pipeline_ref, sha: project.commit(pipeline_ref).sha)
+ end
+
+ before do
+ stub_feature_flags(ci_rules_changes_compare: ff)
+ end
+
+ it { is_expected.to eq(result) }
+ end
+ end
+
+ context 'when compare_to is a sha' do
+ let(:globs) { { paths: ['file2.txt'], compare_to: project.commit('tag_1').sha } }
+
+ let(:pipeline) do
+ build(:ci_pipeline, project: project, ref: 'feature_2', sha: project.commit('feature_2').sha)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when compare_to is not a valid ref' do
+ let(:globs) { { paths: ['file1.txt'], compare_to: 'xyz' } }
+
+ let(:pipeline) do
+ build(:ci_pipeline, project: project, ref: 'feature_2', sha: project.commit('feature_2').sha)
+ end
+
+ it 'raises ParseError' do
+ expect { satisfied_by }.to raise_error(
+ ::Gitlab::Ci::Build::Rules::Rule::Clause::ParseError, 'rules:changes:compare_to is not a valid ref'
+ )
+ end
+
+ context 'when the FF ci_rules_changes_compare is disabled' do
+ before do
+ stub_feature_flags(ci_rules_changes_compare: false)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb
index 81bce989833..31c7437cfe0 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/if_spec.rb
@@ -51,14 +51,6 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::If do
end
it { is_expected.to eq(true) }
-
- context 'when the FF ci_fix_rules_if_comparison_with_regexp_variable is disabled' do
- before do
- stub_feature_flags(ci_fix_rules_if_comparison_with_regexp_variable: false)
- end
-
- it { is_expected.to eq(false) }
- end
end
context 'when comparison is false' do
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index 0fa6d4f8804..6121c28070f 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -1,12 +1,8 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
-require 'support/helpers/stubbed_feature'
-require 'support/helpers/stub_feature_flags'
+require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Image do
- include StubFeatureFlags
-
before do
stub_feature_flags(ci_docker_image_pull_policy: true)
diff --git a/spec/lib/gitlab/ci/config/entry/imageable_spec.rb b/spec/lib/gitlab/ci/config/entry/imageable_spec.rb
new file mode 100644
index 00000000000..88f8e260611
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/imageable_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Config::Entry::Imageable do
+ let(:node_class) do
+ Class.new(::Gitlab::Config::Entry::Node) do
+ include ::Gitlab::Ci::Config::Entry::Imageable
+
+ validations do
+ validates :config, allowed_keys: ::Gitlab::Ci::Config::Entry::Imageable::IMAGEABLE_ALLOWED_KEYS
+ end
+
+ def self.name
+ 'node'
+ end
+
+ def value
+ if string?
+ { name: @config }
+ elsif hash?
+ {
+ name: @config[:name]
+ }.compact
+ else
+ {}
+ end
+ end
+ end
+ end
+
+ subject(:entry) { node_class.new(config) }
+
+ before do
+ entry.compose!
+ end
+
+ context 'when entry value is correct' do
+ let(:config) { 'image:1.0' }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ let(:config) { ['image:1.0'] }
+
+ describe '#errors' do
+ it 'saves errors' do
+ expect(entry.errors.first)
+ .to match /config should be a hash or a string/
+ end
+ end
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+
+ context 'when unexpected key is specified' do
+ let(:config) { { name: 'image:1.0', non_existing: 'test' } }
+
+ describe '#errors' do
+ it 'saves errors' do
+ expect(entry.errors.first)
+ .to match /config contains unknown keys: non_existing/
+ end
+ end
+
+ describe '#valid?' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index 5b9337ede34..714b0a3b6aa 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -212,7 +212,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
let(:unspecified) { double('unspecified', 'specified?' => false) }
let(:default) { double('default', '[]' => unspecified) }
let(:workflow) { double('workflow', 'has_rules?' => false) }
- let(:variables) { }
+ let(:variables) {}
let(:deps) do
double('deps',
diff --git a/spec/lib/gitlab/ci/config/entry/release_spec.rb b/spec/lib/gitlab/ci/config/entry/release_spec.rb
index e5155f91be4..7b6b31ca748 100644
--- a/spec/lib/gitlab/ci/config/entry/release_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/release_spec.rb
@@ -128,25 +128,25 @@ RSpec.describe Gitlab::Ci::Config::Entry::Release do
end
context "when 'ref' is a short commit SHA" do
- let(:ref) { 'b3235930'}
+ let(:ref) { 'b3235930' }
it_behaves_like 'a valid entry'
end
context "when 'ref' is a branch name" do
- let(:ref) { 'fix/123-branch-name'}
+ let(:ref) { 'fix/123-branch-name' }
it_behaves_like 'a valid entry'
end
context "when 'ref' is a semantic versioning tag" do
- let(:ref) { 'v1.2.3'}
+ let(:ref) { 'v1.2.3' }
it_behaves_like 'a valid entry'
end
context "when 'ref' is a semantic versioning tag rc" do
- let(:ref) { 'v1.2.3-rc'}
+ let(:ref) { 'v1.2.3-rc' }
it_behaves_like 'a valid entry'
end
@@ -188,6 +188,30 @@ RSpec.describe Gitlab::Ci::Config::Entry::Release do
end
end
+ context "when value includes 'tag_message' keyword" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ tag_message: "Annotated tag message"
+ }
+ end
+
+ it_behaves_like 'a valid entry'
+ end
+
+ context "when 'tag_message' is nil" do
+ let(:config) do
+ {
+ tag_name: 'v0.06',
+ description: "./release_changelog.txt",
+ tag_message: nil
+ }
+ end
+
+ it_behaves_like 'a valid entry'
+ end
+
context 'when entry value is not correct' do
describe '#errors' do
context 'when value of attribute is invalid' do
@@ -231,6 +255,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Release do
it_behaves_like 'reports error', 'release milestones should be an array of strings or a string'
end
+
+ context 'when `tag_message` is not a string' do
+ let(:config) { { tag_message: 100 } }
+
+ it_behaves_like 'reports error', 'release tag message should be a string'
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 051cccb4833..45aa859a356 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -47,6 +47,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do
:dotenv | 'build.dotenv'
:terraform | 'tfplan.json'
:accessibility | 'gl-accessibility.json'
+ :cyclonedx | 'gl-sbom.cdx.zip'
end
with_them do
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 55ad119ea21..1f8543227c9 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -155,7 +155,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }],
cache: [{ key: "k", untracked: true, paths: ["public/"], policy: "pull-push", when: 'on_success' }],
only: { refs: %w(branches tags) },
- job_variables: { 'VAR' => 'job' },
+ job_variables: { 'VAR' => { value: 'job' } },
root_variables_inheritance: true,
after_script: [],
ignore: false,
@@ -215,7 +215,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Root do
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: [{ key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push', when: 'on_success' }],
- job_variables: { 'VAR' => 'job' },
+ job_variables: { 'VAR' => { value: 'job' } },
root_variables_inheritance: true,
ignore: false,
after_script: ['make clean'],
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb
index 295561b3c4d..64f0a64074c 100644
--- a/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule/changes_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do
let(:factory) do
@@ -119,6 +119,23 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do
end
end
end
+
+ context 'with paths and compare_to' do
+ let(:config) { { paths: %w[app/ lib/], compare_to: 'branch1' } }
+
+ it { is_expected.to be_valid }
+
+ context 'when compare_to is not a string' do
+ let(:config) { { paths: %w[app/ lib/], compare_to: 1 } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns information about errors' do
+ expect(entry.errors)
+ .to include(/should be a string/)
+ end
+ end
+ end
end
describe '#value' do
@@ -137,5 +154,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule::Changes do
it { is_expected.to eq(config) }
end
+
+ context 'with paths and compare_to' do
+ let(:config) do
+ { paths: ['app/', 'lib/'], compare_to: 'branch1' }
+ end
+
+ it { is_expected.to eq(config) }
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
index 93f4a66bfb6..c85fe366da6 100644
--- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
@@ -2,7 +2,6 @@
require 'fast_spec_helper'
require 'gitlab_chronic_duration'
-require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do
@@ -418,6 +417,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do
it { is_expected.to eq(config) }
end
+
+ context 'when using changes with paths and compare_to' do
+ let(:config) { { changes: { paths: %w[app/ lib/ spec/ other/* paths/**/*.rb], compare_to: 'branch1' } } }
+
+ it { is_expected.to eq(config) }
+ end
end
context 'when default value has been provided' do
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index 3c000fd09ed..821ab442d61 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -1,12 +1,8 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
-require 'support/helpers/stubbed_feature'
-require 'support/helpers/stub_feature_flags'
+require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Service do
- include StubFeatureFlags
-
before do
stub_feature_flags(ci_docker_image_pull_policy: true)
entry.compose!
diff --git a/spec/lib/gitlab/ci/config/entry/tags_spec.rb b/spec/lib/gitlab/ci/config/entry/tags_spec.rb
index e05d4ae52b2..24efd08c855 100644
--- a/spec/lib/gitlab/ci/config/entry/tags_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/tags_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Tags do
end
context 'when tags limit is reached' do
- let(:config) { Array.new(50) {|i| "tag-#{i}" } }
+ let(:config) { Array.new(50) { |i| "tag-#{i}" } }
it 'reports error' do
expect(entry.errors)
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index 280bebe1a7c..1306d61d99c 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::File::Base do
- let(:variables) { }
+ let(:variables) {}
let(:context_params) { { sha: 'HEAD', variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
@@ -100,7 +100,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Base do
describe '#to_hash' do
context 'with includes' do
let(:location) { 'some/file/config.yml' }
- let(:content) { 'include: { template: Bash.gitlab-ci.yml }'}
+ let(:content) { 'include: { template: Bash.gitlab-ci.yml }' }
before do
allow_any_instance_of(test_class)
diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
index 0e78498c98e..f5b36ebfa45 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -167,7 +167,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Local do
describe '#to_hash' do
context 'properly includes another local file in the same repository' do
let(:location) { 'some/file/config.yml' }
- let(:content) { 'include: { local: another-config.yml }'}
+ let(:content) { 'include: { local: another-config.yml }' }
let(:another_location) { 'another-config.yml' }
let(:another_content) { 'rspec: JOB' }
diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index 3e1c4df4e32..45dfea636f3 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::File::Remote do
include StubRequests
- let(:variables) {Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
+ let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) }
let(:context_params) { { sha: '12345', variables: variables } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:params) { { remote: location } }
diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb
index 354392eb42e..96ca5d98a6e 100644
--- a/spec/lib/gitlab/ci/config/normalizer_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb
@@ -232,7 +232,7 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
context 'when parallel config does not matches a factory' do
let(:variables_config) { {} }
- let(:parallel_config) { }
+ let(:parallel_config) {}
it 'does not alter the job config' do
is_expected.to match(config)
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 5eb04d969eb..055114769ea 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -872,4 +872,21 @@ RSpec.describe Gitlab::Ci::Config do
end
end
end
+
+ describe '#workflow_rules' do
+ subject(:workflow_rules) { config.workflow_rules }
+
+ let(:yml) do
+ <<-EOS
+ workflow:
+ rules:
+ - if: $CI_COMMIT_REF_NAME == "master"
+
+ rspec:
+ script: exit 0
+ EOS
+ end
+
+ it { is_expected.to eq([{ if: '$CI_COMMIT_REF_NAME == "master"' }]) }
+ end
end
diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index 4017accb462..33474865a93 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -178,7 +178,7 @@ RSpec.describe Gitlab::Ci::CronParser do
end
context 'when time crosses a Daylight Savings boundary' do
- let(:cron) { '* 0 1 12 *'}
+ let(:cron) { '* 0 1 12 *' }
# Note this previously only failed if the time zone is set
# to a zone that observes Daylight Savings
diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb
new file mode 100644
index 00000000000..c99cfa94aa6
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_properties_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Sbom::CyclonedxProperties do
+ subject(:parse_source) { described_class.parse_source(properties) }
+
+ context 'when properties are nil' do
+ let(:properties) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when report does not have gitlab properties' do
+ let(:properties) { ['name' => 'foo', 'value' => 'bar'] }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when schema_version is missing' do
+ let(:properties) do
+ [
+ { 'name' => 'gitlab:dependency_scanning:dependency_file', 'value' => 'package-lock.json' },
+ { 'name' => 'gitlab:dependency_scanning:package_manager_name', 'value' => 'npm' },
+ { 'name' => 'gitlab:dependency_scanning:language', 'value' => 'JavaScript' }
+ ]
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when schema version is unsupported' do
+ let(:properties) do
+ [
+ { 'name' => 'gitlab:meta:schema_version', 'value' => '2' },
+ { 'name' => 'gitlab:dependency_scanning:dependency_file', 'value' => 'package-lock.json' },
+ { 'name' => 'gitlab:dependency_scanning:package_manager_name', 'value' => 'npm' },
+ { 'name' => 'gitlab:dependency_scanning:language', 'value' => 'JavaScript' }
+ ]
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when no dependency_scanning properties are present' do
+ let(:properties) do
+ [
+ { 'name' => 'gitlab:meta:schema_version', 'value' => '1' }
+ ]
+ end
+
+ it 'does not call dependency_scanning parser' do
+ expect(Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning).not_to receive(:parse_source)
+
+ parse_source
+ end
+ end
+
+ context 'when dependency_scanning properties are present' do
+ let(:properties) do
+ [
+ { 'name' => 'gitlab:meta:schema_version', 'value' => '1' },
+ { 'name' => 'gitlab:dependency_scanning:category', 'value' => 'development' },
+ { 'name' => 'gitlab:dependency_scanning:input_file:path', 'value' => 'package-lock.json' },
+ { 'name' => 'gitlab:dependency_scanning:source_file:path', 'value' => 'package.json' },
+ { 'name' => 'gitlab:dependency_scanning:package_manager:name', 'value' => 'npm' },
+ { 'name' => 'gitlab:dependency_scanning:language:name', 'value' => 'JavaScript' },
+ { 'name' => 'gitlab:dependency_scanning:unsupported_property', 'value' => 'Should be ignored' }
+ ]
+ end
+
+ let(:expected_input) do
+ {
+ 'category' => 'development',
+ 'input_file' => { 'path' => 'package-lock.json' },
+ 'source_file' => { 'path' => 'package.json' },
+ 'package_manager' => { 'name' => 'npm' },
+ 'language' => { 'name' => 'JavaScript' }
+ }
+ end
+
+ it 'passes only supported properties to the dependency scanning parser' do
+ expect(Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning).to receive(:source).with(expected_input)
+
+ parse_source
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb
new file mode 100644
index 00000000000..431fe9f3591
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/sbom/cyclonedx_spec.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Sbom::Cyclonedx do
+ let(:report) { instance_double('Gitlab::Ci::Reports::Sbom::Report') }
+ let(:report_data) { base_report_data }
+ let(:raw_report_data) { report_data.to_json }
+ let(:report_valid?) { true }
+ let(:validator_errors) { [] }
+ let(:properties_parser) { class_double('Gitlab::Ci::Parsers::Sbom::CyclonedxProperties') }
+
+ let(:base_report_data) do
+ {
+ 'bomFormat' => 'CycloneDX',
+ 'specVersion' => '1.4',
+ 'version' => 1
+ }
+ end
+
+ subject(:parse!) { described_class.new.parse!(raw_report_data, report) }
+
+ before do
+ allow_next_instance_of(Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator) do |validator|
+ allow(validator).to receive(:valid?).and_return(report_valid?)
+ allow(validator).to receive(:errors).and_return(validator_errors)
+ end
+
+ allow(properties_parser).to receive(:parse_source)
+ stub_const('Gitlab::Ci::Parsers::Sbom::CyclonedxProperties', properties_parser)
+ end
+
+ context 'when report JSON is invalid' do
+ let(:raw_report_data) { '{ ' }
+
+ it 'handles errors and adds them to the report' do
+ expect(report).to receive(:add_error).with(a_string_including("Report JSON is invalid:"))
+
+ expect { parse! }.not_to raise_error
+ end
+ end
+
+ context 'when report uses an unsupported spec version' do
+ let(:report_data) { base_report_data.merge({ 'specVersion' => '1.3' }) }
+
+ it 'reports unsupported version as an error' do
+ expect(report).to receive(:add_error).with("Unsupported CycloneDX spec version. Must be one of: 1.4")
+
+ parse!
+ end
+ end
+
+ context 'when report does not conform to the CycloneDX schema' do
+ let(:report_valid?) { false }
+ let(:validator_errors) { %w[error1 error2] }
+
+ it 'reports all errors returned by the validator' do
+ expect(report).to receive(:add_error).with("error1")
+ expect(report).to receive(:add_error).with("error2")
+
+ parse!
+ end
+ end
+
+ context 'when cyclonedx report has no components' do
+ it 'skips component processing' do
+ expect(report).not_to receive(:add_component)
+
+ parse!
+ end
+ end
+
+ context 'when report has components' do
+ let(:report_data) { base_report_data.merge({ 'components' => components }) }
+ let(:components) do
+ [
+ {
+ "name" => "activesupport",
+ "version" => "5.1.4",
+ "purl" => "pkg:gem/activesupport@5.1.4",
+ "type" => "library",
+ "bom-ref" => "pkg:gem/activesupport@5.1.4"
+ },
+ {
+ "name" => "byebug",
+ "version" => "10.0.0",
+ "purl" => "pkg:gem/byebug@10.0.0",
+ "type" => "library",
+ "bom-ref" => "pkg:gem/byebug@10.0.0"
+ },
+ {
+ "name" => "minimal-component",
+ "type" => "library"
+ },
+ {
+ # Should be skipped
+ "name" => "unrecognized-type",
+ "type" => "unknown"
+ }
+ ]
+ end
+
+ it 'adds each component, ignoring unused attributes' do
+ expect(report).to receive(:add_component)
+ .with({ "name" => "activesupport", "version" => "5.1.4", "type" => "library" })
+ expect(report).to receive(:add_component)
+ .with({ "name" => "byebug", "version" => "10.0.0", "type" => "library" })
+ expect(report).to receive(:add_component)
+ .with({ "name" => "minimal-component", "type" => "library" })
+
+ parse!
+ end
+ end
+
+ context 'when report has metadata properties' do
+ let(:report_data) { base_report_data.merge({ 'metadata' => { 'properties' => properties } }) }
+
+ let(:properties) do
+ [
+ { 'name' => 'gitlab:meta:schema_version', 'value' => '1' },
+ { 'name' => 'gitlab:dependency_scanning:category', 'value' => 'development' },
+ { 'name' => 'gitlab:dependency_scanning:input_file:path', 'value' => 'package-lock.json' },
+ { 'name' => 'gitlab:dependency_scanning:source_file:path', 'value' => 'package.json' },
+ { 'name' => 'gitlab:dependency_scanning:package_manager:name', 'value' => 'npm' },
+ { 'name' => 'gitlab:dependency_scanning:language:name', 'value' => 'JavaScript' }
+ ]
+ end
+
+ it 'passes them to the properties parser' do
+ expect(properties_parser).to receive(:parse_source).with(properties)
+
+ parse!
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb
new file mode 100644
index 00000000000..30114b17cac
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/sbom/source/dependency_scanning_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Parsers::Sbom::Source::DependencyScanning do
+ subject { described_class.source(property_data) }
+
+ context 'when all property data is present' do
+ let(:property_data) do
+ {
+ 'category' => 'development',
+ 'input_file' => { 'path' => 'package-lock.json' },
+ 'source_file' => { 'path' => 'package.json' },
+ 'package_manager' => { 'name' => 'npm' },
+ 'language' => { 'name' => 'JavaScript' }
+ }
+ end
+
+ it 'returns expected source data' do
+ is_expected.to eq({
+ 'type' => :dependency_scanning,
+ 'data' => property_data,
+ 'fingerprint' => '4dbcb747e6f0fb3ed4f48d96b777f1d64acdf43e459fdfefad404e55c004a188'
+ })
+ end
+ end
+
+ context 'when required properties are missing' do
+ let(:property_data) do
+ {
+ 'category' => 'development',
+ 'source_file' => { 'path' => 'package.json' },
+ 'package_manager' => { 'name' => 'npm' },
+ 'language' => { 'name' => 'JavaScript' }
+ }
+ end
+
+ it { is_expected.to be_nil }
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb
new file mode 100644
index 00000000000..c54a3268bbe
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator_spec.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Gitlab::Ci::Parsers::Sbom::Validators::CyclonedxSchemaValidator do
+ # Reports should be valid or invalid according to the specification at
+ # https://cyclonedx.org/docs/1.4/json/
+
+ subject(:validator) { described_class.new(report_data) }
+
+ let_it_be(:required_attributes) do
+ {
+ "bomFormat" => "CycloneDX",
+ "specVersion" => "1.4",
+ "version" => 1
+ }
+ end
+
+ context "with minimally valid report" do
+ let_it_be(:report_data) { required_attributes }
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when report has components" do
+ let(:report_data) { required_attributes.merge({ "components" => components }) }
+
+ context "with minimally valid components" do
+ let(:components) do
+ [
+ {
+ "type" => "library",
+ "name" => "activesupport"
+ },
+ {
+ "type" => "library",
+ "name" => "byebug"
+ }
+ ]
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when components have versions" do
+ let(:components) do
+ [
+ {
+ "type" => "library",
+ "name" => "activesupport",
+ "version" => "5.1.4"
+ },
+ {
+ "type" => "library",
+ "name" => "byebug",
+ "version" => "10.0.0"
+ }
+ ]
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when components are not valid" do
+ let(:components) do
+ [
+ { "type" => "foo" },
+ { "name" => "activesupport" }
+ ]
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it "outputs errors for each validation failure" do
+ expect(validator.errors).to match_array([
+ "property '/components/0' is missing required keys: name",
+ "property '/components/0/type' is not one of: [\"application\", \"framework\"," \
+ " \"library\", \"container\", \"operating-system\", \"device\", \"firmware\", \"file\"]",
+ "property '/components/1' is missing required keys: type"
+ ])
+ end
+ end
+ end
+
+ context "when report has metadata" do
+ let(:metadata) do
+ {
+ "timestamp" => "2022-02-23T08:02:39Z",
+ "tools" => [{ "vendor" => "GitLab", "name" => "Gemnasium", "version" => "2.34.0" }],
+ "authors" => [{ "name" => "GitLab", "email" => "support@gitlab.com" }]
+ }
+ end
+
+ let(:report_data) { required_attributes.merge({ "metadata" => metadata }) }
+
+ it { is_expected.to be_valid }
+
+ context "when metadata has properties" do
+ before do
+ metadata.merge!({ "properties" => properties })
+ end
+
+ context "when properties are valid" do
+ let(:properties) do
+ [
+ { "name" => "gitlab:dependency_scanning:input_file", "value" => "Gemfile.lock" },
+ { "name" => "gitlab:dependency_scanning:package_manager", "value" => "bundler" }
+ ]
+ end
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when properties are invalid" do
+ let(:properties) do
+ [
+ { "name" => ["gitlab:meta:schema_version"], "value" => 1 }
+ ]
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it "outputs errors for each validation failure" do
+ expect(validator.errors).to match_array([
+ "property '/metadata/properties/0/name' is not of type: string",
+ "property '/metadata/properties/0/value' is not of type: string"
+ ])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb
index d06077d69b6..7828aa99f6a 100644
--- a/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/security/validators/schema_validator_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
let_it_be(:project) { create(:project) }
let(:supported_dast_versions) { described_class::SUPPORTED_VERSIONS[:dast].join(', ') }
+ let(:deprecated_schema_version_message) {}
+ let(:missing_schema_version_message) do
+ "Report version not provided, dast report type supports versions: #{supported_dast_versions}"
+ end
let(:scanner) do
{
@@ -24,7 +28,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
expect(described_class::SUPPORTED_VERSIONS.keys).to eq(described_class::DEPRECATED_VERSIONS.keys)
end
- context 'when a schema JSON file exists for a particular report type version' do
+ context 'when all files under schema path are explicitly listed' do
# We only care about the part that comes before report-format.json
# https://rubular.com/r/N8Juz7r8hYDYgD
filename_regex = /(?<report_type>[-\w]*)\-report-format.json/
@@ -38,7 +42,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
matches = filename_regex.match(file)
report_type = matches[:report_type].tr("-", "_").to_sym
- it "#{report_type} #{version} is in the constant" do
+ it "#{report_type} #{version}" do
expect(described_class::SUPPORTED_VERSIONS[report_type]).to include(version)
end
end
@@ -64,11 +68,54 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
describe '#valid?' do
subject { validator.valid? }
+ context 'when given a supported MAJOR.MINOR schema version' do
+ let(:report_type) { :dast }
+ let(:report_version) do
+ latest_vendored_version = described_class::SUPPORTED_VERSIONS[report_type].last.split(".")
+ (latest_vendored_version[0...2] << "34").join(".")
+ end
+
+ context 'and the report is valid' do
+ let(:report_data) do
+ {
+ 'version' => report_version,
+ 'vulnerabilities' => []
+ }
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'and the report is invalid' do
+ let(:report_data) do
+ {
+ 'version' => report_version
+ }
+ end
+
+ it { is_expected.to be_falsey }
+
+ it 'logs related information' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ message: "security report schema validation problem",
+ security_report_type: report_type,
+ security_report_version: report_version,
+ project_id: project.id,
+ security_report_failure: 'schema_validation_fails',
+ security_report_scanner_id: 'gemnasium',
+ security_report_scanner_version: '2.1.0'
+ )
+
+ subject
+ end
+ end
+ end
+
context 'when given a supported schema version' do
let(:report_type) { :dast }
let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last }
- context 'when the report is valid' do
+ context 'and the report is valid' do
let(:report_data) do
{
'version' => report_version,
@@ -79,7 +126,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
it { is_expected.to be_truthy }
end
- context 'when the report is invalid' do
+ context 'and the report is invalid' do
let(:report_data) do
{
'version' => report_version
@@ -118,7 +165,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash)
end
- context 'when the report passes schema validation' do
+ context 'and the report passes schema validation' do
let(:report_data) do
{
'version' => '10.0.0',
@@ -143,34 +190,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
end
end
- context 'when the report does not pass schema validation' do
- context 'when enforce_security_report_validation is enabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: true)
- end
-
- let(:report_data) do
- {
- 'version' => 'V2.7.0'
- }
- end
-
- it { is_expected.to be_falsey }
+ context 'and the report does not pass schema validation' do
+ let(:report_data) do
+ {
+ 'version' => 'V2.7.0'
+ }
end
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
-
- let(:report_data) do
- {
- 'version' => 'V2.7.0'
- }
- end
-
- it { is_expected.to be_truthy }
- end
+ it { is_expected.to be_falsey }
end
end
@@ -178,20 +205,40 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
let(:report_type) { :dast }
let(:report_version) { "12.37.0" }
- context 'when enforce_security_report_validation is enabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: true)
+ context 'and the report is valid' do
+ let(:report_data) do
+ {
+ 'version' => report_version,
+ 'vulnerabilities' => []
+ }
end
- context 'when the report is valid' do
- let(:report_data) do
- {
- 'version' => report_version,
- 'vulnerabilities' => []
- }
- end
+ it { is_expected.to be_falsey }
+
+ it 'logs related information' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ message: "security report schema validation problem",
+ security_report_type: report_type,
+ security_report_version: report_version,
+ project_id: project.id,
+ security_report_failure: 'using_unsupported_schema_version',
+ security_report_scanner_id: 'gemnasium',
+ security_report_scanner_version: '2.1.0'
+ )
- it { is_expected.to be_falsey }
+ subject
+ end
+ end
+
+ context 'and the report is invalid' do
+ let(:report_data) do
+ {
+ 'version' => report_version
+ }
+ end
+
+ context 'and scanner information is empty' do
+ let(:scanner) { {} }
it 'logs related information' do
expect(Gitlab::AppLogger).to receive(:info).with(
@@ -199,79 +246,26 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
security_report_type: report_type,
security_report_version: report_version,
project_id: project.id,
+ security_report_failure: 'schema_validation_fails',
+ security_report_scanner_id: nil,
+ security_report_scanner_version: nil
+ )
+
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ message: "security report schema validation problem",
+ security_report_type: report_type,
+ security_report_version: report_version,
+ project_id: project.id,
security_report_failure: 'using_unsupported_schema_version',
- security_report_scanner_id: 'gemnasium',
- security_report_scanner_version: '2.1.0'
+ security_report_scanner_id: nil,
+ security_report_scanner_version: nil
)
subject
end
end
- context 'when the report is invalid' do
- let(:report_data) do
- {
- 'version' => report_version
- }
- end
-
- context 'when scanner information is empty' do
- let(:scanner) { {} }
-
- it 'logs related information' do
- expect(Gitlab::AppLogger).to receive(:info).with(
- message: "security report schema validation problem",
- security_report_type: report_type,
- security_report_version: report_version,
- project_id: project.id,
- security_report_failure: 'schema_validation_fails',
- security_report_scanner_id: nil,
- security_report_scanner_version: nil
- )
-
- expect(Gitlab::AppLogger).to receive(:info).with(
- message: "security report schema validation problem",
- security_report_type: report_type,
- security_report_version: report_version,
- project_id: project.id,
- security_report_failure: 'using_unsupported_schema_version',
- security_report_scanner_id: nil,
- security_report_scanner_version: nil
- )
-
- subject
- end
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
-
- context 'when the report is valid' do
- let(:report_data) do
- {
- 'version' => report_version,
- 'vulnerabilities' => []
- }
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when the report is invalid' do
- let(:report_data) do
- {
- 'version' => report_version
- }
- end
-
- it { is_expected.to be_truthy }
- end
+ it { is_expected.to be_falsey }
end
end
@@ -284,19 +278,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
}
end
- before do
- stub_feature_flags(enforce_security_report_validation: true)
- end
-
it { is_expected.to be_falsey }
-
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
-
- it { is_expected.to be_truthy }
- end
end
end
@@ -307,7 +289,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
let(:report_type) { :dast }
let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last }
- context 'when the report is valid' do
+ context 'and the report is valid' do
let(:report_data) do
{
'version' => report_version,
@@ -318,34 +300,20 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
it { is_expected.to be_empty }
end
- context 'when the report is invalid' do
+ context 'and the report is invalid' do
let(:report_data) do
{
'version' => report_version
}
end
- context 'when enforce_security_report_validation is enabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: project)
- end
-
- let(:expected_errors) do
- [
- 'root is missing required keys: vulnerabilities'
- ]
- end
-
- it { is_expected.to match_array(expected_errors) }
+ let(:expected_errors) do
+ [
+ 'root is missing required keys: vulnerabilities'
+ ]
end
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
-
- it { is_expected.to be_empty }
- end
+ it { is_expected.to match_array(expected_errors) }
end
end
@@ -363,7 +331,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash)
end
- context 'when the report passes schema validation' do
+ context 'and the report passes schema validation' do
let(:report_data) do
{
'version' => '10.0.0',
@@ -374,119 +342,77 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
it { is_expected.to be_empty }
end
- context 'when the report does not pass schema validation' do
- context 'when enforce_security_report_validation is enabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: true)
- end
-
- let(:report_data) do
- {
- 'version' => 'V2.7.0'
- }
- end
-
- let(:expected_errors) do
- [
- "property '/version' does not match pattern: ^[0-9]+\\.[0-9]+\\.[0-9]+$",
- "root is missing required keys: vulnerabilities"
- ]
- end
-
- it { is_expected.to match_array(expected_errors) }
+ context 'and the report does not pass schema validation' do
+ let(:report_data) do
+ {
+ 'version' => 'V2.7.0'
+ }
end
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
-
- let(:report_data) do
- {
- 'version' => 'V2.7.0'
- }
- end
-
- it { is_expected.to be_empty }
+ let(:expected_errors) do
+ [
+ "property '/version' does not match pattern: ^[0-9]+\\.[0-9]+\\.[0-9]+$",
+ "root is missing required keys: vulnerabilities"
+ ]
end
+
+ it { is_expected.to match_array(expected_errors) }
end
end
context 'when given an unsupported schema version' do
let(:report_type) { :dast }
let(:report_version) { "12.37.0" }
+ let(:expected_unsupported_message) do
+ "Version #{report_version} for report type #{report_type} is unsupported, supported versions for this report type are: "\
+ "#{supported_dast_versions}. GitLab will attempt to validate this report against the earliest supported "\
+ "versions of this report type, to show all the errors but will not ingest the report"
+ end
- context 'when enforce_security_report_validation is enabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: true)
+ context 'and the report is valid' do
+ let(:report_data) do
+ {
+ 'version' => report_version,
+ 'vulnerabilities' => []
+ }
end
- context 'when the report is valid' do
- let(:report_data) do
- {
- 'version' => report_version,
- 'vulnerabilities' => []
- }
- end
-
- let(:expected_errors) do
- [
- "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}"
- ]
- end
-
- it { is_expected.to match_array(expected_errors) }
+ let(:expected_errors) do
+ [
+ expected_unsupported_message
+ ]
end
- context 'when the report is invalid' do
- let(:report_data) do
- {
- 'version' => report_version
- }
- end
-
- let(:expected_errors) do
- [
- "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}",
- "root is missing required keys: vulnerabilities"
- ]
- end
-
- it { is_expected.to match_array(expected_errors) }
- end
+ it { is_expected.to match_array(expected_errors) }
end
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
+ context 'and the report is invalid' do
+ let(:report_data) do
+ {
+ 'version' => report_version
+ }
end
- context 'when the report is valid' do
- let(:report_data) do
- {
- 'version' => report_version,
- 'vulnerabilities' => []
- }
- end
-
- it { is_expected.to be_empty }
+ let(:expected_errors) do
+ [
+ expected_unsupported_message,
+ "root is missing required keys: vulnerabilities"
+ ]
end
- context 'when the report is invalid' do
- let(:report_data) do
- {
- 'version' => report_version
- }
- end
-
- it { is_expected.to be_empty }
- end
+ it { is_expected.to match_array(expected_errors) }
end
end
context 'when not given a schema version' do
let(:report_type) { :dast }
let(:report_version) { nil }
+ let(:expected_missing_version_message) do
+ "Report version not provided, #{report_type} report type supports versions: #{supported_dast_versions}. GitLab "\
+ "will attempt to validate this report against the earliest supported versions of this report type, to show all "\
+ "the errors but will not ingest the report"
+ end
+
let(:report_data) do
{
'vulnerabilities' => []
@@ -496,19 +422,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
let(:expected_errors) do
[
"root is missing required keys: version",
- "Report version not provided, dast report type supports versions: #{supported_dast_versions}"
+ expected_missing_version_message
]
end
it { is_expected.to match_array(expected_errors) }
-
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
-
- it { is_expected.to be_empty }
- end
end
end
@@ -519,7 +437,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
let(:report_type) { :dast }
let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last }
- context 'when the report is valid' do
+ context 'and the report is valid' do
let(:report_data) do
{
'version' => report_version,
@@ -530,7 +448,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
it { is_expected.to be_empty }
end
- context 'when the report is invalid' do
+ context 'and the report is invalid' do
let(:report_data) do
{
'version' => report_version
@@ -550,9 +468,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
end
let(:report_version) { described_class::DEPRECATED_VERSIONS[report_type].last }
+ let(:expected_deprecation_message) do
+ "Version #{report_version} for report type #{report_type} has been deprecated, supported versions for this "\
+ "report type are: #{supported_dast_versions}. GitLab will attempt to parse and ingest this report if valid."
+ end
+
let(:expected_deprecation_warnings) do
[
- "Version V2.7.0 for report type dast has been deprecated, supported versions for this report type are: #{supported_dast_versions}"
+ expected_deprecation_message
]
end
@@ -560,7 +483,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash)
end
- context 'when the report passes schema validation' do
+ context 'and the report passes schema validation' do
let(:report_data) do
{
'version' => report_version,
@@ -571,7 +494,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
it { is_expected.to match_array(expected_deprecation_warnings) }
end
- context 'when the report does not pass schema validation' do
+ context 'and the report does not pass schema validation' do
let(:report_data) do
{
'version' => 'V2.7.0'
@@ -600,11 +523,27 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
describe '#warnings' do
subject { validator.warnings }
- context 'when given a supported schema version' do
+ context 'when given a supported MAJOR.MINOR schema version' do
let(:report_type) { :dast }
- let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last }
+ let(:report_version) do
+ latest_vendored_version = described_class::SUPPORTED_VERSIONS[report_type].last.split(".")
+ (latest_vendored_version[0...2] << "34").join(".")
+ end
+
+ let(:latest_patch_version) do
+ ::Security::ReportSchemaVersionMatcher.new(
+ report_declared_version: report_version,
+ supported_versions: described_class::SUPPORTED_VERSIONS[report_type]
+ ).call
+ end
+
+ let(:message) do
+ "This report uses a supported MAJOR.MINOR schema version but the PATCH version doesn't match"\
+ " any vendored schema version. Validation will be attempted against version"\
+ " #{latest_patch_version}"
+ end
- context 'when the report is valid' do
+ context 'and the report is valid' do
let(:report_data) do
{
'version' => report_version,
@@ -612,37 +551,57 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
}
end
- it { is_expected.to be_empty }
+ it { is_expected.to match_array([message]) }
end
- context 'when the report is invalid' do
+ context 'and the report is invalid' do
let(:report_data) do
{
'version' => report_version
}
end
- context 'when enforce_security_report_validation is enabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: project)
- end
+ it { is_expected.to match_array([message]) }
+
+ it 'logs related information' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ message: "security report schema validation problem",
+ security_report_type: report_type,
+ security_report_version: report_version,
+ project_id: project.id,
+ security_report_failure: 'schema_validation_fails',
+ security_report_scanner_id: 'gemnasium',
+ security_report_scanner_version: '2.1.0'
+ )
- it { is_expected.to be_empty }
+ subject
end
+ end
+ end
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
+ context 'when given a supported schema version' do
+ let(:report_type) { :dast }
+ let(:report_version) { described_class::SUPPORTED_VERSIONS[report_type].last }
- let(:expected_warnings) do
- [
- 'root is missing required keys: vulnerabilities'
- ]
- end
+ context 'and the report is valid' do
+ let(:report_data) do
+ {
+ 'version' => report_version,
+ 'vulnerabilities' => []
+ }
+ end
+
+ it { is_expected.to be_empty }
+ end
- it { is_expected.to match_array(expected_warnings) }
+ context 'and the report is invalid' do
+ let(:report_data) do
+ {
+ 'version' => report_version
+ }
end
+
+ it { is_expected.to be_empty }
end
end
@@ -660,7 +619,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
stub_const("#{described_class}::DEPRECATED_VERSIONS", deprecations_hash)
end
- context 'when the report passes schema validation' do
+ context 'and the report passes schema validation' do
let(:report_data) do
{
'vulnerabilities' => []
@@ -670,35 +629,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
it { is_expected.to be_empty }
end
- context 'when the report does not pass schema validation' do
+ context 'and the report does not pass schema validation' do
let(:report_data) do
{
'version' => 'V2.7.0'
}
end
- context 'when enforce_security_report_validation is enabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: true)
- end
-
- it { is_expected.to be_empty }
- end
-
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
-
- let(:expected_warnings) do
- [
- "property '/version' does not match pattern: ^[0-9]+\\.[0-9]+\\.[0-9]+$",
- "root is missing required keys: vulnerabilities"
- ]
- end
-
- it { is_expected.to match_array(expected_warnings) }
- end
+ it { is_expected.to be_empty }
end
end
@@ -706,71 +644,25 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
let(:report_type) { :dast }
let(:report_version) { "12.37.0" }
- context 'when enforce_security_report_validation is enabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: true)
- end
-
- context 'when the report is valid' do
- let(:report_data) do
- {
- 'version' => report_version,
- 'vulnerabilities' => []
- }
- end
-
- it { is_expected.to be_empty }
+ context 'and the report is valid' do
+ let(:report_data) do
+ {
+ 'version' => report_version,
+ 'vulnerabilities' => []
+ }
end
- context 'when the report is invalid' do
- let(:report_data) do
- {
- 'version' => report_version
- }
- end
-
- it { is_expected.to be_empty }
- end
+ it { is_expected.to be_empty }
end
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
-
- context 'when the report is valid' do
- let(:report_data) do
- {
- 'version' => report_version,
- 'vulnerabilities' => []
- }
- end
-
- let(:expected_warnings) do
- [
- "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}"
- ]
- end
-
- it { is_expected.to match_array(expected_warnings) }
+ context 'and the report is invalid' do
+ let(:report_data) do
+ {
+ 'version' => report_version
+ }
end
- context 'when the report is invalid' do
- let(:report_data) do
- {
- 'version' => report_version
- }
- end
-
- let(:expected_warnings) do
- [
- "Version 12.37.0 for report type dast is unsupported, supported versions for this report type are: #{supported_dast_versions}",
- "root is missing required keys: vulnerabilities"
- ]
- end
-
- it { is_expected.to match_array(expected_warnings) }
- end
+ it { is_expected.to be_empty }
end
end
@@ -784,21 +676,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
end
it { is_expected.to be_empty }
-
- context 'when enforce_security_report_validation is disabled' do
- before do
- stub_feature_flags(enforce_security_report_validation: false)
- end
-
- let(:expected_warnings) do
- [
- "root is missing required keys: version",
- "Report version not provided, dast report type supports versions: #{supported_dast_versions}"
- ]
- end
-
- it { is_expected.to match_array(expected_warnings) }
- end
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 0d78ce3440a..de43e759193 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -282,7 +282,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
subject { command.ambiguous_ref? }
context 'when ref is not ambiguous' do
- it { is_expected. to eq(false) }
+ it { is_expected.to eq(false) }
end
context 'when ref is ambiguous' do
@@ -291,7 +291,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
project.repository.add_branch(project.creator, 'ref', 'master')
end
- it { is_expected. to eq(true) }
+ it { is_expected.to eq(true) }
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
index cbf92f8fa83..be5d3a96126 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments do
end
context 'when the corresponding environment does not exist' do
- let!(:environment) { }
+ let!(:environment) {}
it 'does not create a deployment record' do
expect { subject }.not_to change { Deployment.count }
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 e30a78546af..eb5a37f19f4 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
@@ -45,7 +45,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
before do
allow(step).to receive(:workflow_rules_result)
.and_return(
- double(pass?: true, variables: { 'VAR1' => 'val2' })
+ double(pass?: true, variables: { 'VAR1' => 'val2', 'VAR2' => 3 })
)
step.perform!
@@ -65,7 +65,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules do
end
it 'saves workflow_rules_result' do
- expect(command.workflow_rules_result.variables).to eq({ 'VAR1' => 'val2' })
+ expect(command.workflow_rules_result.variables).to eq({ 'VAR1' => 'val2', 'VAR2' => 3 })
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb
index fabfbd779f3..5ee96b0baa8 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::SeedBlock do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, developer_projects: [project]) }
- let(:seeds_block) { }
+ let(:seeds_block) {}
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
index 687bb82a8ef..f7774e199fb 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
- let(:seeds_block) { }
+ let(:seeds_block) {}
let(:command) { initialize_command }
let(:pipeline) { build(:ci_pipeline, project: project) }
@@ -205,6 +205,30 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
end
+ describe '#rule_variables' do
+ let(:config) do
+ {
+ variables: { VAR1: 11 },
+ workflow: {
+ rules: [{ if: '$CI_PIPELINE_SOURCE',
+ variables: { SYMBOL: :symbol, STRING: "string", INTEGER: 1 } },
+ { when: 'always' }]
+ },
+ rspec: { script: 'rake' }
+ }
+ end
+
+ let(:rspec_variables) { command.pipeline_seed.stages[0].statuses[0].variables.to_hash }
+
+ it 'correctly parses rule variables' do
+ run_chain
+
+ expect(rspec_variables['SYMBOL']).to eq("symbol")
+ expect(rspec_variables['STRING']).to eq("string")
+ expect(rspec_variables['INTEGER']).to eq("1")
+ end
+ end
+
context 'N+1 queries' do
it 'avoids N+1 queries when calculating variables of jobs', :use_sql_query_cache do
warm_up_pipeline, warm_up_command = prepare_pipeline1
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 eeac0c85a77..fb1a360a4b7 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
@@ -148,6 +148,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
payload = Gitlab::Json.parse(params[:body])
+ expect(payload['total_builds_count']).to eq(0)
+
builds = payload['builds']
expect(builds.count).to eq(2)
expect(builds[0]['services']).to be_nil
@@ -160,6 +162,23 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
perform!
end
+
+ context "with existing jobs from other project's alive pipelines" do
+ before do
+ create(:ci_pipeline, :with_job, user: user)
+ create(:ci_pipeline, :with_job)
+ end
+
+ it 'returns the expected total_builds_count' do
+ expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
+ payload = Gitlab::Json.parse(params[:body])
+
+ expect(payload['total_builds_count']).to eq(1)
+ end
+
+ perform!
+ end
+ end
end
context 'when EXTERNAL_VALIDATION_SERVICE_TOKEN is set' do
@@ -243,7 +262,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
context 'when save_incompleted is false' do
- let(:save_incompleted) { false}
+ let(:save_incompleted) { false }
it 'adds errors to the pipeline without dropping it' do
perform!
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
index 83742699d3d..47f172922a5 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -160,14 +160,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
let(:left_value) { 'abcde' }
it { is_expected.to eq(true) }
-
- context 'when the FF ci_fix_rules_if_comparison_with_regexp_variable is disabled' do
- before do
- stub_feature_flags(ci_fix_rules_if_comparison_with_regexp_variable: false)
- end
-
- it { is_expected.to eq(false) }
- end
end
context 'when not matching' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
index aad33106647..9e7ea3e4ea4 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -160,14 +160,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
let(:left_value) { 'abcde' }
it { is_expected.to eq(false) }
-
- context 'when the FF ci_fix_rules_if_comparison_with_regexp_variable is disabled' do
- before do
- stub_feature_flags(ci_fix_rules_if_comparison_with_regexp_variable: false)
- end
-
- it { is_expected.to eq(true) }
- end
end
context 'when not matching' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index bbd11a00149..acaec07f95b 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -179,24 +179,16 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do
.to_hash
end
- where(:expression, :ff, :result) do
- '$teststring =~ "abcde"' | true | true
- '$teststring =~ "abcde"' | false | true
- '$teststring =~ $teststring' | true | true
- '$teststring =~ $teststring' | false | true
- '$teststring =~ $pattern1' | true | true
- '$teststring =~ $pattern1' | false | false
- '$teststring =~ $pattern2' | true | false
- '$teststring =~ $pattern2' | false | false
+ where(:expression, :result) do
+ '$teststring =~ "abcde"' | true
+ '$teststring =~ $teststring' | true
+ '$teststring =~ $pattern1' | true
+ '$teststring =~ $pattern2' | false
end
with_them do
let(:text) { expression }
- before do
- stub_feature_flags(ci_fix_rules_if_comparison_with_regexp_variable: ff)
- end
-
it { is_expected.to eq(result) }
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
index 8f727749ee2..a742c619584 100644
--- a/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Quota::Deployments do
let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
- let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2)}
+ let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2) }
let(:command) do
double(:command,
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 040f3ab5830..75f6a773c2d 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -97,15 +97,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let(:attributes) do
{ name: 'rspec',
ref: 'master',
- job_variables: [{ key: 'VAR1', value: 'var 1', public: true },
- { key: 'VAR2', value: 'var 2', public: true }],
+ job_variables: [{ key: 'VAR1', value: 'var 1' },
+ { key: 'VAR2', value: 'var 2' }],
rules: [{ if: '$VAR == null', variables: { VAR1: 'new var 1', VAR3: 'var 3' } }] }
end
it do
- is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1', public: true },
- { key: 'VAR2', value: 'var 2', public: true },
- { key: 'VAR3', value: 'var 3', public: true }])
+ is_expected.to include(yaml_variables: [{ key: 'VAR1', value: 'new var 1' },
+ { key: 'VAR3', value: 'var 3' },
+ { key: 'VAR2', value: 'var 2' }])
end
end
@@ -114,13 +114,13 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
{
name: 'rspec',
ref: 'master',
- job_variables: [{ key: 'VARIABLE', value: 'value', public: true }],
+ job_variables: [{ key: 'VARIABLE', value: 'value' }],
tag_list: ['static-tag', '$VARIABLE', '$NO_VARIABLE']
}
end
it { is_expected.to include(tag_list: ['static-tag', 'value', '$NO_VARIABLE']) }
- it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value', public: true }]) }
+ it { is_expected.to include(yaml_variables: [{ key: 'VARIABLE', value: 'value' }]) }
end
context 'with cache:key' do
@@ -257,19 +257,19 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build 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 }],
+ yaml_variables: [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }],
+ job_variables: [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }],
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 }]
+ [{ key: 'VAR1', value: 'var overridden pipeline 1' },
+ { key: 'VAR2', value: 'var pipeline 2' },
+ { key: 'VAR3', value: 'var pipeline 3' },
+ { key: 'VAR4', value: 'new var pipeline 4' }]
end
context 'when root_variables_inheritance is true' do
@@ -277,10 +277,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
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 }]
+ [{ key: 'VAR1', value: 'var overridden pipeline 1' },
+ { key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' },
+ { key: 'VAR4', value: 'new var pipeline 4' }]
)
end
end
@@ -290,8 +290,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
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 }]
+ [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }]
)
end
end
@@ -301,9 +301,9 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
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: 'VAR1', value: 'var overridden pipeline 1' },
+ { key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }]
)
end
end
@@ -314,8 +314,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
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 }])
+ [{ key: 'VAR2', value: 'var 2' },
+ { key: 'VAR3', value: 'var 3' }])
end
end
end
@@ -324,8 +324,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 }],
- job_variables: [{ key: 'VAR1', value: 'var 1', public: true }],
+ yaml_variables: [{ key: 'VAR1', value: 'var 1' }],
+ job_variables: [{ key: 'VAR1', value: 'var 1' }],
root_variables_inheritance: root_variables_inheritance,
rules: rules }
end
@@ -338,14 +338,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
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 })
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
+ { key: 'VAR2', value: 'new var 2' })
end
end
context 'when the rules use root variables' do
let(:root_variables) do
- [{ key: 'VAR2', value: 'var pipeline 2', public: true }]
+ [{ key: 'VAR2', value: 'var pipeline 2' }]
end
let(:rules) do
@@ -353,15 +353,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
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 })
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'overridden var 1' },
+ { key: 'VAR2', value: 'overridden var 2' })
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 })
+ expect(subject[:yaml_variables]).to contain_exactly({ key: 'VAR1', value: 'var 1' })
end
end
end
@@ -769,7 +769,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
with_them do
it { is_expected.not_to be_included }
- it 'correctly populates when:' do
+ it 'still correctly populates when:' do
expect(seed_build.attributes).to include(when: 'never')
end
end
@@ -958,6 +958,26 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
expect(seed_build.attributes).to include(when: 'never')
end
end
+
+ context 'with invalid rules raising error' do
+ let(:rule_set) do
+ [
+ { changes: { paths: ['README.md'], compare_to: 'invalid-ref' }, when: 'never' }
+ ]
+ end
+
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
+
+ it 'returns an error' do
+ expect(seed_build.errors).to contain_exactly(
+ 'Failed to parse rule for rspec: rules:changes:compare_to is not a valid ref'
+ )
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/reports/sbom/component_spec.rb b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
new file mode 100644
index 00000000000..672117c311f
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/sbom/component_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Sbom::Component do
+ let(:attributes) do
+ {
+ 'type' => 'library',
+ 'name' => 'component-name',
+ 'version' => 'v0.0.1'
+ }
+ end
+
+ subject { described_class.new(attributes) }
+
+ it 'has correct attributes' do
+ expect(subject).to have_attributes(
+ component_type: 'library',
+ name: 'component-name',
+ version: 'v0.0.1'
+ )
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/sbom/report_spec.rb b/spec/lib/gitlab/ci/reports/sbom/report_spec.rb
new file mode 100644
index 00000000000..d7a285ab13c
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/sbom/report_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Sbom::Report do
+ subject(:report) { described_class.new }
+
+ describe '#add_error' do
+ it 'appends errors to a list' do
+ report.add_error('error1')
+ report.add_error('error2')
+
+ expect(report.errors).to match_array(%w[error1 error2])
+ end
+ end
+
+ describe '#set_source' do
+ let_it_be(:source) do
+ {
+ 'type' => :dependency_scanning,
+ 'data' => {
+ 'input_file' => { 'path' => 'package-lock.json' },
+ 'source_file' => { 'path' => 'package.json' },
+ 'package_manager' => { 'name' => 'npm' },
+ 'language' => { 'name' => 'JavaScript' }
+ },
+ 'fingerprint' => 'c01df1dc736c1148717e053edbde56cb3a55d3e31f87cea955945b6f67c17d42'
+ }
+ end
+
+ it 'stores the source' do
+ report.set_source(source)
+
+ expect(report.source).to be_a(Gitlab::Ci::Reports::Sbom::Source)
+ end
+ end
+
+ describe '#add_component' do
+ let_it_be(:components) do
+ [
+ { 'type' => 'library', 'name' => 'component1', 'version' => 'v0.0.1' },
+ { 'type' => 'library', 'name' => 'component2', 'version' => 'v0.0.2' },
+ { 'type' => 'library', 'name' => 'component2' }
+ ]
+ end
+
+ it 'appends components to a list' do
+ components.each { |component| report.add_component(component) }
+
+ expect(report.components.size).to eq(3)
+ expect(report.components).to all(be_a(Gitlab::Ci::Reports::Sbom::Component))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb b/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb
new file mode 100644
index 00000000000..97d8d7abb33
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/sbom/reports_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Sbom::Reports do
+ subject(:reports_list) { described_class.new }
+
+ describe '#add_report' do
+ let(:rep1) { Gitlab::Ci::Reports::Sbom::Report.new }
+ let(:rep2) { Gitlab::Ci::Reports::Sbom::Report.new }
+
+ it 'appends the report to the report list' do
+ reports_list.add_report(rep1)
+ reports_list.add_report(rep2)
+
+ expect(reports_list.reports.length).to eq(2)
+ expect(reports_list.reports.first).to eq(rep1)
+ expect(reports_list.reports.last).to eq(rep2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/sbom/source_spec.rb b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
new file mode 100644
index 00000000000..2d6434534a0
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/sbom/source_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Reports::Sbom::Source do
+ let(:attributes) do
+ {
+ 'type' => :dependency_scanning,
+ 'data' => {
+ 'category' => 'development',
+ 'input_file' => { 'path' => 'package-lock.json' },
+ 'source_file' => { 'path' => 'package.json' },
+ 'package_manager' => { 'name' => 'npm' },
+ 'language' => { 'name' => 'JavaScript' }
+ },
+ 'fingerprint' => '4dbcb747e6f0fb3ed4f48d96b777f1d64acdf43e459fdfefad404e55c004a188'
+ }
+ end
+
+ subject { described_class.new(attributes) }
+
+ it 'has correct attributes' do
+ expect(subject).to have_attributes(
+ source_type: attributes['type'],
+ data: attributes['data'],
+ fingerprint: attributes['fingerprint']
+ )
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/security/reports_spec.rb b/spec/lib/gitlab/ci/reports/security/reports_spec.rb
index 79eee642552..e240edc4a12 100644
--- a/spec/lib/gitlab/ci/reports/security/reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/security/reports_spec.rb
@@ -57,7 +57,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do
let(:high_severity_dast) { build(:ci_reports_security_finding, severity: 'high', report_type: 'dast') }
let(:vulnerabilities_allowed) { 0 }
let(:severity_levels) { %w(critical high) }
- let(:vulnerability_states) { %w(newly_detected)}
+ let(:vulnerability_states) { %w(newly_detected) }
subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states) }
diff --git a/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb
index 44e66fd9028..6f75e2c55e8 100644
--- a/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/security/vulnerability_reports_comparer_spec.rb
@@ -60,7 +60,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer do
end
describe '#added' do
- let(:new_location) {build(:ci_reports_security_locations_sast, :dynamic) }
+ let(:new_location) { build(:ci_reports_security_locations_sast, :dynamic) }
let(:vul_params) { vuln_params(project.id, [identifier], confidence: :high) }
let(:vuln) { build(:ci_reports_security_finding, severity: Enums::Vulnerability.severity_levels[:critical], location: new_location, **vul_params) }
let(:low_vuln) { build(:ci_reports_security_finding, severity: Enums::Vulnerability.severity_levels[:low], location: new_location, **vul_params) }
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index 1d6b39a7831..4a1f77bed65 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -91,7 +91,7 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do
subject { test_suite.with_attachment! }
context 'when test cases do not contain an attachment' do
- let(:test_case) { build(:report_test_case, :failed)}
+ let(:test_case) { build(:report_test_case, :failed) }
before do
test_suite.add_test_case(test_case)
@@ -103,7 +103,7 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do
end
context 'when test cases contain an attachment' do
- let(:test_case_with_attachment) { build(:report_test_case, :failed_with_attachment)}
+ let(:test_case_with_attachment) { build(:report_test_case, :failed_with_attachment) }
before do
test_suite.add_test_case(test_case_with_attachment)
diff --git a/spec/lib/gitlab/ci/runner_releases_spec.rb b/spec/lib/gitlab/ci/runner_releases_spec.rb
index 576eb02ad83..ad1e9b12b8a 100644
--- a/spec/lib/gitlab/ci/runner_releases_spec.rb
+++ b/spec/lib/gitlab/ci/runner_releases_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::RunnerReleases do
subject { described_class.instance }
- let(:runner_releases_url) { 'the release API URL' }
+ let(:runner_releases_url) { 'http://testurl.com/runner_public_releases' }
def releases
subject.releases
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
before do
subject.reset_backoff!
- stub_application_setting(public_runner_releases_url: runner_releases_url)
+ allow(subject).to receive(:runner_releases_url).and_return(runner_releases_url)
end
describe 'caching behavior', :use_clean_rails_memory_store_caching do
@@ -77,7 +77,8 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
allow(Gitlab::HTTP).to receive(:get).with(runner_releases_url, anything) do
http_call_timestamp_offsets << Time.now.utc - start_time
- raise Net::OpenTimeout if opts&.dig(:raise_timeout)
+ err_class = opts&.dig(:raise_error)
+ raise err_class if err_class
mock_http_response(response)
end
@@ -113,12 +114,13 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
end
context 'when request results in timeout' do
- let(:response) { }
+ let(:response) {}
let(:expected_releases) { nil }
let(:expected_releases_by_minor) { nil }
it_behaves_like 'requests that follow cache status', 5.seconds
- it_behaves_like 'a service implementing exponential backoff', raise_timeout: true
+ it_behaves_like 'a service implementing exponential backoff', raise_error: Net::OpenTimeout
+ it_behaves_like 'a service implementing exponential backoff', raise_error: Errno::ETIMEDOUT
end
context 'when response is nil' do
diff --git a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
index f2507a24b10..55c3834bfa7 100644
--- a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
+++ b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
@@ -5,36 +5,35 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
using RSpec::Parameterized::TableSyntax
- describe '#check_runner_upgrade_status' do
- subject(:result) { described_class.instance.check_runner_upgrade_status(runner_version) }
+ subject(:instance) { described_class.new(gitlab_version, runner_releases) }
+
+ describe '#check_runner_upgrade_suggestion' do
+ subject(:result) { instance.check_runner_upgrade_suggestion(runner_version) }
let(:gitlab_version) { '14.1.1' }
let(:parsed_runner_version) { ::Gitlab::VersionInfo.parse(runner_version, parse_suffix: true) }
-
- before do
- allow(described_class.instance).to receive(:gitlab_version)
- .and_return(::Gitlab::VersionInfo.parse(gitlab_version))
- end
+ let(:runner_releases) { instance_double(Gitlab::Ci::RunnerReleases) }
context 'with failing Gitlab::Ci::RunnerReleases request' do
let(:runner_version) { '14.1.123' }
- let(:runner_releases_double) { instance_double(Gitlab::Ci::RunnerReleases) }
before do
- allow(Gitlab::Ci::RunnerReleases).to receive(:instance).and_return(runner_releases_double)
- allow(runner_releases_double).to receive(:releases).and_return(nil)
+ allow(runner_releases).to receive(:releases).and_return(nil)
end
it 'returns :error' do
- is_expected.to eq({ error: parsed_runner_version })
+ is_expected.to eq([parsed_runner_version, :error])
end
end
context 'with available_runner_releases configured' do
- before do
- url = ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url
+ let(:runner_releases) { Gitlab::Ci::RunnerReleases.instance }
+ let(:runner_releases_url) do
+ ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url
+ end
- WebMock.stub_request(:get, url).to_return(
+ before do
+ WebMock.stub_request(:get, runner_releases_url).to_return(
body: available_runner_releases.map { |v| { name: v } }.to_json,
status: 200,
headers: { 'Content-Type' => 'application/json' }
@@ -53,7 +52,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
let(:runner_version) { 'v14.0.1' }
it 'returns :not_available' do
- is_expected.to eq({ not_available: parsed_runner_version })
+ is_expected.to eq([parsed_runner_version, :not_available])
end
end
end
@@ -68,7 +67,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
let(:runner_version) { nil }
it 'returns :invalid_version' do
- is_expected.to match({ invalid_version: anything })
+ is_expected.to match([anything, :invalid_version])
end
end
@@ -76,7 +75,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
let(:runner_version) { 'junk' }
it 'returns :invalid_version' do
- is_expected.to match({ invalid_version: anything })
+ is_expected.to match([anything, :invalid_version])
end
end
@@ -87,7 +86,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
let(:runner_version) { 'v14.2.0' }
it 'returns :not_available' do
- is_expected.to eq({ not_available: parsed_runner_version })
+ is_expected.to eq([parsed_runner_version, :not_available])
end
end
end
@@ -96,7 +95,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
let(:gitlab_version) { '14.0.1' }
context 'with valid params' do
- where(:runner_version, :expected_result, :expected_suggested_version) do
+ where(:runner_version, :expected_status, :expected_suggested_version) do
'v15.0.0' | :not_available | '15.0.0' # not available since the GitLab instance is still on 14.x, a major version might be incompatible, and a patch upgrade is not available
'v14.1.0-rc3' | :recommended | '14.1.1' # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
'v14.1.0~beta.1574.gf6ea9389' | :recommended | '14.1.1' # suffixes are correctly handled
@@ -116,7 +115,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
end
with_them do
- it { is_expected.to eq({ expected_result => Gitlab::VersionInfo.parse(expected_suggested_version) }) }
+ it { is_expected.to eq([Gitlab::VersionInfo.parse(expected_suggested_version), expected_status]) }
end
end
end
@@ -125,7 +124,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
let(:gitlab_version) { '13.9.0' }
context 'with valid params' do
- where(:runner_version, :expected_result, :expected_suggested_version) do
+ where(:runner_version, :expected_status, :expected_suggested_version) do
'v14.0.0' | :recommended | '14.0.2' # recommended upgrade since 14.0.2 is available, even though the GitLab instance is still on 13.x and a major version might be incompatible
'v13.10.1' | :not_available | '13.10.1' # not available since 13.10.1 is already ahead of GitLab instance version and is the latest patch update for 13.10.x
'v13.10.0' | :recommended | '13.10.1' # recommended upgrade since 13.10.1 is available
@@ -136,7 +135,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
end
with_them do
- it { is_expected.to eq({ expected_result => Gitlab::VersionInfo.parse(expected_suggested_version) }) }
+ it { is_expected.to eq([Gitlab::VersionInfo.parse(expected_suggested_version), expected_status]) }
end
end
end
@@ -152,7 +151,7 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
let(:runner_version) { '14.11.0~beta.29.gd0c550e3' }
it 'recommends 15.1.0 since 14.11 is an unknown release and 15.1.0 is available' do
- is_expected.to eq({ recommended: Gitlab::VersionInfo.new(15, 1, 0) })
+ is_expected.to eq([Gitlab::VersionInfo.new(15, 1, 0), :recommended])
end
end
end
diff --git a/spec/lib/gitlab/ci/status/bridge/common_spec.rb b/spec/lib/gitlab/ci/status/bridge/common_spec.rb
index 30e6ad234a0..37524afc83d 100644
--- a/spec/lib/gitlab/ci/status/bridge/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/bridge/common_spec.rb
@@ -29,15 +29,7 @@ RSpec.describe Gitlab::Ci::Status::Bridge::Common do
end
it { expect(subject).to have_details }
- it { expect(subject.details_path).to include "jobs/#{bridge.id}" }
-
- context 'with ci_retry_downstream_pipeline ff disabled' do
- before do
- stub_feature_flags(ci_retry_downstream_pipeline: false)
- end
-
- it { expect(subject.details_path).to include "pipelines/#{downstream_pipeline.id}" }
- end
+ it { expect(subject.details_path).to include "pipelines/#{downstream_pipeline.id}" }
end
context 'when user does not have access to read downstream pipeline' do
diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
index e30a2211c8f..519b970ca5e 100644
--- a/spec/lib/gitlab/ci/status/build/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Canceled do
end
describe '.matches?' do
- subject {described_class.matches?(build, user) }
+ subject { described_class.matches?(build, user) }
context 'when build is canceled' do
let(:build) { create(:ci_build, :canceled) }
diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb
index 49468674140..9738b3c1f36 100644
--- a/spec/lib/gitlab/ci/status/build/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/created_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Created do
end
describe '.matches?' do
- subject {described_class.matches?(build, user) }
+ subject { described_class.matches?(build, user) }
context 'when build is created' do
let(:build) { create(:ci_build, :created) }
diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb
index 150705c1e36..a1152cb77e3 100644
--- a/spec/lib/gitlab/ci/status/build/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Manual do
end
describe '.matches?' do
- subject {described_class.matches?(build, user) }
+ subject { described_class.matches?(build, user) }
context 'when build is manual' do
let(:build) { create(:ci_build, :manual) }
diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb
index 7b695d33877..b7dda9ce9c9 100644
--- a/spec/lib/gitlab/ci/status/build/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Pending do
end
describe '.matches?' do
- subject {described_class.matches?(build, user) }
+ subject { described_class.matches?(build, user) }
context 'when build is pending' do
let(:build) { create(:ci_build, :pending) }
diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
index 0b998a52a57..4437ac0089f 100644
--- a/spec/lib/gitlab/ci/status/build/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Status::Build::Skipped do
end
describe '.matches?' do
- subject {described_class.matches?(build, user) }
+ subject { described_class.matches?(build, user) }
context 'when build is skipped' do
let(:build) { create(:ci_build, :skipped) }
diff --git a/spec/lib/gitlab/ci/status/processable/waiting_for_resource_spec.rb b/spec/lib/gitlab/ci/status/processable/waiting_for_resource_spec.rb
index 91a9724d043..26087fd771c 100644
--- a/spec/lib/gitlab/ci/status/processable/waiting_for_resource_spec.rb
+++ b/spec/lib/gitlab/ci/status/processable/waiting_for_resource_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::Ci::Status::Processable::WaitingForResource do
end
describe '.matches?' do
- subject {described_class.matches?(processable, user) }
+ subject { described_class.matches?(processable, user) }
context 'when processable is waiting for resource' do
let(:processable) { create(:ci_build, :waiting_for_resource) }
diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb
index 0f97bc06a4e..5ff179b6fee 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb
@@ -36,9 +36,10 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
let(:pipeline) { service.execute(merge_request).payload }
- it 'has no jobs' do
+ it 'creates a pipeline with the expected jobs' do
expect(pipeline).to be_merge_request_event
- expect(build_names).to be_empty
+ expect(pipeline.errors.full_messages).to be_empty
+ expect(build_names).to match_array(%w(kics-iac-sast))
end
end
diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
index 78d3982a79f..1a909f52ec3 100644
--- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
context 'when the project is set for deployment to AWS' do
let(:platform_value) { 'ECS' }
- let(:review_prod_build_names) { build_names.select {|n| n.include?('review') || n.include?('production')} }
+ let(:review_prod_build_names) { build_names.select { |n| n.include?('review') || n.include?('production') } }
before do
create(:ci_variable, project: project, key: 'AUTO_DEVOPS_PLATFORM_TARGET', value: platform_value)
diff --git a/spec/lib/gitlab/ci/trace/remote_checksum_spec.rb b/spec/lib/gitlab/ci/trace/remote_checksum_spec.rb
index 1cd88034166..be29543676f 100644
--- a/spec/lib/gitlab/ci/trace/remote_checksum_spec.rb
+++ b/spec/lib/gitlab/ci/trace/remote_checksum_spec.rb
@@ -47,7 +47,7 @@ RSpec.describe Gitlab::Ci::Trace::RemoteChecksum do
end
context 'when the response does not include :content_md5' do
- let(:metadata) {{}}
+ let(:metadata) { {} }
it 'raises an exception' do
expect { subject }.to raise_error KeyError, /content_md5/
@@ -55,7 +55,7 @@ RSpec.describe Gitlab::Ci::Trace::RemoteChecksum do
end
context 'when the response include :content_md5' do
- let(:metadata) {{ content_md5: base64checksum }}
+ let(:metadata) { { content_md5: base64checksum } }
it { is_expected.to eq(checksum) }
end
diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb
index 8ec0846bdca..6ab2089cce8 100644
--- a/spec/lib/gitlab/ci/variables/builder_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Variables::Builder do
+ include Ci::TemplateHelpers
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, namespace: group) }
let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project) }
@@ -92,6 +93,8 @@ RSpec.describe Gitlab::Ci::Variables::Builder do
value: project.pages_url },
{ key: 'CI_API_V4_URL',
value: API::Helpers::Version.new('v4').root_url },
+ { key: 'CI_TEMPLATE_REGISTRY_HOST',
+ value: template_registry_host },
{ key: 'CI_PIPELINE_IID',
value: pipeline.iid.to_s },
{ key: 'CI_PIPELINE_SOURCE',
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index 26c560565e0..8ac03301322 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -302,6 +302,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
.append(key: 'CI_BUILD_ID', value: '1')
.append(key: 'RAW_VAR', value: '$TEST1', raw: true)
.append(key: 'TEST1', value: 'test-3')
+ .append(key: 'FILEVAR1', value: 'file value 1', file: true)
end
context 'table tests' do
@@ -311,28 +312,23 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
{
"empty value": {
value: '',
- result: '',
- keep_undefined: false
+ result: ''
},
"simple expansions": {
value: 'key$TEST1-$CI_BUILD_ID',
- result: 'keytest-3-1',
- keep_undefined: false
+ result: 'keytest-3-1'
},
"complex expansion": {
value: 'key${TEST1}-${CI_JOB_NAME}',
- result: 'keytest-3-test-1',
- keep_undefined: false
+ result: 'keytest-3-test-1'
},
"complex expansions with raw variable": {
value: 'key${RAW_VAR}-${CI_JOB_NAME}',
- result: 'key$TEST1-test-1',
- keep_undefined: false
+ result: 'key$TEST1-test-1'
},
"missing variable not keeping original": {
value: 'key${MISSING_VAR}-${CI_JOB_NAME}',
- result: 'key-test-1',
- keep_undefined: false
+ result: 'key-test-1'
},
"missing variable keeping original": {
value: 'key${MISSING_VAR}-${CI_JOB_NAME}',
@@ -341,14 +337,24 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
},
"escaped characters are kept intact": {
value: 'key-$TEST1-%%HOME%%-$${HOME}',
- result: 'key-test-3-%%HOME%%-$${HOME}',
- keep_undefined: false
+ result: 'key-test-3-%%HOME%%-$${HOME}'
+ },
+ "file variable with expand_file_vars: true": {
+ value: 'key-$FILEVAR1-$TEST1',
+ result: 'key-file value 1-test-3'
+ },
+ "file variable with expand_file_vars: false": {
+ value: 'key-$FILEVAR1-$TEST1',
+ result: 'key-$FILEVAR1-test-3',
+ expand_file_vars: false
}
}
end
with_them do
- subject { collection.expand_value(value, keep_undefined: keep_undefined) }
+ let(:options) { { keep_undefined: keep_undefined, expand_file_vars: expand_file_vars }.compact }
+
+ subject(:result) { collection.expand_value(value, **options) }
it 'matches expected expansion' do
is_expected.to eq(result)
diff --git a/spec/lib/gitlab/ci/variables/helpers_spec.rb b/spec/lib/gitlab/ci/variables/helpers_spec.rb
index f13b334c10e..2a1cdaeb3a7 100644
--- a/spec/lib/gitlab/ci/variables/helpers_spec.rb
+++ b/spec/lib/gitlab/ci/variables/helpers_spec.rb
@@ -15,21 +15,27 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do
end
let(:result) do
- [{ key: 'key1', value: 'value1', public: true },
- { key: 'key2', value: 'value22', public: true },
- { key: 'key3', value: 'value3', public: true }]
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value22' },
+ { key: 'key3', value: 'value3' }]
end
subject { described_class.merge_variables(current_variables, new_variables) }
- it { is_expected.to eq(result) }
+ it { is_expected.to match_array(result) }
context 'when new variables is a hash' do
let(:new_variables) do
{ 'key2' => 'value22', 'key3' => 'value3' }
end
- it { is_expected.to eq(result) }
+ let(:result) do
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value22' },
+ { key: 'key3', value: 'value3' }]
+ end
+
+ it { is_expected.to match_array(result) }
end
context 'when new variables is a hash with symbol keys' do
@@ -37,67 +43,72 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do
{ key2: 'value22', key3: 'value3' }
end
- it { is_expected.to eq(result) }
+ let(:result) do
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value22' },
+ { key: 'key3', value: 'value3' }]
+ end
+
+ it { is_expected.to match_array(result) }
end
context 'when new variables is nil' do
let(:new_variables) {}
let(:result) do
- [{ key: 'key1', value: 'value1', public: true },
- { key: 'key2', value: 'value2', public: true }]
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value2' }]
end
- it { is_expected.to eq(result) }
+ it { is_expected.to match_array(result) }
end
end
- describe '.transform_to_yaml_variables' do
- let(:variables) do
- { 'key1' => 'value1', 'key2' => 'value2' }
- end
+ describe '.transform_to_array' do
+ subject { described_class.transform_to_array(variables) }
- let(:result) do
- [{ key: 'key1', value: 'value1', public: true },
- { key: 'key2', value: 'value2', public: true }]
- end
+ context 'when values are strings' do
+ let(:variables) do
+ { 'key1' => 'value1', 'key2' => 'value2' }
+ end
- subject { described_class.transform_to_yaml_variables(variables) }
+ let(:result) do
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value2' }]
+ end
- it { is_expected.to eq(result) }
+ it { is_expected.to match_array(result) }
+ end
context 'when variables is nil' do
let(:variables) {}
- it { is_expected.to eq([]) }
- end
- end
-
- describe '.transform_from_yaml_variables' do
- let(:variables) do
- [{ key: 'key1', value: 'value1', public: true },
- { key: 'key2', value: 'value2', public: true }]
+ it { is_expected.to match_array([]) }
end
- let(:result) do
- { 'key1' => 'value1', 'key2' => 'value2' }
- end
+ context 'when values are hashes' do
+ let(:variables) do
+ { 'key1' => { value: 'value1', description: 'var 1' }, 'key2' => { value: 'value2' } }
+ end
- subject { described_class.transform_from_yaml_variables(variables) }
+ let(:result) do
+ [{ key: 'key1', value: 'value1', description: 'var 1' },
+ { key: 'key2', value: 'value2' }]
+ end
- it { is_expected.to eq(result) }
+ it { is_expected.to match_array(result) }
- context 'when variables is nil' do
- let(:variables) {}
+ context 'when a value data has `key` as a key' do
+ let(:variables) do
+ { 'key1' => { value: 'value1', key: 'new_key1' }, 'key2' => { value: 'value2' } }
+ end
- it { is_expected.to eq({}) }
- end
+ let(:result) do
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value2' }]
+ end
- context 'when variables is a hash' do
- let(:variables) do
- { key1: 'value1', 'key2' => 'value2' }
+ it { is_expected.to match_array(result) }
end
-
- it { is_expected.to eq(result) }
end
end
@@ -115,35 +126,35 @@ RSpec.describe Gitlab::Ci::Variables::Helpers do
let(:inheritance) { true }
let(:result) do
- [{ key: 'key1', value: 'value1', public: true },
- { key: 'key2', value: 'value22', public: true },
- { key: 'key3', value: 'value3', public: true }]
+ [{ key: 'key1', value: 'value1' },
+ { key: 'key2', value: 'value22' },
+ { key: 'key3', value: 'value3' }]
end
subject { described_class.inherit_yaml_variables(from: from, to: to, inheritance: inheritance) }
- it { is_expected.to eq(result) }
+ it { is_expected.to match_array(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 }]
+ [{ key: 'key2', value: 'value22' },
+ { key: 'key3', value: 'value3' }]
end
- it { is_expected.to eq(result) }
+ it { is_expected.to match_array(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 }]
+ [{ key: 'key2', value: 'value22' },
+ { key: 'key3', value: 'value3' }]
end
- it { is_expected.to eq(result) }
+ it { is_expected.to match_array(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 8416501e949..f7a0905d9da 100644
--- a/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor/result_spec.rb
@@ -72,8 +72,8 @@ module Gitlab
it 'returns calculated variables with root and job variables' do
is_expected.to match_array([
- { key: 'VAR1', value: 'value 11', public: true },
- { key: 'VAR2', value: 'value 2', public: true }
+ { key: 'VAR1', value: 'value 11' },
+ { key: 'VAR2', value: 'value 2' }
])
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 22bc6b0db59..3477fe837b4 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -448,7 +448,7 @@ module Gitlab
it 'parses the root:variables as #root_variables' do
expect(subject.root_variables)
- .to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true })
+ .to contain_exactly({ key: 'SUPPORTED', value: 'parsed' })
end
end
@@ -490,7 +490,7 @@ module Gitlab
it 'parses the root:variables as #root_variables' do
expect(subject.root_variables)
- .to contain_exactly({ key: 'SUPPORTED', value: 'parsed', public: true })
+ .to contain_exactly({ key: 'SUPPORTED', value: 'parsed' })
end
end
@@ -1098,8 +1098,8 @@ module Gitlab
it 'returns job variables' do
expect(job_variables).to contain_exactly(
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
+ { key: 'VAR1', value: 'value1' },
+ { key: 'VAR2', value: 'value2' }
)
expect(root_variables_inheritance).to eq(true)
end
@@ -1203,21 +1203,21 @@ module Gitlab
expect(config_processor.builds[0]).to include(
name: 'test1',
options: { script: ['test'] },
- job_variables: [{ key: 'VAR1', value: 'test1 var 1', public: true },
- { key: 'VAR2', value: 'test2 var 2', public: true }]
+ job_variables: [{ key: 'VAR1', value: 'test1 var 1' },
+ { key: 'VAR2', value: 'test2 var 2' }]
)
expect(config_processor.builds[1]).to include(
name: 'test2',
options: { script: ['test'] },
- job_variables: [{ key: 'VAR1', value: 'base var 1', public: true },
- { key: 'VAR2', value: 'test2 var 2', public: true }]
+ job_variables: [{ key: 'VAR1', value: 'base var 1' },
+ { key: 'VAR2', value: 'test2 var 2' }]
)
expect(config_processor.builds[2]).to include(
name: 'test3',
options: { script: ['test'] },
- job_variables: [{ key: 'VAR1', value: 'base var 1', public: true }]
+ job_variables: [{ key: 'VAR1', value: 'base var 1' }]
)
expect(config_processor.builds[3]).to include(
@@ -1425,7 +1425,7 @@ module Gitlab
it 'returns the parallel config' do
build_options = builds.map { |build| build[:options] }
parallel_config = {
- matrix: parallel[:matrix].map { |var| var.transform_values { |v| Array(v).flatten }},
+ matrix: parallel[:matrix].map { |var| var.transform_values { |v| Array(v).flatten } },
total: build_options.size
}
@@ -1766,6 +1766,7 @@ module Gitlab
script: ["make changelog | tee release_changelog.txt"],
release: {
tag_name: "$CI_COMMIT_TAG",
+ tag_message: "Annotated tag message",
name: "Release $CI_TAG_NAME",
description: "./release_changelog.txt",
ref: 'b3235930aa443112e639f941c69c578912189bdd',
@@ -1956,7 +1957,7 @@ module Gitlab
subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)).execute }
context 'no dependencies' do
- let(:dependencies) { }
+ let(:dependencies) {}
it { is_expected.to be_valid }
end
@@ -2012,8 +2013,8 @@ module Gitlab
end
describe "Job Needs" do
- let(:needs) { }
- let(:dependencies) { }
+ let(:needs) {}
+ let(:dependencies) {}
let(:config) do
{
@@ -2893,7 +2894,7 @@ module Gitlab
end
end
- describe 'Rules' do
+ describe 'Job rules' do
context 'changes' do
let(:config) do
<<~YAML
@@ -2938,6 +2939,49 @@ module Gitlab
end
end
+ describe 'Workflow rules' do
+ context 'changes' do
+ let(:config) do
+ <<~YAML
+ workflow:
+ rules:
+ - changes: [README.md]
+
+ rspec:
+ script: exit 0
+ YAML
+ end
+
+ it 'returns pipeline with correct rules' do
+ expect(processor.builds.size).to eq(1)
+ expect(processor.workflow_rules).to eq(
+ [{ changes: { paths: ["README.md"] } }]
+ )
+ end
+
+ context 'with paths' do
+ let(:config) do
+ <<~YAML
+ workflow:
+ rules:
+ - changes:
+ paths: [README.md]
+
+ rspec:
+ script: exit 0
+ YAML
+ end
+
+ it 'returns pipeline with correct rules' do
+ expect(processor.builds.size).to eq(1)
+ expect(processor.workflow_rules).to eq(
+ [{ changes: { paths: ["README.md"] } }]
+ )
+ end
+ end
+ end
+ end
+
describe '#execute' do
subject { Gitlab::Ci::YamlProcessor.new(content).execute }
diff --git a/spec/lib/gitlab/composer/cache_spec.rb b/spec/lib/gitlab/composer/cache_spec.rb
index 071771960c6..a4d632da848 100644
--- a/spec/lib/gitlab/composer/cache_spec.rb
+++ b/spec/lib/gitlab/composer/cache_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Gitlab::Composer::Cache do
cache_file = Packages::Composer::CacheFile.last
freeze_time do
- expect { subject }.to change { cache_file.reload.delete_at}.from(nil).to(1.day.from_now)
+ expect { subject }.to change { cache_file.reload.delete_at }.from(nil).to(1.day.from_now)
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index 7173ea43450..0e7d7f1efda 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::CycleAnalytics::StageSummary do
+ include CycleAnalyticsHelpers
+
let_it_be(:project) { create(:project, :repository) }
let(:options) { { from: 1.day.ago } }
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 9cee0802e87..2c239d5868a 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::DataBuilder::Build do
let!(:tag_names) { %w(tag-1 tag-2) }
- let(:runner) { create(:ci_runner, :instance, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n)}) }
+ let(:runner) { create(:ci_runner, :instance, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n) }) }
let(:user) { create(:user, :public_email) }
let(:build) { create(:ci_build, :running, runner: runner, user: user) }
@@ -33,6 +33,7 @@ RSpec.describe Gitlab::DataBuilder::Build do
it { expect(data[:project_id]).to eq(build.project.id) }
it { expect(data[:project_name]).to eq(build.project.full_name) }
it { expect(data[:pipeline_id]).to eq(build.pipeline.id) }
+
it {
expect(data[:user]).to eq(
{
@@ -43,6 +44,7 @@ RSpec.describe Gitlab::DataBuilder::Build do
email: user.email
})
}
+
it { expect(data[:commit][:id]).to eq(build.pipeline.id) }
it { expect(data[:runner][:id]).to eq(build.runner.id) }
it { expect(data[:runner][:tags]).to match_array(tag_names) }
diff --git a/spec/lib/gitlab/data_builder/issuable_spec.rb b/spec/lib/gitlab/data_builder/issuable_spec.rb
index c1ae65c160f..f0802f335f4 100644
--- a/spec/lib/gitlab/data_builder/issuable_spec.rb
+++ b/spec/lib/gitlab/data_builder/issuable_spec.rb
@@ -113,6 +113,7 @@ RSpec.describe Gitlab::DataBuilder::Issuable do
expect(data[:object_attributes]['assignee_id']).to eq(user.id)
expect(data[:assignees].first).to eq(user.hook_attrs)
expect(data).not_to have_key(:assignee)
+ expect(data).not_to have_key(:reviewers)
end
end
@@ -126,5 +127,25 @@ RSpec.describe Gitlab::DataBuilder::Issuable do
expect(data).not_to have_key(:assignee)
end
end
+
+ context 'merge_request is assigned reviewers' do
+ let(:merge_request) { create(:merge_request, reviewers: [user]) }
+ let(:data) { described_class.new(merge_request).build(user: user) }
+
+ it 'returns correct hook data' do
+ expect(data[:object_attributes]['reviewer_ids']).to match_array([user.id])
+ expect(data[:reviewers].first).to eq(user.hook_attrs)
+ end
+ end
+
+ context 'when merge_request does not have reviewers and assignees' do
+ let(:merge_request) { create(:merge_request) }
+ let(:data) { described_class.new(merge_request).build(user: user) }
+
+ it 'returns correct hook data' do
+ expect(data).not_to have_key(:assignees)
+ expect(data).not_to have_key(:reviewers)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 469812c80fc..86a1539a836 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -54,7 +54,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
context 'build with runner' do
let_it_be(:tag_names) { %w(tag-1 tag-2) }
- let_it_be(:ci_runner) { create(:ci_runner, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n)}) }
+ let_it_be(:ci_runner) { create(:ci_runner, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n) }) }
let_it_be(:build) { create(:ci_build, pipeline: pipeline, runner: ci_runner) }
it 'has runner attributes', :aggregate_failures do
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index 7eb81a880bf..a3dd4e49e83 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -67,6 +67,7 @@ RSpec.describe Gitlab::DataBuilder::Push do
it { expect(data[:project_id]).to eq(15) }
it { expect(data[:commits].size).to eq(1) }
it { expect(data[:total_commits_count]).to eq(1) }
+
it 'contains project data' do
expect(data[:project]).to be_a(Hash)
expect(data[:project][:id]).to eq(15)
diff --git a/spec/lib/gitlab/database/async_indexes/index_destructor_spec.rb b/spec/lib/gitlab/database/async_indexes/index_destructor_spec.rb
new file mode 100644
index 00000000000..adb0f45706d
--- /dev/null
+++ b/spec/lib/gitlab/database/async_indexes/index_destructor_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::AsyncIndexes::IndexDestructor do
+ include ExclusiveLeaseHelpers
+
+ describe '#perform' do
+ subject { described_class.new(async_index) }
+
+ let(:async_index) { create(:postgres_async_index, :with_drop) }
+
+ let(:index_model) { Gitlab::Database::AsyncIndexes::PostgresAsyncIndex }
+
+ let(:model) { Gitlab::Database.database_base_models[Gitlab::Database::PRIMARY_DATABASE_NAME] }
+ let(:connection) { model.connection }
+
+ let!(:lease) { stub_exclusive_lease(lease_key, :uuid, timeout: lease_timeout) }
+ let(:lease_key) { "gitlab/database/async_indexes/index_destructor/#{Gitlab::Database::PRIMARY_DATABASE_NAME}" }
+ let(:lease_timeout) { described_class::TIMEOUT_PER_ACTION }
+
+ before do
+ connection.add_index(async_index.table_name, 'id', name: async_index.name)
+ end
+
+ around do |example|
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ example.run
+ end
+ end
+
+ context 'when the index does not exist' do
+ before do
+ connection.execute(async_index.definition)
+ end
+
+ it 'skips index destruction' do
+ expect(connection).not_to receive(:execute).with(/DROP INDEX/)
+
+ subject.perform
+ end
+ end
+
+ it 'creates the index while controlling lock timeout' do
+ allow(connection).to receive(:execute).and_call_original
+ expect(connection).to receive(:execute).with("SET lock_timeout TO '60000ms'").and_call_original
+ expect(connection).to receive(:execute).with(async_index.definition).and_call_original
+ expect(connection).to receive(:execute)
+ .with("RESET idle_in_transaction_session_timeout; RESET lock_timeout")
+ .and_call_original
+
+ subject.perform
+ end
+
+ it 'removes the index preparation record from postgres_async_indexes' do
+ expect(async_index).to receive(:destroy).and_call_original
+
+ expect { subject.perform }.to change { index_model.count }.by(-1)
+ end
+
+ it 'skips logic if not able to acquire exclusive lease' do
+ expect(lease).to receive(:try_obtain).ordered.and_return(false)
+ expect(connection).not_to receive(:execute).with(/DROP INDEX/)
+ expect(async_index).not_to receive(:destroy)
+
+ expect { subject.perform }.not_to change { index_model.count }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/async_indexes/migration_helpers_spec.rb b/spec/lib/gitlab/database/async_indexes/migration_helpers_spec.rb
index 9ba3dad72b3..52f5e37eff2 100644
--- a/spec/lib/gitlab/database/async_indexes/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/async_indexes/migration_helpers_spec.rb
@@ -142,4 +142,42 @@ RSpec.describe Gitlab::Database::AsyncIndexes::MigrationHelpers do
end
end
end
+
+ describe '#prepare_async_index_removal' do
+ before do
+ connection.create_table(table_name)
+ connection.add_index(table_name, 'id', name: index_name)
+ end
+
+ it 'creates the record for the async index removal' do
+ expect do
+ migration.prepare_async_index_removal(table_name, 'id', name: index_name)
+ end.to change { index_model.where(name: index_name).count }.by(1)
+
+ record = index_model.find_by(name: index_name)
+
+ expect(record.table_name).to eq(table_name)
+ expect(record.definition).to match(/DROP INDEX CONCURRENTLY "#{index_name}"/)
+ end
+
+ context 'when the index does not exist' do
+ it 'does not create the record' do
+ connection.remove_index(table_name, 'id', name: index_name)
+
+ expect do
+ migration.prepare_async_index_removal(table_name, 'id', name: index_name)
+ end.not_to change { index_model.where(name: index_name).count }
+ end
+ end
+
+ context 'when the record already exists' do
+ it 'does attempt to create the record' do
+ create(:postgres_async_index, table_name: table_name, name: index_name)
+
+ expect do
+ migration.prepare_async_index_removal(table_name, 'id', name: index_name)
+ end.not_to change { index_model.where(name: index_name).count }
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/async_indexes/postgres_async_index_spec.rb b/spec/lib/gitlab/database/async_indexes/postgres_async_index_spec.rb
index 223730f87c0..806d57af4b3 100644
--- a/spec/lib/gitlab/database/async_indexes/postgres_async_index_spec.rb
+++ b/spec/lib/gitlab/database/async_indexes/postgres_async_index_spec.rb
@@ -16,4 +16,21 @@ RSpec.describe Gitlab::Database::AsyncIndexes::PostgresAsyncIndex, type: :model
it { is_expected.to validate_presence_of(:definition) }
it { is_expected.to validate_length_of(:definition).is_at_most(definition_limit) }
end
+
+ describe 'scopes' do
+ let!(:async_index_creation) { create(:postgres_async_index) }
+ let!(:async_index_destruction) { create(:postgres_async_index, :with_drop) }
+
+ describe '.to_create' do
+ subject { described_class.to_create }
+
+ it { is_expected.to contain_exactly(async_index_creation) }
+ end
+
+ describe '.to_drop' do
+ subject { described_class.to_drop }
+
+ it { is_expected.to contain_exactly(async_index_destruction) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/async_indexes_spec.rb b/spec/lib/gitlab/database/async_indexes_spec.rb
index 74e30ea2c4e..8a5509f892f 100644
--- a/spec/lib/gitlab/database/async_indexes_spec.rb
+++ b/spec/lib/gitlab/database/async_indexes_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe Gitlab::Database::AsyncIndexes do
end
it 'takes 2 pending indexes and creates those' do
- Gitlab::Database::AsyncIndexes::PostgresAsyncIndex.order(:id).limit(2).each do |index|
+ Gitlab::Database::AsyncIndexes::PostgresAsyncIndex.to_create.order(:id).limit(2).each do |index|
creator = double('index creator')
expect(Gitlab::Database::AsyncIndexes::IndexCreator).to receive(:new).with(index).and_return(creator)
expect(creator).to receive(:perform)
@@ -20,4 +20,22 @@ RSpec.describe Gitlab::Database::AsyncIndexes do
subject
end
end
+
+ describe '.drop_pending_indexes!' do
+ subject { described_class.drop_pending_indexes! }
+
+ before do
+ create_list(:postgres_async_index, 4, :with_drop)
+ end
+
+ it 'takes 2 pending indexes and destroys those' do
+ Gitlab::Database::AsyncIndexes::PostgresAsyncIndex.to_drop.order(:id).limit(2).each do |index|
+ destructor = double('index destructor')
+ expect(Gitlab::Database::AsyncIndexes::IndexDestructor).to receive(:new).with(index).and_return(destructor)
+ expect(destructor).to receive(:perform)
+ end
+
+ subject
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb
index a7b3670da7c..32746a46308 100644
--- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb
@@ -304,6 +304,13 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d
it { expect(subject).to be_falsey }
end
+
+ context 'when the batch_size is 1' do
+ let(:job) { create(:batched_background_migration_job, :failed, batch_size: 1) }
+ let(:exception) { ActiveRecord::StatementTimeout.new }
+
+ it { expect(subject).to be_falsey }
+ end
end
describe '#time_efficiency' do
@@ -415,10 +422,18 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d
end
context 'when batch size is already 1' do
- let!(:job) { create(:batched_background_migration_job, :failed, batch_size: 1) }
+ let!(:job) { create(:batched_background_migration_job, :failed, batch_size: 1, attempts: 3) }
- it 'raises an exception' do
- expect { job.split_and_retry! }.to raise_error 'Job cannot be split further'
+ it 'keeps the same batch size' do
+ job.split_and_retry!
+
+ expect(job.reload.batch_size).to eq 1
+ end
+
+ it 'resets the number of attempts' do
+ job.split_and_retry!
+
+ expect(job.attempts).to eq 0
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
index b8ff78be333..4ef2e7f936b 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb
@@ -15,8 +15,8 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
end
before do
- allow(Gitlab::Database::BackgroundMigration::HealthStatus).to receive(:evaluate)
- .and_return(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Normal)
+ normal_signal = instance_double(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Normal, stop?: false)
+ allow(Gitlab::Database::BackgroundMigration::HealthStatus).to receive(:evaluate).and_return([normal_signal])
end
describe '#run_migration_job' do
@@ -77,14 +77,14 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
end
it 'puts migration on hold on stop signal' do
- expect(health_status).to receive(:evaluate).and_return(stop_signal)
+ expect(health_status).to receive(:evaluate).and_return([stop_signal])
expect { runner.run_migration_job(migration) }.to change { migration.on_hold? }
.from(false).to(true)
end
it 'optimizes migration on normal signal' do
- expect(health_status).to receive(:evaluate).and_return(normal_signal)
+ expect(health_status).to receive(:evaluate).and_return([normal_signal])
expect(migration).to receive(:optimize!)
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
end
it 'optimizes migration on no signal' do
- expect(health_status).to receive(:evaluate).and_return(not_available_signal)
+ expect(health_status).to receive(:evaluate).and_return([not_available_signal])
expect(migration).to receive(:optimize!)
@@ -100,7 +100,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do
end
it 'optimizes migration on unknown signal' do
- expect(health_status).to receive(:evaluate).and_return(unknown_signal)
+ expect(health_status).to receive(:evaluate).and_return([unknown_signal])
expect(migration).to receive(:optimize!)
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 55f607c0cb0..06c2bc32db3 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
@@ -307,7 +307,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
describe '#batch_class' do
- let(:batch_class) { Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy}
+ let(:batch_class) { Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy }
let(:batched_migration) { build(:batched_background_migration) }
it 'returns the class of the batch strategy for the migration' do
@@ -617,6 +617,49 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
end
+ describe '#progress' do
+ subject { migration.progress }
+
+ context 'when the migration is finished' do
+ let(:migration) do
+ create(:batched_background_migration, :finished, total_tuple_count: 1).tap do |record|
+ create(:batched_background_migration_job, :succeeded, batched_migration: record, batch_size: 1)
+ end
+ end
+
+ it 'returns 100' do
+ expect(subject).to be 100
+ end
+ end
+
+ context 'when the migration does not have jobs' do
+ let(:migration) { create(:batched_background_migration, :active) }
+
+ it 'returns zero' do
+ expect(subject).to be 0
+ end
+ end
+
+ context 'when the `total_tuple_count` is zero' do
+ let(:migration) { create(:batched_background_migration, :active, total_tuple_count: 0) }
+ let!(:batched_job) { create(:batched_background_migration_job, :succeeded, batched_migration: migration) }
+
+ it 'returns nil' do
+ expect(subject).to be nil
+ end
+ end
+
+ context 'when migration has completed jobs' do
+ let(:migration) { create(:batched_background_migration, :active, total_tuple_count: 100) }
+
+ let!(:batched_job) { create(:batched_background_migration_job, :succeeded, batched_migration: migration, batch_size: 8) }
+
+ it 'calculates the progress' do
+ expect(subject).to be 8
+ end
+ end
+ end
+
describe '.for_configuration' do
let!(:attributes) do
{
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 83c0275a870..983f482d464 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
@@ -38,10 +38,11 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
batch_column: 'id',
sub_batch_size: 1,
pause_ms: pause_ms,
+ job_arguments: active_migration.job_arguments,
connection: connection)
.and_return(job_instance)
- expect(job_instance).to receive(:perform).with('id', 'other_id')
+ expect(job_instance).to receive(:perform).with(no_args)
perform
end
@@ -49,7 +50,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
it 'updates the tracking record in the database' do
test_metrics = { 'my_metrics' => 'some value' }
- expect(job_instance).to receive(:perform).with('id', 'other_id')
+ expect(job_instance).to receive(:perform).with(no_args)
expect(job_instance).to receive(:batch_metrics).and_return(test_metrics)
freeze_time do
@@ -78,7 +79,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
it 'increments attempts and updates other fields' do
updated_metrics = { 'updated_metrics' => 'some_value' }
- expect(job_instance).to receive(:perform).with('id', 'other_id')
+ expect(job_instance).to receive(:perform).with(no_args)
expect(job_instance).to receive(:batch_metrics).and_return(updated_metrics)
freeze_time do
@@ -97,7 +98,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
context 'when the migration job does not raise an error' do
it 'marks the tracking record as succeeded' do
- expect(job_instance).to receive(:perform).with('id', 'other_id')
+ expect(job_instance).to receive(:perform).with(no_args)
freeze_time do
perform
@@ -110,7 +111,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
end
it 'tracks metrics of the execution' do
- expect(job_instance).to receive(:perform).with('id', 'other_id')
+ expect(job_instance).to receive(:perform).with(no_args)
expect(metrics_tracker).to receive(:track).with(job_record)
perform
@@ -120,7 +121,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
context 'when the migration job raises an error' do
shared_examples 'an error is raised' do |error_class|
it 'marks the tracking record as failed' do
- expect(job_instance).to receive(:perform).with('id', 'other_id').and_raise(error_class)
+ expect(job_instance).to receive(:perform).with(no_args).and_raise(error_class)
freeze_time do
expect { perform }.to raise_error(error_class)
@@ -133,7 +134,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
end
it 'tracks metrics of the execution' do
- expect(job_instance).to receive(:perform).with('id', 'other_id').and_raise(error_class)
+ expect(job_instance).to receive(:perform).with(no_args).and_raise(error_class)
expect(metrics_tracker).to receive(:track).with(job_record)
expect { perform }.to raise_error(error_class)
@@ -147,6 +148,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
context 'when the batched background migration does not inherit from BatchedMigrationJob' do
let(:job_class) { Class.new }
+ let(:job_instance) { job_class.new }
it 'runs the job with the correct arguments' do
expect(job_class).to receive(:new).with(no_args).and_return(job_instance)
diff --git a/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb b/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb
index 21204814f17..db4383a79d4 100644
--- a/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/health_status/indicators/autovacuum_active_on_table_spec.rb
@@ -20,9 +20,9 @@ RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::
swapout_view_for_table(:postgres_autovacuum_activity)
end
- let(:context) { Gitlab::Database::BackgroundMigration::HealthStatus::Context.new(tables) }
let(:tables) { [table] }
let(:table) { 'users' }
+ let(:context) { Gitlab::Database::BackgroundMigration::HealthStatus::Context.new(connection, tables) }
context 'without autovacuum activity' do
it 'returns Normal signal' do
diff --git a/spec/lib/gitlab/database/background_migration/health_status/indicators/write_ahead_log_spec.rb b/spec/lib/gitlab/database/background_migration/health_status/indicators/write_ahead_log_spec.rb
new file mode 100644
index 00000000000..650f11e3cd5
--- /dev/null
+++ b/spec/lib/gitlab/database/background_migration/health_status/indicators/write_ahead_log_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus::Indicators::WriteAheadLog do
+ let(:connection) { Gitlab::Database.database_base_models[:main].connection }
+
+ around do |example|
+ Gitlab::Database::SharedModel.using_connection(connection) do
+ example.run
+ end
+ end
+
+ describe '#evaluate' do
+ let(:tables) { [table] }
+ let(:table) { 'users' }
+ let(:context) { Gitlab::Database::BackgroundMigration::HealthStatus::Context.new(connection, tables) }
+
+ subject(:evaluate) { described_class.new(context).evaluate }
+
+ it 'remembers the indicator class' do
+ expect(evaluate.indicator_class).to eq(described_class)
+ end
+
+ it 'returns NoSignal signal in case the feature flag is disabled' do
+ stub_feature_flags(batched_migrations_health_status_wal: false)
+
+ expect(evaluate).to be_a(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::NotAvailable)
+ expect(evaluate.reason).to include('indicator disabled')
+ end
+
+ it 'returns NoSignal signal when WAL archive queue can not be calculated' do
+ expect(connection).to receive(:execute).and_return([{ 'pending_wal_count' => nil }])
+
+ expect(evaluate).to be_a(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::NotAvailable)
+ expect(evaluate.reason).to include('WAL archive queue can not be calculated')
+ end
+
+ it 'uses primary database' do
+ expect(Gitlab::Database::LoadBalancing::Session.current).to receive(:use_primary).and_yield
+
+ evaluate
+ end
+
+ context 'when WAL archive queue size is below the limit' do
+ it 'returns Normal signal' do
+ expect(connection).to receive(:execute).and_return([{ 'pending_wal_count' => 1 }])
+ expect(evaluate).to be_a(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Normal)
+ expect(evaluate.reason).to include('WAL archive queue is within limit')
+ end
+ end
+
+ context 'when WAL archive queue size is above the limit' do
+ it 'returns Stop signal' do
+ expect(connection).to receive(:execute).and_return([{ 'pending_wal_count' => 420 }])
+ expect(evaluate).to be_a(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Stop)
+ expect(evaluate.reason).to include('WAL archive queue is too big')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/background_migration/health_status_spec.rb b/spec/lib/gitlab/database/background_migration/health_status_spec.rb
index 6d0430dcbbb..8bc04d80fa1 100644
--- a/spec/lib/gitlab/database/background_migration/health_status_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/health_status_spec.rb
@@ -12,30 +12,47 @@ RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus do
end
describe '.evaluate' do
- subject(:evaluate) { described_class.evaluate(migration, indicator_class) }
+ subject(:evaluate) { described_class.evaluate(migration, [autovacuum_indicator_class]) }
let(:migration) { build(:batched_background_migration, :active) }
- let(:health_status) { 'Gitlab::Database::BackgroundMigration::HealthStatus' }
- let(:indicator_class) { class_double("#{health_status}::Indicators::AutovacuumActiveOnTable") }
- let(:indicator) { instance_double("#{health_status}::Indicators::AutovacuumActiveOnTable") }
+ let(:health_status) { Gitlab::Database::BackgroundMigration::HealthStatus }
+ let(:autovacuum_indicator_class) { health_status::Indicators::AutovacuumActiveOnTable }
+ let(:wal_indicator_class) { health_status::Indicators::WriteAheadLog }
+ let(:autovacuum_indicator) { instance_double(autovacuum_indicator_class) }
+ let(:wal_indicator) { instance_double(wal_indicator_class) }
before do
- allow(indicator_class).to receive(:new).with(migration.health_context).and_return(indicator)
+ allow(autovacuum_indicator_class).to receive(:new).with(migration.health_context).and_return(autovacuum_indicator)
end
- it 'returns a signal' do
+ context 'with default indicators' do
+ subject(:evaluate) { described_class.evaluate(migration) }
+
+ it 'returns a collection of signals' do
+ normal_signal = instance_double("#{health_status}::Signals::Normal", log_info?: false)
+ not_available_signal = instance_double("#{health_status}::Signals::NotAvailable", log_info?: false)
+
+ expect(autovacuum_indicator).to receive(:evaluate).and_return(normal_signal)
+ expect(wal_indicator_class).to receive(:new).with(migration.health_context).and_return(wal_indicator)
+ expect(wal_indicator).to receive(:evaluate).and_return(not_available_signal)
+
+ expect(evaluate).to contain_exactly(normal_signal, not_available_signal)
+ end
+ end
+
+ it 'returns a collection of signals' do
signal = instance_double("#{health_status}::Signals::Normal", log_info?: false)
- expect(indicator).to receive(:evaluate).and_return(signal)
+ expect(autovacuum_indicator).to receive(:evaluate).and_return(signal)
- expect(evaluate).to eq(signal)
+ expect(evaluate).to contain_exactly(signal)
end
it 'logs interesting signals' do
signal = instance_double("#{health_status}::Signals::Stop", log_info?: true)
- expect(indicator).to receive(:evaluate).and_return(signal)
+ expect(autovacuum_indicator).to receive(:evaluate).and_return(signal)
expect(described_class).to receive(:log_signal).with(signal, migration)
evaluate
@@ -44,7 +61,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus do
it 'does not log signals of no interest' do
signal = instance_double("#{health_status}::Signals::Normal", log_info?: false)
- expect(indicator).to receive(:evaluate).and_return(signal)
+ expect(autovacuum_indicator).to receive(:evaluate).and_return(signal)
expect(described_class).not_to receive(:log_signal)
evaluate
@@ -54,7 +71,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus do
let(:error) { RuntimeError.new('everything broken') }
before do
- expect(indicator).to receive(:evaluate).and_raise(error)
+ expect(autovacuum_indicator).to receive(:evaluate).and_raise(error)
end
it 'does not fail' do
@@ -62,8 +79,10 @@ RSpec.describe Gitlab::Database::BackgroundMigration::HealthStatus do
end
it 'returns Unknown signal' do
- expect(evaluate).to be_an_instance_of(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Unknown)
- expect(evaluate.reason).to eq("unexpected error: everything broken (RuntimeError)")
+ signal = evaluate.first
+
+ expect(signal).to be_an_instance_of(Gitlab::Database::BackgroundMigration::HealthStatus::Signals::Unknown)
+ expect(signal.reason).to eq("unexpected error: everything broken (RuntimeError)")
end
it 'reports the exception to error tracking' do
diff --git a/spec/lib/gitlab/database/bulk_update_spec.rb b/spec/lib/gitlab/database/bulk_update_spec.rb
index 08b4d50f83b..fa519cffd6b 100644
--- a/spec/lib/gitlab/database/bulk_update_spec.rb
+++ b/spec/lib/gitlab/database/bulk_update_spec.rb
@@ -91,7 +91,8 @@ RSpec.describe Gitlab::Database::BulkUpdate do
.to eq(['MR a', 'Issue a', 'Issue b'])
end
- context 'validates prepared_statements support', :reestablished_active_record_base do
+ context 'validates prepared_statements support', :reestablished_active_record_base,
+ :suppress_gitlab_schemas_validate_connection do
using RSpec::Parameterized::TableSyntax
where(:prepared_statements) do
diff --git a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb
index 34eb64997c1..9c09253b24c 100644
--- a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb
@@ -358,7 +358,11 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
end
it 'returns true for deeply wrapped/nested errors' do
- top = twice_wrapped_exception(ActionView::Template::Error, ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished)
+ top = twice_wrapped_exception(
+ ActionView::Template::Error,
+ ActiveRecord::StatementInvalid,
+ ActiveRecord::ConnectionNotEstablished
+ )
expect(lb.connection_error?(top)).to eq(true)
end
@@ -404,7 +408,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
end
describe '#select_up_to_date_host' do
- let(:location) { 'AB/12345'}
+ let(:location) { 'AB/12345' }
let(:hosts) { lb.host_list.hosts }
let(:set_host) { request_cache[described_class::CACHE_KEY] }
@@ -455,7 +459,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
end
it 'does not modify connection class pool' do
- expect { with_replica_pool(5) { } }.not_to change { ActiveRecord::Base.connection_pool }
+ expect { with_replica_pool(5) {} }.not_to change { ActiveRecord::Base.connection_pool }
end
def with_replica_pool(*args)
diff --git a/spec/lib/gitlab/database/load_balancing/rack_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/rack_middleware_spec.rb
index b768d4ecea3..a1c141af537 100644
--- a/spec/lib/gitlab/database/load_balancing/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/rack_middleware_spec.rb
@@ -30,6 +30,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::RackMiddleware, :redis do
expect(app).to receive(:call).with(env).and_return(10)
+ allow(ActiveSupport::Notifications).to receive(:instrument).and_call_original
+
expect(ActiveSupport::Notifications)
.to receive(:instrument)
.with('web_transaction_completed.load_balancing')
diff --git a/spec/lib/gitlab/database/load_balancing/session_spec.rb b/spec/lib/gitlab/database/load_balancing/session_spec.rb
index 74512f76fd4..05b44579c62 100644
--- a/spec/lib/gitlab/database/load_balancing/session_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/session_spec.rb
@@ -132,7 +132,11 @@ RSpec.describe Gitlab::Database::LoadBalancing::Session do
it 'does not prevent using primary if an exception is raised' do
instance = described_class.new
- instance.ignore_writes { raise ArgumentError } rescue ArgumentError
+ begin
+ instance.ignore_writes { raise ArgumentError }
+ rescue ArgumentError
+ nil
+ end
instance.write!
expect(instance).to be_using_primary
diff --git a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
index 31be3963565..8053bd57bba 100644
--- a/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::LoadBalancing::SidekiqServerMiddleware, :clean_gitlab_redis_queues do
let(:middleware) { described_class.new }
let(:worker) { worker_class.new }
- let(:location) {'0/D525E3A8' }
+ let(:location) { '0/D525E3A8' }
let(:wal_locations) { { Gitlab::Database::MAIN_DATABASE_NAME.to_sym => location } }
let(:job) { { "retry" => 3, "job_id" => "a180b47c-3fd6-41b8-81e9-34da61c3400e", 'wal_locations' => wal_locations } }
diff --git a/spec/lib/gitlab/database/load_balancing/sticking_spec.rb b/spec/lib/gitlab/database/load_balancing/sticking_spec.rb
index f3139bb1b4f..2ffb2c32c32 100644
--- a/spec/lib/gitlab/database/load_balancing/sticking_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing/sticking_spec.rb
@@ -77,6 +77,8 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
let(:last_write_location) { 'foo' }
before do
+ allow(ActiveSupport::Notifications).to receive(:instrument).and_call_original
+
allow(sticking)
.to receive(:last_write_location_for)
.with(:user, 42)
diff --git a/spec/lib/gitlab/database/load_balancing_spec.rb b/spec/lib/gitlab/database/load_balancing_spec.rb
index f320fe0276f..76dfaa74ae6 100644
--- a/spec/lib/gitlab/database/load_balancing_spec.rb
+++ b/spec/lib/gitlab/database/load_balancing_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Database::LoadBalancing do
+RSpec.describe Gitlab::Database::LoadBalancing, :suppress_gitlab_schemas_validate_connection do
describe '.base_models' do
it 'returns the models to apply load balancing to' do
models = described_class.base_models
diff --git a/spec/lib/gitlab/database/lock_writes_manager_spec.rb b/spec/lib/gitlab/database/lock_writes_manager_spec.rb
new file mode 100644
index 00000000000..eb527d492cf
--- /dev/null
+++ b/spec/lib/gitlab/database/lock_writes_manager_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::LockWritesManager do
+ let(:connection) { ApplicationRecord.connection }
+ let(:test_table) { '_test_table' }
+ let(:logger) { instance_double(Logger) }
+
+ subject(:lock_writes_manager) do
+ described_class.new(
+ table_name: test_table,
+ connection: connection,
+ database_name: 'main',
+ logger: logger
+ )
+ end
+
+ before do
+ allow(logger).to receive(:info)
+
+ connection.execute(<<~SQL)
+ CREATE TABLE #{test_table} (id integer NOT NULL, value integer NOT NULL DEFAULT 0);
+
+ INSERT INTO #{test_table} (id, value)
+ VALUES (1, 1), (2, 2), (3, 3)
+ SQL
+ end
+
+ describe '#lock_writes' do
+ it 'prevents any writes on the table' do
+ subject.lock_writes
+
+ expect do
+ connection.execute("delete from #{test_table}")
+ end.to raise_error(ActiveRecord::StatementInvalid, /Table: "#{test_table}" is write protected/)
+ end
+
+ it 'prevents truncating the table' do
+ subject.lock_writes
+
+ expect do
+ connection.execute("truncate #{test_table}")
+ end.to raise_error(ActiveRecord::StatementInvalid, /Table: "#{test_table}" is write protected/)
+ end
+
+ it 'adds 3 triggers to the ci schema tables on the main database' do
+ expect do
+ subject.lock_writes
+ end.to change {
+ number_of_triggers_on(connection, test_table)
+ }.by(3) # Triggers to block INSERT / UPDATE / DELETE
+ # Triggers on TRUNCATE are not added to the information_schema.triggers
+ # See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us
+ end
+
+ it 'logs the write locking' do
+ expect(logger).to receive(:info).with("Database: 'main', Table: '_test_table': Lock Writes")
+
+ subject.lock_writes
+ end
+
+ it 'retries again if it receives a statement_timeout a few number of times' do
+ error_message = "PG::QueryCanceled: ERROR: canceling statement due to statement timeout"
+ call_count = 0
+ allow(connection).to receive(:execute) do |statement|
+ if statement.include?("CREATE TRIGGER")
+ call_count += 1
+ raise(ActiveRecord::QueryCanceled, error_message) if call_count.even?
+ end
+ end
+ subject.lock_writes
+ end
+
+ it 'raises the exception if it happened many times' do
+ error_message = "PG::QueryCanceled: ERROR: canceling statement due to statement timeout"
+ allow(connection).to receive(:execute) do |statement|
+ if statement.include?("CREATE TRIGGER")
+ raise(ActiveRecord::QueryCanceled, error_message)
+ end
+ end
+
+ expect do
+ subject.lock_writes
+ end.to raise_error(ActiveRecord::QueryCanceled)
+ end
+ end
+
+ describe '#unlock_writes' do
+ before do
+ subject.lock_writes
+ end
+
+ it 'allows writing on the table again' do
+ subject.unlock_writes
+
+ expect do
+ connection.execute("delete from #{test_table}")
+ end.not_to raise_error
+ end
+
+ it 'removes the write protection triggers from the gitlab_main tables on the ci database' do
+ expect do
+ subject.unlock_writes
+ end.to change {
+ number_of_triggers_on(connection, test_table)
+ }.by(-3) # Triggers to block INSERT / UPDATE / DELETE
+ # Triggers on TRUNCATE are not added to the information_schema.triggers
+ # See https://www.postgresql.org/message-id/16934.1568989957%40sss.pgh.pa.us
+ end
+
+ it 'logs the write unlocking' do
+ expect(logger).to receive(:info).with("Database: 'main', Table: '_test_table': Allow Writes")
+
+ subject.unlock_writes
+ end
+ end
+
+ def number_of_triggers_on(connection, table_name)
+ connection
+ .select_value("SELECT count(*) FROM information_schema.triggers WHERE event_object_table=$1", nil, [table_name])
+ end
+end
diff --git a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
index 87a3e0f81e4..ff99f681b0c 100644
--- a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
@@ -84,4 +84,32 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do
end
end
end
+
+ describe '.definitions' do
+ subject(:definitions) { described_class.definitions }
+
+ it 'contains at least all parent tables that have triggers' do
+ all_definition_parent_tables = definitions.map { |d| d.to_table }.to_set
+
+ triggers_query = <<~SQL
+ SELECT event_object_table, trigger_name
+ FROM information_schema.triggers
+ WHERE trigger_name LIKE '%_loose_fk_trigger'
+ GROUP BY event_object_table, trigger_name
+ SQL
+
+ all_triggers = ApplicationRecord.connection.execute(triggers_query)
+
+ all_triggers.each do |trigger|
+ table = trigger['event_object_table']
+ trigger_name = trigger['trigger_name']
+ error_message = <<~END
+ Missing a loose foreign key definition for parent table: #{table} with trigger: #{trigger_name}.
+ Loose foreign key definitions must be added before triggers are added and triggers must be removed before removing the loose foreign key definition.
+ Read more at https://docs.gitlab.com/ee/development/database/loose_foreign_keys.html ."
+ END
+ expect(all_definition_parent_tables).to include(table), error_message
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb b/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
index 1009ec354c3..e43cfe0814e 100644
--- a/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
@@ -5,6 +5,13 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_analyzers: false, stub_feature_flags: false do
let(:schema_class) { Class.new(Gitlab::Database::Migration[1.0]).include(described_class) }
+ # We keep only the GitlabSchemasValidateConnection analyzer running
+ around do |example|
+ Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection.with_suppressed(false) do
+ example.run
+ end
+ end
+
describe '#restrict_gitlab_migration' do
it 'invalid schema raises exception' do
expect { schema_class.restrict_gitlab_migration gitlab_schema: :gitlab_non_exisiting }
diff --git a/spec/lib/gitlab/database/migration_helpers/v2_spec.rb b/spec/lib/gitlab/database/migration_helpers/v2_spec.rb
index 5c054795697..2055dc33d48 100644
--- a/spec/lib/gitlab/database/migration_helpers/v2_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers/v2_spec.rb
@@ -266,7 +266,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::V2 do
let(:env) { { 'DISABLE_LOCK_RETRIES' => 'true' } }
it 'sets the migration class name in the logs' do
- model.with_lock_retries(env: env, logger: in_memory_logger) { }
+ model.with_lock_retries(env: env, logger: in_memory_logger) {}
buffer.rewind
expect(buffer.read).to include("\"class\":\"#{model.class}\"")
@@ -280,7 +280,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::V2 do
expect(Gitlab::Database::WithLockRetries).to receive(:new).and_return(with_lock_retries)
expect(with_lock_retries).to receive(:run).with(raise_on_exhaustion: raise_on_exhaustion)
- model.with_lock_retries(env: env, logger: in_memory_logger, raise_on_exhaustion: raise_on_exhaustion) { }
+ model.with_lock_retries(env: env, logger: in_memory_logger, raise_on_exhaustion: raise_on_exhaustion) {}
end
end
@@ -289,7 +289,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::V2 do
expect(Gitlab::Database::WithLockRetries).to receive(:new).and_return(with_lock_retries)
expect(with_lock_retries).to receive(:run).with(raise_on_exhaustion: false)
- model.with_lock_retries(env: env, logger: in_memory_logger) { }
+ model.with_lock_retries(env: env, logger: in_memory_logger) {}
end
it 'defaults to disallowing subtransactions' do
@@ -297,7 +297,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::V2 do
expect(Gitlab::Database::WithLockRetries).to receive(:new).with(hash_including(allow_savepoints: false)).and_return(with_lock_retries)
expect(with_lock_retries).to receive(:run).with(raise_on_exhaustion: false)
- model.with_lock_retries(env: env, logger: in_memory_logger) { }
+ model.with_lock_retries(env: env, logger: in_memory_logger) {}
end
context 'when in transaction' do
@@ -323,7 +323,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::V2 do
end
it 'raises an error' do
- expect { model.with_lock_retries(env: env, logger: in_memory_logger) { } }.to raise_error /can not be run inside an already open transaction/
+ expect { model.with_lock_retries(env: env, logger: in_memory_logger) {} }.to raise_error /can not be run inside an already open transaction/
end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 3ccc3a17862..dd5ad40d8ef 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
describe 'overridden dynamic model helpers' do
- let(:test_table) { '__test_batching_table' }
+ let(:test_table) { '_test_batching_table' }
before do
model.connection.execute(<<~SQL)
@@ -1022,6 +1022,40 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(Project.sum(:star_count)).to eq(2 * Project.count)
end
end
+
+ context 'when the table is write-locked' do
+ let(:test_table) { '_test_table' }
+ let(:lock_writes_manager) do
+ Gitlab::Database::LockWritesManager.new(
+ table_name: test_table,
+ connection: model.connection,
+ database_name: 'main'
+ )
+ end
+
+ before do
+ model.connection.execute(<<~SQL)
+ CREATE TABLE #{test_table} (id integer NOT NULL, value integer NOT NULL DEFAULT 0);
+
+ INSERT INTO #{test_table} (id, value)
+ VALUES (1, 1), (2, 2), (3, 3)
+ SQL
+
+ lock_writes_manager.lock_writes
+ end
+
+ it 'disables the write-lock trigger function' do
+ expect do
+ model.update_column_in_batches(test_table, :value, Arel.sql('1+1'), disable_lock_writes: true)
+ end.not_to raise_error
+ end
+
+ it 'raises an error if it does not disable the trigger function' do
+ expect do
+ model.update_column_in_batches(test_table, :value, Arel.sql('1+1'), disable_lock_writes: false)
+ end.to raise_error(ActiveRecord::StatementInvalid, /Table: "#{test_table}" is write protected/)
+ end
+ end
end
context 'when running inside the transaction' do
@@ -1080,6 +1114,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'renames a column concurrently' do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
+
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers)
@@ -1112,6 +1148,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
let(:connection) { ActiveRecord::Migration.connection }
before do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
expect(Gitlab::Database::UnidirectionalCopyTrigger).to receive(:on_table)
.with(:users, connection: connection).and_return(copy_trigger)
end
@@ -1119,6 +1156,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
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("SELECT set_config('lock_writes.users', 'false', true)")
expect(model).to receive(:execute).with("UPDATE \"users\" SET \"new\" = cast_to_jsonb_with_default(\"users\".\"id\") WHERE \"users\".\"id\" >= #{user.id}")
expect(copy_trigger).to receive(:create).with(:id, :new, trigger_name: nil)
@@ -1165,6 +1203,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'copies the default to the new column' do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
+
expect(model).to receive(:change_column_default)
.with(:users, :new, old_column.default)
@@ -1176,6 +1216,34 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
+ context 'when the table in the other database is write-locked' do
+ let(:test_table) { '_test_table' }
+ let(:lock_writes_manager) do
+ Gitlab::Database::LockWritesManager.new(
+ table_name: test_table,
+ connection: model.connection,
+ database_name: 'main'
+ )
+ end
+
+ before do
+ model.connection.execute(<<~SQL)
+ CREATE TABLE #{test_table} (id integer NOT NULL, value integer NOT NULL DEFAULT 0);
+
+ INSERT INTO #{test_table} (id, value)
+ VALUES (1, 1), (2, 2), (3, 3)
+ SQL
+
+ lock_writes_manager.lock_writes
+ end
+
+ it 'does not raise an error when renaming the column' do
+ expect do
+ model.rename_column_concurrently(test_table, :value, :new_value)
+ end.not_to raise_error
+ end
+ end
+
context 'when the column to be renamed does not exist' do
before do
allow(model).to receive(:columns).and_return([])
@@ -1246,6 +1314,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'reverses the operations of cleanup_concurrent_column_rename' do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
+
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:install_rename_triggers)
@@ -1302,6 +1372,8 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
it 'copies the default to the old column' do
+ expect(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection).to receive(:with_suppressed).and_yield
+
expect(model).to receive(:change_column_default)
.with(:users, :old, new_column.default)
@@ -2438,7 +2510,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
let(:env) { { 'DISABLE_LOCK_RETRIES' => 'true' } }
it 'sets the migration class name in the logs' do
- model.with_lock_retries(env: env, logger: in_memory_logger) { }
+ model.with_lock_retries(env: env, logger: in_memory_logger) {}
buffer.rewind
expect(buffer.read).to include("\"class\":\"#{model.class}\"")
@@ -2452,7 +2524,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(Gitlab::Database::WithLockRetries).to receive(:new).and_return(with_lock_retries)
expect(with_lock_retries).to receive(:run).with(raise_on_exhaustion: raise_on_exhaustion)
- model.with_lock_retries(env: env, logger: in_memory_logger, raise_on_exhaustion: raise_on_exhaustion) { }
+ model.with_lock_retries(env: env, logger: in_memory_logger, raise_on_exhaustion: raise_on_exhaustion) {}
end
end
@@ -2461,7 +2533,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(Gitlab::Database::WithLockRetries).to receive(:new).and_return(with_lock_retries)
expect(with_lock_retries).to receive(:run).with(raise_on_exhaustion: false)
- model.with_lock_retries(env: env, logger: in_memory_logger) { }
+ model.with_lock_retries(env: env, logger: in_memory_logger) {}
end
it 'defaults to allowing subtransactions' do
@@ -2470,7 +2542,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
expect(Gitlab::Database::WithLockRetries).to receive(:new).with(hash_including(allow_savepoints: true)).and_return(with_lock_retries)
expect(with_lock_retries).to receive(:run).with(raise_on_exhaustion: false)
- model.with_lock_retries(env: env, logger: in_memory_logger) { }
+ model.with_lock_retries(env: env, logger: in_memory_logger) {}
end
end
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 c423340a572..f21f1ac5e52 100644
--- a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
@@ -37,12 +37,6 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
freeze_time { example.run }
end
- before do
- User.class_eval do
- include EachBatch
- end
- end
-
it 'returns the final expected delay' do
Sidekiq::Testing.fake! do
final_delay = model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, batch_size: 2)
diff --git a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
index 5bfb2516ba1..a2f6e6b43ed 100644
--- a/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/batched_background_migration_helpers_spec.rb
@@ -15,12 +15,25 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
describe '#queue_batched_background_migration' do
let(:pgclass_info) { instance_double('Gitlab::Database::PgClass', cardinality_estimate: 42) }
+ let(:job_class) do
+ Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
+ def self.name
+ 'MyJobClass'
+ end
+ end
+ end
before do
allow(Gitlab::Database::PgClass).to receive(:for_table).and_call_original
expect(Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas).to receive(:require_dml_mode!)
allow(migration).to receive(:transaction_open?).and_return(false)
+
+ stub_const("Gitlab::Database::BackgroundMigration::BatchedMigration::JOB_CLASS_MODULE", '')
+ allow_next_instance_of(Gitlab::Database::BackgroundMigration::BatchedMigration) do |batched_migration|
+ allow(batched_migration).to receive(:job_class)
+ .and_return(job_class)
+ end
end
context 'when such migration already exists' do
@@ -42,7 +55,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
expect do
migration.queue_batched_background_migration(
- 'MyJobClass',
+ job_class.name,
:projects,
:id,
[:id], [:id_convert_to_bigint],
@@ -62,7 +75,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
expect do
migration.queue_batched_background_migration(
- 'MyJobClass',
+ job_class.name,
:projects,
:id,
job_interval: 5.minutes,
@@ -97,7 +110,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
it 'sets the job interval to the minimum value' do
expect do
- migration.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: minimum_delay - 1.minute)
+ migration.queue_batched_background_migration(job_class.name, :events, :id, job_interval: minimum_delay - 1.minute)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
created_migration = Gitlab::Database::BackgroundMigration::BatchedMigration.last
@@ -107,26 +120,76 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
end
context 'when additional arguments are passed to the method' do
- it 'saves the arguments on the database record' do
- expect do
- migration.queue_batched_background_migration(
- 'MyJobClass',
- :projects,
- :id,
- 'my',
- 'arguments',
- job_interval: 5.minutes,
- batch_max_value: 1000)
- end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
+ context 'when the job class provides job_arguments_count' do
+ context 'when defined job arguments for the job class does not match provided arguments' do
+ it 'raises an error' do
+ expect do
+ migration.queue_batched_background_migration(
+ job_class.name,
+ :projects,
+ :id,
+ 'my',
+ 'arguments',
+ job_interval: 2.minutes)
+ end.to raise_error(RuntimeError, /Wrong number of job arguments for MyJobClass \(given 2, expected 0\)/)
+ end
+ end
- expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to have_attributes(
- job_class_name: 'MyJobClass',
- table_name: 'projects',
- column_name: 'id',
- interval: 300,
- min_value: 1,
- max_value: 1000,
- job_arguments: %w[my arguments])
+ context 'when defined job arguments for the job class match provided arguments' do
+ let(:job_class) do
+ Class.new(Gitlab::BackgroundMigration::BatchedMigrationJob) do
+ def self.name
+ 'MyJobClass'
+ end
+
+ job_arguments :foo, :bar
+ end
+ end
+
+ it 'saves the arguments on the database record' do
+ expect do
+ migration.queue_batched_background_migration(
+ job_class.name,
+ :projects,
+ :id,
+ 'my',
+ 'arguments',
+ job_interval: 5.minutes,
+ batch_max_value: 1000)
+ end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
+
+ expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to have_attributes(
+ job_class_name: 'MyJobClass',
+ table_name: 'projects',
+ column_name: 'id',
+ interval: 300,
+ min_value: 1,
+ max_value: 1000,
+ job_arguments: %w[my arguments])
+ end
+ end
+ end
+
+ context 'when the job class does not provide job_arguments_count' do
+ let(:job_class) do
+ Class.new do
+ def self.name
+ 'MyJobClass'
+ end
+ end
+ end
+
+ it 'does not raise an error' do
+ expect do
+ migration.queue_batched_background_migration(
+ job_class.name,
+ :projects,
+ :id,
+ 'my',
+ 'arguments',
+ job_interval: 2.minutes)
+ end.not_to raise_error
+ end
end
end
@@ -138,7 +201,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
it 'creates the record with the current max value' do
expect do
- migration.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: 5.minutes)
+ migration.queue_batched_background_migration(job_class.name, :events, :id, job_interval: 5.minutes)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
created_migration = Gitlab::Database::BackgroundMigration::BatchedMigration.last
@@ -148,7 +211,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
it 'creates the record with an active status' do
expect do
- migration.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: 5.minutes)
+ migration.queue_batched_background_migration(job_class.name, :events, :id, job_interval: 5.minutes)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to be_active
@@ -158,7 +221,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
context 'when the database is empty' do
it 'sets the max value to the min value' do
expect do
- migration.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: 5.minutes)
+ migration.queue_batched_background_migration(job_class.name, :events, :id, job_interval: 5.minutes)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
created_migration = Gitlab::Database::BackgroundMigration::BatchedMigration.last
@@ -168,7 +231,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
it 'creates the record with a finished status' do
expect do
- migration.queue_batched_background_migration('MyJobClass', :projects, :id, job_interval: 5.minutes)
+ migration.queue_batched_background_migration(job_class.name, :projects, :id, job_interval: 5.minutes)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
expect(Gitlab::Database::BackgroundMigration::BatchedMigration.last).to be_finished
@@ -181,7 +244,7 @@ RSpec.describe Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers d
expect(migration).to receive(:gitlab_schema_from_context).and_return(:gitlab_ci)
expect do
- migration.queue_batched_background_migration('MyJobClass', :events, :id, job_interval: 5.minutes)
+ migration.queue_batched_background_migration(job_class.name, :events, :id, job_interval: 5.minutes)
end.to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }.by(1)
created_migration = Gitlab::Database::BackgroundMigration::BatchedMigration.last
diff --git a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb
index c31244060ec..3540a120b8f 100644
--- a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb
+++ b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb
@@ -122,7 +122,11 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do
it 'records observations for all migrations' do
subject.observe(version: migration_version, name: migration_name, connection: connection) {}
- subject.observe(version: migration_version_2, name: migration_name_2, connection: connection) { raise 'something went wrong' } rescue nil
+ begin
+ subject.observe(version: migration_version_2, name: migration_name_2, connection: connection) { raise 'something went wrong' }
+ rescue StandardError
+ nil
+ end
expect { load_observation(result_dir, migration_name) }.not_to raise_error
expect { load_observation(result_dir, migration_name_2) }.not_to raise_error
diff --git a/spec/lib/gitlab/database/migrations/lock_retry_mixin_spec.rb b/spec/lib/gitlab/database/migrations/lock_retry_mixin_spec.rb
index 50ad77caaf1..6092d985ce8 100644
--- a/spec/lib/gitlab/database/migrations/lock_retry_mixin_spec.rb
+++ b/spec/lib/gitlab/database/migrations/lock_retry_mixin_spec.rb
@@ -83,10 +83,10 @@ RSpec.describe Gitlab::Database::Migrations::LockRetryMixin do
context 'with transactions disabled' do
let(:migration) { double('migration', enable_lock_retries?: false) }
- let(:receiver) { double('receiver', use_transaction?: false)}
+ let(:receiver) { double('receiver', use_transaction?: false) }
it 'calls super method' do
- p = proc { }
+ p = proc {}
expect(receiver).to receive(:ddl_transaction).with(migration, &p)
@@ -95,11 +95,11 @@ RSpec.describe Gitlab::Database::Migrations::LockRetryMixin do
end
context 'with transactions enabled, but lock retries disabled' do
- let(:receiver) { double('receiver', use_transaction?: true)}
+ let(:receiver) { double('receiver', use_transaction?: true) }
let(:migration) { double('migration', enable_lock_retries?: false) }
it 'calls super method' do
- p = proc { }
+ p = proc {}
expect(receiver).to receive(:ddl_transaction).with(migration, &p)
@@ -108,12 +108,12 @@ RSpec.describe Gitlab::Database::Migrations::LockRetryMixin do
end
context 'with transactions enabled and lock retries enabled' do
- let(:receiver) { double('receiver', use_transaction?: true)}
+ let(:receiver) { double('receiver', use_transaction?: true) }
let(:migration) { double('migration', migration_connection: connection, enable_lock_retries?: true) }
let(:connection) { ActiveRecord::Base.connection }
it 'calls super method' do
- p = proc { }
+ p = proc {}
expect(receiver).not_to receive(:ddl_transaction)
expect_next_instance_of(Gitlab::Database::WithLockRetries) do |retries|
diff --git a/spec/lib/gitlab/database/migrations/runner_spec.rb b/spec/lib/gitlab/database/migrations/runner_spec.rb
index e7f68e3e4a8..a37247ba0c6 100644
--- a/spec/lib/gitlab/database/migrations/runner_spec.rb
+++ b/spec/lib/gitlab/database/migrations/runner_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe Gitlab::Database::Migrations::Runner do
allow(described_class).to receive(:migration_context).and_return(ctx)
- names_this_branch = (applied_migrations_this_branch + pending_migrations).map { |m| "db/migrate/#{m.version}_#{m.name}.rb"}
+ names_this_branch = (applied_migrations_this_branch + pending_migrations).map { |m| "db/migrate/#{m.version}_#{m.name}.rb" }
allow(described_class).to receive(:migration_file_names_this_branch).and_return(names_this_branch)
end
diff --git a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
index f1f72d71e1a..9451a6bd34a 100644
--- a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
+++ b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
let(:connection) { ApplicationRecord.connection }
- let(:table_name) { "_test_column_copying"}
+ let(:table_name) { "_test_column_copying" }
before do
connection.execute(<<~SQL)
@@ -50,18 +50,16 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
context 'with jobs to run' do
let(:migration_name) { 'TestBackgroundMigration' }
- before do
- migration.queue_batched_background_migration(
- migration_name, table_name, :id, job_interval: 5.minutes, batch_size: 100
- )
- end
-
it 'samples jobs' do
calls = []
define_background_migration(migration_name) do |*args|
calls << args
end
+ migration.queue_batched_background_migration(migration_name, table_name, :id,
+ job_interval: 5.minutes,
+ batch_size: 100)
+
described_class.new(result_dir: result_dir, connection: connection).run_jobs(for_duration: 3.minutes)
expect(calls.count).to eq(10) # 1000 rows / batch size 100 = 10
@@ -70,6 +68,9 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
context 'with multiple jobs to run' do
it 'runs all jobs created within the last 3 hours' do
old_migration = define_background_migration(migration_name)
+ migration.queue_batched_background_migration(migration_name, table_name, :id,
+ job_interval: 5.minutes,
+ batch_size: 100)
travel 4.hours
diff --git a/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
index d8b06ee1a5d..04b9fba5b2f 100644
--- a/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
@@ -48,61 +48,43 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy do
end
describe '#validate_and_fix' do
- context 'feature flag is disabled' do
- before do
- stub_feature_flags(fix_sliding_list_partitioning: false)
- end
+ it 'does not call change_column_default if the partitioning in a valid state' do
+ expect(strategy.model.connection).not_to receive(:change_column_default)
- it 'does not try to fix the default partition value' do
- connection.change_column_default(model.table_name, strategy.partitioning_key, 3)
- expect(strategy.model.connection).not_to receive(:change_column_default)
- strategy.validate_and_fix
- end
+ strategy.validate_and_fix
end
- context 'feature flag is enabled' do
- before do
- stub_feature_flags(fix_sliding_list_partitioning: true)
- end
-
- it 'does not call change_column_default if the partitioning in a valid state' do
- expect(strategy.model.connection).not_to receive(:change_column_default)
-
- strategy.validate_and_fix
- end
-
- it 'calls change_column_default on partition_key with the most default partition number' do
- connection.change_column_default(model.table_name, strategy.partitioning_key, 1)
+ it 'calls change_column_default on partition_key with the most default partition number' do
+ connection.change_column_default(model.table_name, strategy.partitioning_key, 1)
- expect(Gitlab::AppLogger).to receive(:warn).with(
- message: 'Fixed default value of sliding_list_strategy partitioning_key',
- connection_name: 'main',
- old_value: 1,
- new_value: 2,
- table_name: table_name,
- column: strategy.partitioning_key
- )
+ expect(Gitlab::AppLogger).to receive(:warn).with(
+ message: 'Fixed default value of sliding_list_strategy partitioning_key',
+ connection_name: 'main',
+ old_value: 1,
+ new_value: 2,
+ table_name: table_name,
+ column: strategy.partitioning_key
+ )
- expect(strategy.model.connection).to receive(:change_column_default).with(
- model.table_name, strategy.partitioning_key, 2
- ).and_call_original
+ expect(strategy.model.connection).to receive(:change_column_default).with(
+ model.table_name, strategy.partitioning_key, 2
+ ).and_call_original
- strategy.validate_and_fix
- end
+ strategy.validate_and_fix
+ end
- it 'does not change the default column if it has been changed in the meanwhile by another process' do
- expect(strategy).to receive(:current_default_value).and_return(1, 2)
+ it 'does not change the default column if it has been changed in the meanwhile by another process' do
+ expect(strategy).to receive(:current_default_value).and_return(1, 2)
- expect(strategy.model.connection).not_to receive(:change_column_default)
+ expect(strategy.model.connection).not_to receive(:change_column_default)
- expect(Gitlab::AppLogger).to receive(:warn).with(
- message: 'Table partitions or partition key default value have been changed by another process',
- table_name: table_name,
- default_value: 2
- )
+ expect(Gitlab::AppLogger).to receive(:warn).with(
+ message: 'Table partitions or partition key default value have been changed by another process',
+ table_name: table_name,
+ default_value: 2
+ )
- strategy.validate_and_fix
- end
+ strategy.validate_and_fix
end
end
diff --git a/spec/lib/gitlab/database/partitioning_spec.rb b/spec/lib/gitlab/database/partitioning_spec.rb
index 7c69f639aab..36c8b0811fe 100644
--- a/spec/lib/gitlab/database/partitioning_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_spec.rb
@@ -89,7 +89,7 @@ RSpec.describe Gitlab::Database::Partitioning do
end
it 'manages partitions for each given model' do
- expect { described_class.sync_partitions(models)}
+ expect { described_class.sync_partitions(models) }
.to change { find_partitions(table_names.first).size }.from(0)
.and change { find_partitions(table_names.last).size }.from(0)
end
diff --git a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb
index 5e8afc0102e..ddf5793049d 100644
--- a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb
+++ b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb
@@ -5,6 +5,13 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection, query_analyzers: false do
let(:analyzer) { described_class }
+ # We keep only the GitlabSchemasValidateConnection analyzer running
+ around do |example|
+ Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection.with_suppressed(false) do
+ example.run
+ end
+ end
+
context 'properly observes all queries', :request_store do
using RSpec::Parameterized::TableSyntax
@@ -61,6 +68,24 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection
end
end
+ context "when analyzer is enabled for tests", :query_analyzers do
+ before do
+ skip_if_multiple_databases_not_setup
+ end
+
+ it "throws an error when trying to access a table that belongs to the gitlab_main schema from the ci database" do
+ expect do
+ Ci::ApplicationRecord.connection.execute("select * from users limit 1")
+ end.to raise_error(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection::CrossSchemaAccessError)
+ end
+
+ it "throws an error when trying to access a table that belongs to the gitlab_ci schema from the main database" do
+ expect do
+ ApplicationRecord.connection.execute("select * from ci_builds limit 1")
+ end.to raise_error(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection::CrossSchemaAccessError)
+ end
+ end
+
def process_sql(model, sql)
Gitlab::Database::QueryAnalyzer.instance.within([analyzer]) do
# Skip load balancer and retrieve connection assigned to model
diff --git a/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb b/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb
index 34670696787..1bccdda3be1 100644
--- a/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb
+++ b/spec/lib/gitlab/database/reindexing/grafana_notifier_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Database::Reindexing::GrafanaNotifier do
include Database::DatabaseHelpers
let(:api_key) { "foo" }
- let(:api_url) { "http://bar"}
+ let(:api_url) { "http://bar" }
let(:additional_tag) { "some-tag" }
let(:action) { create(:reindex_action) }
diff --git a/spec/lib/gitlab/database/reindexing_spec.rb b/spec/lib/gitlab/database/reindexing_spec.rb
index 976b9896dfa..495e953f993 100644
--- a/spec/lib/gitlab/database/reindexing_spec.rb
+++ b/spec/lib/gitlab/database/reindexing_spec.rb
@@ -46,6 +46,27 @@ RSpec.describe Gitlab::Database::Reindexing do
end
end
+ context 'when async index destruction is enabled' do
+ it 'executes async index destruction prior to any reindexing actions' do
+ stub_feature_flags(database_async_index_destruction: true)
+
+ expect(Gitlab::Database::AsyncIndexes).to receive(:drop_pending_indexes!).ordered.exactly(databases_count).times
+ expect(described_class).to receive(:automatic_reindexing).ordered.exactly(databases_count).times
+
+ described_class.invoke
+ end
+ end
+
+ context 'when async index destruction is disabled' do
+ it 'does not execute async index destruction' do
+ stub_feature_flags(database_async_index_destruction: false)
+
+ expect(Gitlab::Database::AsyncIndexes).not_to receive(:drop_pending_indexes!)
+
+ described_class.invoke
+ end
+ end
+
context 'calls automatic reindexing' do
it 'uses all candidate indexes' do
expect(described_class).to receive(:automatic_reindexing).exactly(databases_count).times
diff --git a/spec/lib/gitlab/database/shared_model_spec.rb b/spec/lib/gitlab/database/shared_model_spec.rb
index c88edc17817..7e0ba3397d1 100644
--- a/spec/lib/gitlab/database/shared_model_spec.rb
+++ b/spec/lib/gitlab/database/shared_model_spec.rb
@@ -106,7 +106,7 @@ RSpec.describe Gitlab::Database::SharedModel do
shared_model = shared_model_class.new
- expect(shared_model.connection_db_config). to eq(described_class.connection_db_config)
+ expect(shared_model.connection_db_config).to eq(described_class.connection_db_config)
end
end
end
diff --git a/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb b/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb
index 6c32fb3ca17..836332524a9 100644
--- a/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb
+++ b/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb
@@ -232,14 +232,14 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction do
expect(connection).to receive(:execute).with('RESET idle_in_transaction_session_timeout; RESET lock_timeout').and_call_original
expect(connection).to receive(:execute).with("SET lock_timeout TO '15ms'").and_call_original
- subject.run { }
+ subject.run {}
end
it 'calls `sleep` after the first iteration fails, using the configured sleep time' do
expect(subject).to receive(:run_block_with_lock_timeout).and_raise(ActiveRecord::LockWaitTimeout).twice
expect(subject).to receive(:sleep).with(0.025)
- subject.run { }
+ subject.run {}
end
end
end
diff --git a/spec/lib/gitlab/database/with_lock_retries_spec.rb b/spec/lib/gitlab/database/with_lock_retries_spec.rb
index 6b35ccafabc..797a01c482d 100644
--- a/spec/lib/gitlab/database/with_lock_retries_spec.rb
+++ b/spec/lib/gitlab/database/with_lock_retries_spec.rb
@@ -248,14 +248,14 @@ RSpec.describe Gitlab::Database::WithLockRetries do
expect(connection).to receive(:execute).with("SET LOCAL lock_timeout TO '15ms'").and_call_original
expect(connection).to receive(:execute).with("RELEASE SAVEPOINT active_record_1", "TRANSACTION").and_call_original
- subject.run { }
+ subject.run {}
end
it 'calls `sleep` after the first iteration fails, using the configured sleep time' do
expect(subject).to receive(:run_block_with_lock_timeout).and_raise(ActiveRecord::LockWaitTimeout).twice
expect(subject).to receive(:sleep).with(0.025)
- subject.run { }
+ subject.run {}
end
end
@@ -265,13 +265,13 @@ RSpec.describe Gitlab::Database::WithLockRetries do
it 'prevents running inside already open transaction' do
allow(connection).to receive(:transaction_open?).and_return(true)
- expect { subject.run { } }.to raise_error(/should not run inside already open transaction/)
+ expect { subject.run {} }.to raise_error(/should not run inside already open transaction/)
end
it 'does not raise the error if not inside open transaction' do
allow(connection).to receive(:transaction_open?).and_return(false)
- expect { subject.run { } }.not_to raise_error
+ expect { subject.run {} }.not_to raise_error
end
end
end
diff --git a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
index fdf16069381..1150de880b5 100644
--- a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
end
context 'if ID is missing' do
- let(:query_identifier) { }
+ let(:query_identifier) {}
it 'raises exception' do
expect { subject.execute }.to raise_error(Gitlab::DatabaseImporters::CommonMetrics::Importer::MissingQueryId)
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index 5350dda5fb2..1d1ffc8c275 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -115,6 +115,10 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
.once
.and_call_original
+ Gitlab::Redis::Cache.with do |redis|
+ expect(redis).to receive(:expire).with(cache.key, described_class::EXPIRATION)
+ end
+
2.times { cache.write_if_empty }
end
@@ -259,8 +263,12 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
describe '#key' do
subject { cache.key }
+ def options_hash(options_array)
+ OpenSSL::Digest::SHA256.hexdigest(options_array.join)
+ end
+
it 'returns cache key' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{cache.diff_options}:true:true")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, true, true])}")
end
context 'when the `use_marker_ranges` feature flag is disabled' do
@@ -269,7 +277,7 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
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:true")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, false, true])}")
end
end
@@ -279,7 +287,7 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
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")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, true, false])}")
end
end
end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 624160d2f48..c378ecb8134 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -117,7 +117,7 @@ RSpec.describe Gitlab::Diff::Highlight do
it 'reports to Sentry if configured' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).and_call_original
- expect { subject }. to raise_exception(RangeError)
+ expect { subject }.to raise_exception(RangeError)
end
end
diff --git a/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb b/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb
index 42ab2d1d063..ad92d90e253 100644
--- a/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb
+++ b/spec/lib/gitlab/diff/rendered/notebook/diff_file_helper_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFileHelper do
describe '#image_as_rich_text' do
let(:img) { 'data:image/png;base64,some_image_here' }
- let(:line_text) { " ![](#{img})"}
+ let(:line_text) { " ![](#{img})" }
subject { dummy.image_as_rich_text(line_text) }
diff --git a/spec/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512_spec.rb b/spec/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512_spec.rb
new file mode 100644
index 00000000000..e953733c997
--- /dev/null
+++ b/spec/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::DoorkeeperSecretStoring::Pbkdf2Sha512 do
+ describe '.transform_secret' do
+ let(:plaintext_token) { 'CzOBzBfU9F-HvsqfTaTXF4ivuuxYZuv3BoAK4pnvmyw' }
+
+ it 'generates a PBKDF2+SHA512 hashed value in the correct format' do
+ expect(described_class.transform_secret(plaintext_token))
+ .to eq("$pbkdf2-sha512$20000$$.c0G5XJVEew1TyeJk5TrkvB0VyOaTmDzPrsdNRED9vVeZlSyuG3G90F0ow23zUCiWKAVwmNnR/ceh.nJG3MdpQ") # rubocop:disable Layout/LineLength
+ end
+
+ context 'when hash_oauth_tokens is disabled' do
+ before do
+ stub_feature_flags(hash_oauth_tokens: false)
+ end
+
+ it 'returns a plaintext token' do
+ expect(described_class.transform_secret(plaintext_token)).to eq(plaintext_token)
+ end
+ end
+ end
+
+ describe 'STRETCHES' do
+ it 'is 20_000' do
+ expect(described_class::STRETCHES).to eq(20_000)
+ end
+ end
+
+ describe 'SALT' do
+ it 'is empty' do
+ expect(described_class::SALT).to be_empty
+ 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 9ff395070ea..585dce331ed 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
it_behaves_like :note_handler_shared_examples do
let(:recipient) { sent_notification.recipient }
- let(:update_commands_only) { fixture_file('emails/update_commands_only_reply.eml')}
+ let(:update_commands_only) { fixture_file('emails/update_commands_only_reply.eml') }
let(:no_content) { fixture_file('emails/no_content_reply.eml') }
let(:commands_in_reply) { fixture_file('emails/commands_in_reply.eml') }
let(:with_quick_actions) { fixture_file('emails/valid_reply_with_quick_actions.eml') }
@@ -54,7 +54,7 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
end
context 'with a secondary verified email address' do
- let(:verified_email) { 'alan@adventuretime.ooo'}
+ let(:verified_email) { 'alan@adventuretime.ooo' }
let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub('jake@adventuretime.ooo', verified_email) }
before do
diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
index d0aba70081b..08a7383700b 100644
--- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -493,11 +493,19 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
end
it 'does not create an issue' do
- expect { receiver.execute rescue nil }.not_to change { Issue.count }
+ expect do
+ receiver.execute
+ rescue StandardError
+ nil
+ end.not_to change { Issue.count }
end
it 'does not send thank you email' do
- expect { receiver.execute rescue nil }.not_to have_enqueued_job.on_queue('mailers')
+ expect do
+ receiver.execute
+ rescue StandardError
+ nil
+ end.not_to have_enqueued_job.on_queue('mailers')
end
end
@@ -532,7 +540,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
end
context 'service desk is disabled for the project' do
- let(:group) { create(:group)}
+ let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group, path: 'test', service_desk_enabled: false) }
it 'bounces the email' do
@@ -540,7 +548,11 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
end
it "doesn't create an issue" do
- expect { receiver.execute rescue nil }.not_to change { Issue.count }
+ expect do
+ receiver.execute
+ rescue StandardError
+ nil
+ end.not_to change { Issue.count }
end
end
end
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/admin_verify_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/admin_verify_spec.rb
index b5c3415fe12..7a09feb5b64 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing/admin_verify_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing/admin_verify_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::AdminVerify do
let(:series) { 0 }
- subject(:message) { described_class.new(group: group, user: user, series: series)}
+ subject(:message) { described_class.new(group: group, user: user, series: series) }
describe 'public methods' do
it 'returns value for series', :aggregate_failures do
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb
index 35470ef3555..d5aec280ea6 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing/create_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Create do
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
- subject(:message) { described_class.new(group: group, user: user, series: series)}
+ subject(:message) { described_class.new(group: group, user: user, series: series) }
describe "public methods" do
where(series: [0, 1, 2])
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/team_short_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/team_short_spec.rb
index daeacef53f6..3ac2076bf35 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing/team_short_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing/team_short_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::TeamShort do
let(:series) { 0 }
- subject(:message) { described_class.new(group: group, user: user, series: series)}
+ subject(:message) { described_class.new(group: group, user: user, series: series) }
describe 'public methods' do
it 'returns value for series', :aggregate_failures do
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb
index eca8ba1df00..3354b2ed5cf 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing/team_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Team do
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
- subject(:message) { described_class.new(group: group, user: user, series: series)}
+ subject(:message) { described_class.new(group: group, user: user, series: series) }
describe "public methods" do
where(series: [0, 1])
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/trial_short_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/trial_short_spec.rb
index ebad4672eb3..cf0a119ea80 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing/trial_short_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing/trial_short_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::TrialShort do
let(:series) { 0 }
- subject(:message) { described_class.new(group: group, user: user, series: series)}
+ subject(:message) { described_class.new(group: group, user: user, series: series) }
describe 'public methods' do
it 'returns value for series', :aggregate_failures do
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb
index 3e18b8e35b6..7f86c9a6c6f 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing/trial_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Trial do
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
- subject(:message) { described_class.new(group: group, user: user, series: series)}
+ subject(:message) { described_class.new(group: group, user: user, series: series) }
describe "public methods" do
where(series: [0, 1, 2])
diff --git a/spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb b/spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb
index a7da2e9553d..7e6f62289d2 100644
--- a/spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb
+++ b/spec/lib/gitlab/email/message/in_product_marketing/verify_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Verify do
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
- subject(:message) { described_class.new(group: group, user: user, series: series)}
+ subject(:message) { described_class.new(group: group, user: user, series: series) }
describe "public methods" do
context 'with series 0' do
diff --git a/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb b/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb
index 81e2a410962..bcd59c34ea2 100644
--- a/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb
+++ b/spec/lib/gitlab/error_tracking/error_repository/open_api_strategy_spec.rb
@@ -430,7 +430,7 @@ RSpec.describe Gitlab::ErrorTracking::ErrorRepository::OpenApiStrategy do
it do
is_expected
- .to eq("#{config.scheme}://#{public_key}@#{config.host}/errortracking/api/v1/projects/api/#{project.id}")
+ .to eq("#{config.scheme}://#{public_key}@#{config.host}/errortracking/api/v1/projects/#{project.id}")
end
end
end
diff --git a/spec/lib/gitlab/error_tracking/logger_spec.rb b/spec/lib/gitlab/error_tracking/logger_spec.rb
index 751ec10a1f0..1b722fc7896 100644
--- a/spec/lib/gitlab/error_tracking/logger_spec.rb
+++ b/spec/lib/gitlab/error_tracking/logger_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::ErrorTracking::Logger do
describe '.capture_exception' do
let(:exception) { RuntimeError.new('boom') }
let(:payload) { { foo: '123' } }
- let(:log_entry) { { message: 'boom', context: payload }}
+ let(:log_entry) { { message: 'boom', context: payload } }
it 'calls Gitlab::ErrorTracking::Logger.error with formatted log entry' do
expect_next_instance_of(Gitlab::ErrorTracking::LogFormatter) do |log_formatter|
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 d33f8393904..bc4526758c0 100644
--- a/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
+++ b/spec/lib/gitlab/error_tracking/processor/sidekiq_processor_spec.rb
@@ -159,13 +159,13 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor, :sentry do
context 'when processing via the default error handler' do
context 'with Raven events' do
- let(:event) { raven_event}
+ let(:event) { raven_event }
include_examples 'Sidekiq arguments', args_in_job_hash: true
end
context 'with Sentry events' do
- let(:event) { sentry_event}
+ let(:event) { sentry_event }
include_examples 'Sidekiq arguments', args_in_job_hash: true
end
@@ -173,13 +173,13 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor, :sentry do
context 'when processing via Gitlab::ErrorTracking' do
context 'with Raven events' do
- let(:event) { raven_event}
+ let(:event) { raven_event }
include_examples 'Sidekiq arguments', args_in_job_hash: false
end
context 'with Sentry events' do
- let(:event) { sentry_event}
+ let(:event) { sentry_event }
include_examples 'Sidekiq arguments', args_in_job_hash: false
end
@@ -209,13 +209,13 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor, :sentry do
end
context 'with Raven events' do
- let(:event) { raven_event}
+ let(:event) { raven_event }
it_behaves_like 'handles jobstr fields'
end
context 'with Sentry events' do
- let(:event) { sentry_event}
+ let(:event) { sentry_event }
it_behaves_like 'handles jobstr fields'
end
@@ -233,13 +233,13 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor, :sentry do
end
context 'with Raven events' do
- let(:event) { raven_event}
+ let(:event) { raven_event }
it_behaves_like 'does nothing'
end
context 'with Sentry events' do
- let(:event) { sentry_event}
+ let(:event) { sentry_event }
it_behaves_like 'does nothing'
end
@@ -256,13 +256,13 @@ RSpec.describe Gitlab::ErrorTracking::Processor::SidekiqProcessor, :sentry do
end
context 'with Raven events' do
- let(:event) { raven_event}
+ let(:event) { raven_event }
it_behaves_like 'does nothing'
end
context 'with Sentry events' do
- let(:event) { sentry_event}
+ let(:event) { sentry_event }
it_behaves_like 'does nothing'
end
diff --git a/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
index f74fbf1206f..1f30ac79488 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb
@@ -52,6 +52,28 @@ RSpec.describe Gitlab::ExclusiveLeaseHelpers::SleepingLock, :clean_gitlab_redis_
end
end
+ context 'when the lease is obtained already' do
+ let!(:lease) { stub_exclusive_lease_taken(key) }
+
+ context 'when retries are not specified' do
+ it 'retries to obtain a lease and raises an error' do
+ expect(lease).to receive(:try_obtain).exactly(10).times
+
+ expect { subject.obtain }.to raise_error('Failed to obtain a lock')
+ end
+ end
+
+ context 'when specified retries are above the maximum attempts' do
+ let(:max_attempts) { 100 }
+
+ it 'retries to obtain a lease and raises an error' do
+ expect(lease).to receive(:try_obtain).exactly(65).times
+
+ expect { subject.obtain(max_attempts) }.to raise_error('Failed to obtain a lock')
+ end
+ end
+ end
+
context 'when the lease is held elsewhere' do
let!(:lease) { stub_exclusive_lease_taken(key) }
let(:max_attempts) { 7 }
diff --git a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
index 8bf06bcebe2..f9db93a6167 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
@@ -9,12 +9,12 @@ RSpec.describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state d
let(:unique_key) { SecureRandom.hex(10) }
describe '#in_lock' do
- subject { class_instance.in_lock(unique_key, **options) { } }
+ subject { class_instance.in_lock(unique_key, **options) {} }
let(:options) { {} }
context 'when unique key is not set' do
- let(:unique_key) { }
+ let(:unique_key) {}
it 'raises an error' do
expect { subject }.to raise_error ArgumentError
diff --git a/spec/lib/gitlab/file_markdown_link_builder_spec.rb b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
index ea21bda12d3..d684beaaaca 100644
--- a/spec/lib/gitlab/file_markdown_link_builder_spec.rb
+++ b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::FileMarkdownLinkBuilder do
end
describe 'markdown_link' do
- let(:url) { "/uploads/#{filename}"}
+ let(:url) { "/uploads/#{filename}" }
before do
allow(custom_class).to receive(:secure_url).and_return(url)
diff --git a/spec/lib/gitlab/form_builders/gitlab_ui_form_builder_spec.rb b/spec/lib/gitlab/form_builders/gitlab_ui_form_builder_spec.rb
index 2b1fcac9257..98fb154fb05 100644
--- a/spec/lib/gitlab/form_builders/gitlab_ui_form_builder_spec.rb
+++ b/spec/lib/gitlab/form_builders/gitlab_ui_form_builder_spec.rb
@@ -9,6 +9,40 @@ RSpec.describe Gitlab::FormBuilders::GitlabUiFormBuilder do
let_it_be(:form_builder) { described_class.new(:user, user, fake_action_view_base, {}) }
+ describe '#submit' do
+ context 'without pajamas_button enabled' do
+ subject(:submit_html) do
+ form_builder.submit('Save', class: 'gl-button btn-confirm custom-class', data: { test: true })
+ end
+
+ it 'renders a submit input' do
+ expected_html = <<~EOS
+ <input type="submit" name="commit" value="Save" class="gl-button btn-confirm custom-class" data-test="true" data-disable-with="Save" />
+ EOS
+
+ expect(html_strip_whitespace(submit_html)).to eq(html_strip_whitespace(expected_html))
+ end
+ end
+
+ context 'with pajamas_button enabled' do
+ subject(:submit_html) do
+ form_builder.submit('Save', pajamas_button: true, class: 'custom-class', data: { test: true })
+ end
+
+ it 'renders a submit button' do
+ expected_html = <<~EOS
+ <button class="gl-button btn btn-md btn-confirm custom-class" data-test="true" type="submit">
+ <span class="gl-button-text">
+ Save
+ </span>
+ </button>
+ EOS
+
+ expect(html_strip_whitespace(submit_html)).to eq(html_strip_whitespace(expected_html))
+ end
+ end
+ end
+
describe '#gitlab_ui_checkbox_component' do
context 'when not using slots' do
let(:optional_args) { {} }
@@ -25,7 +59,7 @@ RSpec.describe Gitlab::FormBuilders::GitlabUiFormBuilder do
it 'renders correct html' do
expected_html = <<~EOS
<div class="gl-form-checkbox custom-control custom-checkbox">
- <input name="user[view_diffs_file_by_file]" type="hidden" value="0" />
+ <input name="user[view_diffs_file_by_file]" type="hidden" value="0" autocomplete="off" />
<input class="custom-control-input" type="checkbox" value="1" name="user[view_diffs_file_by_file]" id="user_view_diffs_file_by_file" />
<label class="custom-control-label" for="user_view_diffs_file_by_file">
<span>Show one file at a time on merge request&#39;s Changes tab</span>
@@ -51,7 +85,7 @@ RSpec.describe Gitlab::FormBuilders::GitlabUiFormBuilder do
it 'renders help text' do
expected_html = <<~EOS
<div class="gl-form-checkbox custom-control custom-checkbox">
- <input name="user[view_diffs_file_by_file]" type="hidden" value="1" />
+ <input name="user[view_diffs_file_by_file]" type="hidden" value="1" autocomplete="off" />
<input class="custom-control-input checkbox-foo-bar" type="checkbox" value="3" name="user[view_diffs_file_by_file]" id="user_view_diffs_file_by_file" />
<label class="custom-control-label label-foo-bar" for="user_view_diffs_file_by_file">
<span>Show one file at a time on merge request&#39;s Changes tab</span>
@@ -101,7 +135,7 @@ RSpec.describe Gitlab::FormBuilders::GitlabUiFormBuilder do
it 'renders correct html' do
expected_html = <<~EOS
<div class="gl-form-checkbox custom-control custom-checkbox">
- <input name="user[view_diffs_file_by_file]" type="hidden" value="0" />
+ <input name="user[view_diffs_file_by_file]" type="hidden" value="0" autocomplete="off" />
<input class="custom-control-input" type="checkbox" value="1" name="user[view_diffs_file_by_file]" id="user_view_diffs_file_by_file" />
<label class="custom-control-label" for="user_view_diffs_file_by_file">
<span>Show one file at a time on merge request&#39;s Changes tab</span>
@@ -195,6 +229,45 @@ RSpec.describe Gitlab::FormBuilders::GitlabUiFormBuilder do
end
end
+ describe '#gitlab_ui_datepicker' do
+ subject(:datepicker_html) do
+ form_builder.gitlab_ui_datepicker(
+ :expires_at,
+ **optional_args
+ )
+ end
+
+ let(:optional_args) { {} }
+
+ context 'without optional arguments' do
+ it 'renders correct html' do
+ expected_html = <<~EOS
+ <input class="datepicker form-control gl-form-input" type="text" name="user[expires_at]" id="user_expires_at" />
+ EOS
+
+ expect(html_strip_whitespace(datepicker_html)).to eq(html_strip_whitespace(expected_html))
+ end
+ end
+
+ context 'with optional arguments' do
+ let(:optional_args) do
+ {
+ id: 'milk_gone_bad',
+ data: { action: 'throw' },
+ value: '2022-08-01'
+ }
+ end
+
+ it 'renders correct html' do
+ expected_html = <<~EOS
+ <input id="milk_gone_bad" data-action="throw" value="2022-08-01" class="datepicker form-control gl-form-input" type="text" name="user[expires_at]" />
+ EOS
+
+ expect(html_strip_whitespace(datepicker_html)).to eq(html_strip_whitespace(expected_html))
+ end
+ end
+ end
+
private
def html_strip_whitespace(html)
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index e514e128785..45d88f57c09 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::Git::Blame do
it 'only returns the range' do
expect(result.size).to eq(range.size)
- expect(result.map {|r| r[:line] }).to eq(['', 'This guide details how contribute to GitLab.', ''])
+ expect(result.map { |r| r[:line] }).to eq(['', 'This guide details how contribute to GitLab.', ''])
end
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index fb4510a78de..0da7aa7dad0 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -50,7 +50,7 @@ RSpec.describe Gitlab::Git::Blob, :seed_helper do
end
context 'utf-8 branch' do
- let(:blob) { Gitlab::Git::Blob.find(repository, 'Ääh-test-utf-8', "files/ruby/popen.rb")}
+ let(:blob) { Gitlab::Git::Blob.find(repository, 'Ääh-test-utf-8', "files/ruby/popen.rb") }
it { expect(blob.id).to eq(SeedRepo::RubyBlob::ID) }
end
@@ -235,6 +235,7 @@ RSpec.describe Gitlab::Git::Blob, :seed_helper do
it { expect(blob.id).to eq('409f37c4f05865e4fb208c771485f211a22c4c2d') }
it { expect(blob.data).to eq('') }
+
it 'does not mark the blob as binary' do
expect(blob).not_to be_binary_in_repo
end
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 97cd4777b4d..feaa1f6595c 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -2,8 +2,9 @@
require "spec_helper"
-RSpec.describe Gitlab::Git::Branch, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
+RSpec.describe Gitlab::Git::Branch do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
subject { repository.branches }
@@ -54,14 +55,14 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
describe '#size' do
subject { super().size }
- it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
+ it { is_expected.to eq(TestEnv::BRANCH_SHA.size) }
end
describe 'first branch' do
let(:branch) { repository.branches.first }
- it { expect(branch.name).to eq(SeedRepo::Repo::BRANCHES.first) }
- it { expect(branch.dereferenced_target.sha).to eq("0b4bc9a49b562e85de7cc9e834518ea6828729b9") }
+ it { expect(branch.name).to eq(TestEnv::BRANCH_SHA.keys.min) }
+ it { expect(branch.dereferenced_target.sha).to start_with(TestEnv::BRANCH_SHA[TestEnv::BRANCH_SHA.keys.min]) }
end
describe 'master branch' do
@@ -69,14 +70,10 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
repository.branches.find { |branch| branch.name == 'master' }
end
- it { expect(branch.dereferenced_target.sha).to eq(SeedRepo::LastCommit::ID) }
+ it { expect(branch.dereferenced_target.sha).to start_with(TestEnv::BRANCH_SHA['master']) }
end
context 'with active, stale and future branches' do
- let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project')
- end
-
let(:user) { create(:user) }
let(:stale_sha) { travel_to(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago - 5.days) { create_commit } }
let(:active_sha) { travel_to(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago + 5.days) { create_commit } }
@@ -88,10 +85,6 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
repository.create_branch('future-1', future_sha)
end
- after do
- ensure_seeds
- end
-
describe 'examine if the branch is active or stale' do
let(:stale_branch) { repository.find_branch('stale-1') }
let(:active_branch) { repository.find_branch('active-1') }
@@ -117,8 +110,6 @@ RSpec.describe Gitlab::Git::Branch, :seed_helper do
end
end
- it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) }
-
def create_commit
repository.multi_action(
user,
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index da77d8ee5d6..95b49186d0f 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -222,6 +222,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
it 'has 10 elements' do
expect(subject.size).to eq(10)
end
+
it { is_expected.to include(SeedRepo::EmptyCommit::ID) }
end
@@ -240,6 +241,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
it 'has 10 elements' do
expect(subject.size).to eq(10)
end
+
it { is_expected.to include(SeedRepo::EmptyCommit::ID) }
end
@@ -259,6 +261,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
it 'has 3 elements' do
expect(subject.size).to eq(3)
end
+
it { is_expected.to include("d14d6c0abdd253381df51a723d58691b2ee1ab08") }
it { is_expected.not_to include("eb49186cfa5c4338011f5f590fac11bd66c5c631") }
end
@@ -279,6 +282,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
it 'has 3 elements' do
expect(subject.size).to eq(3)
end
+
it { is_expected.to include("2f63565e7aac07bcdadb654e253078b727143ec4") }
it { is_expected.not_to include(SeedRepo::Commit::ID) }
end
@@ -299,6 +303,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
it 'has 3 elements' do
expect(subject.size).to eq(3)
end
+
it { is_expected.to include("874797c3a73b60d2187ed6e2fcabd289ff75171e") }
it { is_expected.not_to include(SeedRepo::Commit::ID) }
end
@@ -570,13 +575,13 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
describe '#id' do
subject { super().id }
- it { is_expected.to eq(sample_commit_hash[:id])}
+ it { is_expected.to eq(sample_commit_hash[:id]) }
end
describe '#message' do
subject { super().message }
- it { is_expected.to eq(sample_commit_hash[:message])}
+ it { is_expected.to eq(sample_commit_hash[:message]) }
end
end
@@ -648,6 +653,7 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
it 'has 2 element' do
expect(subject.size).to eq(2)
end
+
it { is_expected.to include("master") }
it { is_expected.not_to include("feature") }
end
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 114b3d01952..0e3e92e03cf 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -520,7 +520,7 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#real_size' do
subject { super().real_size }
- it { is_expected.to eq('0')}
+ it { is_expected.to eq('0') }
end
describe '#line_count' do
@@ -595,7 +595,7 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
end
context 'multi-file collections' do
- let(:iterator) { [{ diff: 'b' }, { diff: 'a' * 20480 }]}
+ let(:iterator) { [{ diff: 'b' }, { diff: 'a' * 20480 }] }
it 'prunes diffs that are quite big' do
diff = nil
diff --git a/spec/lib/gitlab/git/raw_diff_change_spec.rb b/spec/lib/gitlab/git/raw_diff_change_spec.rb
index f894ae1d98b..c55fcc729b6 100644
--- a/spec/lib/gitlab/git/raw_diff_change_spec.rb
+++ b/spec/lib/gitlab/git/raw_diff_change_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Git::RawDiffChange do
- let(:raw_change) { }
+ let(:raw_change) {}
let(:change) { described_class.new(raw_change) }
context 'bad input' do
diff --git a/spec/lib/gitlab/git/remote_repository_spec.rb b/spec/lib/gitlab/git/remote_repository_spec.rb
deleted file mode 100644
index c7bc81573a6..00000000000
--- a/spec/lib/gitlab/git/remote_repository_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Git::RemoteRepository, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
-
- subject { described_class.new(repository) }
-
- describe '#empty?' do
- using RSpec::Parameterized::TableSyntax
-
- where(:repository, :result) do
- Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') | false
- Gitlab::Git::Repository.new('default', 'does-not-exist.git', '', 'group/project') | true
- end
-
- with_them do
- it { expect(subject.empty?).to eq(result) }
- end
- end
-
- describe '#commit_id' do
- it 'returns an OID if the revision exists' do
- expect(subject.commit_id('v1.0.0')).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
- end
-
- it 'is nil when the revision does not exist' do
- expect(subject.commit_id('does-not-exist')).to be_nil
- end
- end
-
- describe '#branch_exists?' do
- using RSpec::Parameterized::TableSyntax
-
- where(:branch, :result) do
- 'master' | true
- 'does-not-exist' | false
- end
-
- with_them do
- it { expect(subject.branch_exists?(branch)).to eq(result) }
- end
- end
-
- describe '#same_repository?' do
- using RSpec::Parameterized::TableSyntax
-
- where(:other_repository, :result) do
- repository | true
- Gitlab::Git::Repository.new(repository.storage, repository.relative_path, '', 'group/project') | true
- Gitlab::Git::Repository.new('broken', TEST_REPO_PATH, '', 'group/project') | false
- Gitlab::Git::Repository.new(repository.storage, 'wrong/relative-path.git', '', 'group/project') | false
- Gitlab::Git::Repository.new('broken', 'wrong/relative-path.git', '', 'group/project') | false
- end
-
- with_them do
- it { expect(subject.same_repository?(other_repository)).to eq(result) }
- end
- end
-end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index e20d5b928c4..a1fb8b70bd7 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1252,8 +1252,8 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
describe '#raw_changes_between' do
- let(:old_rev) { }
- let(:new_rev) { }
+ let(:old_rev) {}
+ let(:new_rev) {}
let(:changes) { repository.raw_changes_between(old_rev, new_rev) }
context 'initial commit' do
@@ -1837,6 +1837,47 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#find_tag' do
+ it 'returns a tag' do
+ tag = repository.find_tag('v1.0.0')
+
+ expect(tag).to be_a_kind_of(Gitlab::Git::Tag)
+ expect(tag.name).to eq('v1.0.0')
+ end
+
+ shared_examples 'a nonexistent tag' do
+ it 'returns nil' do
+ expect(repository.find_tag('this-is-garbage')).to be_nil
+ end
+ end
+
+ context 'when asking for a non-existent tag' do
+ it_behaves_like 'a nonexistent tag'
+ end
+
+ context 'when Gitaly returns Internal error' do
+ before do
+ expect(repository.gitaly_ref_client)
+ .to receive(:find_tag)
+ .and_raise(GRPC::Internal, "tag not found")
+ end
+
+ it_behaves_like 'a nonexistent tag'
+ end
+
+ context 'when Gitaly returns tag_not_found error' do
+ before do
+ expect(repository.gitaly_ref_client)
+ .to receive(:find_tag)
+ .and_raise(new_detailed_error(GRPC::Core::StatusCodes::NOT_FOUND,
+ "tag was not found",
+ Gitaly::FindTagError.new(tag_not_found: Gitaly::ReferenceNotFoundError.new)))
+ end
+
+ it_behaves_like 'a nonexistent tag'
+ end
+ end
+
describe '#languages' do
it 'returns exactly the expected results' do
languages = repository.languages('4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6')
@@ -2017,17 +2058,14 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
describe '#set_full_path' do
before do
- repository_rugged.config["gitlab.fullpath"] = repository_path
+ repository.set_full_path(full_path: repository_path)
end
context 'is given a path' do
it 'writes it to disk' do
repository.set_full_path(full_path: "not-the/real-path.git")
- config = File.read(File.join(repository_path, "config"))
-
- expect(config).to include("[gitlab]")
- expect(config).to include("fullpath = not-the/real-path.git")
+ expect(repository.full_path).to eq('not-the/real-path.git')
end
end
@@ -2035,15 +2073,12 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'does not write it to disk' do
repository.set_full_path(full_path: "")
- config = File.read(File.join(repository_path, "config"))
-
- expect(config).to include("[gitlab]")
- expect(config).to include("fullpath = #{repository_path}")
+ expect(repository.full_path).to eq(repository_path)
end
end
context 'repository does not exist' do
- it 'raises NoRepository and does not call Gitaly WriteConfig' do
+ it 'raises NoRepository and does not call SetFullPath' do
repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project')
expect(repository.gitaly_repository_client).not_to receive(:set_full_path)
@@ -2055,6 +2090,18 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#full_path' do
+ let(:full_path) { 'some/path' }
+
+ before do
+ repository.set_full_path(full_path: full_path)
+ end
+
+ it 'returns the full path' do
+ expect(repository.full_path).to eq(full_path)
+ end
+ end
+
describe '#merge_to_ref' do
let(:repository) { mutable_repository }
let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
@@ -2468,7 +2515,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
describe '#rename' do
- let(:project) { create(:project, :repository)}
+ let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
it 'moves the repository' do
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index b2603e099e6..03d1c125e36 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -58,35 +58,55 @@ RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
end
end
- context 'when not running puma with multiple threads' do
- before do
- allow(subject).to receive(:running_puma_with_multiple_threads?).and_return(false)
+ context 'when skip_rugged_auto_detect feature flag is enabled' do
+ context 'when not running puma with multiple threads' do
+ before do
+ allow(subject).to receive(:running_puma_with_multiple_threads?).and_return(false)
+ stub_feature_flags(feature_flag_name => nil)
+ stub_feature_flags(skip_rugged_auto_detect: true)
+ end
+
+ it 'returns false' do
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be false
+ end
end
+ end
- it 'returns true when gitaly matches disk' do
- expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+ context 'when skip_rugged_auto_detect feature flag is disabled' do
+ before do
+ stub_feature_flags(skip_rugged_auto_detect: false)
end
- it 'returns false when disk access fails' do
- allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return("/fake/path/doesnt/exist")
+ context 'when not running puma with multiple threads' do
+ before do
+ allow(subject).to receive(:running_puma_with_multiple_threads?).and_return(false)
+ end
- expect(subject.use_rugged?(repository, feature_flag_name)).to be false
- end
+ it 'returns true when gitaly matches disk' do
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+ end
- it "returns false when gitaly doesn't match disk" do
- allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return(temp_gitaly_metadata_file)
+ it 'returns false when disk access fails' do
+ allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return("/fake/path/doesnt/exist")
- expect(subject.use_rugged?(repository, feature_flag_name)).to be_falsey
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be false
+ end
- File.delete(temp_gitaly_metadata_file)
- end
+ it "returns false when gitaly doesn't match disk" do
+ allow(Gitlab::GitalyClient).to receive(:storage_metadata_file_path).and_return(temp_gitaly_metadata_file)
+
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be_falsey
- it "doesn't lead to a second rpc call because gitaly client should use the cached value" do
- expect(subject.use_rugged?(repository, feature_flag_name)).to be true
+ File.delete(temp_gitaly_metadata_file)
+ end
- expect(Gitlab::GitalyClient).not_to receive(:filesystem_id)
+ it "doesn't lead to a second rpc call because gitaly client should use the cached value" do
+ expect(subject.use_rugged?(repository, feature_flag_name)).to be true
- subject.use_rugged?(repository, feature_flag_name)
+ expect(Gitlab::GitalyClient).not_to receive(:filesystem_id)
+
+ subject.use_rugged?(repository, feature_flag_name)
+ end
end
end
end
@@ -165,7 +185,7 @@ RSpec.describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
context 'all features are enabled' do
let(:feature_keys) { [:feature_key_1, :feature_key_2] }
- it { is_expected.to be_truthy}
+ it { is_expected.to be_truthy }
end
context 'all features are not enabled' do
diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb
index 4f56595d7d2..240cf6ed46f 100644
--- a/spec/lib/gitlab/git/tag_spec.rb
+++ b/spec/lib/gitlab/git/tag_spec.rb
@@ -2,12 +2,13 @@
require "spec_helper"
-RSpec.describe Gitlab::Git::Tag, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
+RSpec.describe Gitlab::Git::Tag do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:repository) { project.repository.raw }
describe '#tags' do
- describe 'first tag' do
- let(:tag) { repository.tags.first }
+ describe 'unsigned tag' do
+ let(:tag) { repository.tags.detect { |t| t.name == 'v1.0.0' } }
it { expect(tag.name).to eq("v1.0.0") }
it { expect(tag.target).to eq("f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8") }
@@ -22,29 +23,13 @@ RSpec.describe Gitlab::Git::Tag, :seed_helper do
it { expect(tag.tagger.timezone).to eq("+0200") }
end
- describe 'last tag' do
- let(:tag) { repository.tags.last }
-
- it { expect(tag.name).to eq("v1.2.1") }
- it { expect(tag.target).to eq("2ac1f24e253e08135507d0830508febaaccf02ee") }
- it { expect(tag.dereferenced_target.sha).to eq("fa1b1e6c004a68b7d8763b86455da9e6b23e36d6") }
- it { expect(tag.message).to eq("Version 1.2.1") }
- it { expect(tag.has_signature?).to be_falsey }
- it { expect(tag.signature_type).to eq(:NONE) }
- it { expect(tag.signature).to be_nil }
- it { expect(tag.tagger.name).to eq("Douwe Maan") }
- it { expect(tag.tagger.email).to eq("douwe@selenight.nl") }
- it { expect(tag.tagger.date).to eq(Google::Protobuf::Timestamp.new(seconds: 1427789449)) }
- it { expect(tag.tagger.timezone).to eq("+0200") }
- end
-
describe 'signed tag' do
- let(:project) { create(:project, :repository) }
- let(:tag) { project.repository.find_tag('v1.1.1') }
+ let(:tag) { repository.tags.detect { |t| t.name == 'v1.1.1' } }
+ it { expect(tag.name).to eq("v1.1.1") }
it { expect(tag.target).to eq("8f03acbcd11c53d9c9468078f32a2622005a4841") }
it { expect(tag.dereferenced_target.sha).to eq("189a6c924013fc3fe40d6f1ec1dc20214183bc97") }
- it { expect(tag.message).to eq("x509 signed tag" + "\n" + X509Helpers::User1.signed_tag_signature.chomp) }
+ it { expect(tag.message).to eq("x509 signed tag\n" + X509Helpers::User1.signed_tag_signature.chomp) }
it { expect(tag.has_signature?).to be_truthy }
it { expect(tag.signature_type).to eq(:X509) }
it { expect(tag.signature).not_to be_nil }
@@ -54,11 +39,11 @@ RSpec.describe Gitlab::Git::Tag, :seed_helper do
it { expect(tag.tagger.timezone).to eq("+0100") }
end
- it { expect(repository.tags.size).to eq(SeedRepo::Repo::TAGS.size) }
+ it { expect(repository.tags.size).to be > 0 }
end
describe '.get_message' do
- let(:tag_ids) { %w[f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b] }
+ let(:tag_ids) { %w[f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 8f03acbcd11c53d9c9468078f32a2622005a4841] }
subject do
tag_ids.map { |id| described_class.get_message(repository, id) }
@@ -66,7 +51,7 @@ RSpec.describe Gitlab::Git::Tag, :seed_helper do
it 'gets tag messages' do
expect(subject[0]).to eq("Release\n")
- expect(subject[1]).to eq("Version 1.1.0\n")
+ expect(subject[1]).to eq("x509 signed tag\n" + X509Helpers::User1.signed_tag_signature)
end
it 'gets messages in one batch', :request_store do
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 172d7a3f27b..b520de03929 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -2,10 +2,11 @@
require "spec_helper"
-RSpec.describe Gitlab::Git::Tree, :seed_helper do
+RSpec.describe Gitlab::Git::Tree do
let_it_be(:user) { create(:user) }
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
shared_examples :repo do
subject(:tree) { Gitlab::Git::Tree.where(repository, sha, path, recursive, pagination_params) }
@@ -105,10 +106,6 @@ RSpec.describe Gitlab::Git::Tree, :seed_helper do
).newrev
end
- after do
- ensure_seeds
- end
-
it { expect(subdir_file.flat_path).to eq('files/flat/path/correct') }
end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 5ee9cf05b3e..8577cad1011 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GitAccess do
+RSpec.describe Gitlab::GitAccess, :aggregate_failures do
include TermsHelper
include GitHelpers
include AdminModeHelper
@@ -78,9 +78,7 @@ RSpec.describe Gitlab::GitAccess do
let(:auth_result_type) { :ci }
it "doesn't block http pull" do
- aggregate_failures do
- expect { pull_access_check }.not_to raise_error
- end
+ expect { pull_access_check }.not_to raise_error
end
end
end
@@ -153,6 +151,15 @@ RSpec.describe Gitlab::GitAccess do
it 'does not block pushes with "not found"' do
expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload])
end
+
+ it 'logs' do
+ expect(Gitlab::AppJsonLogger).to receive(:info).with(
+ message: 'Actor was :ci',
+ project_id: project.id
+ ).once
+
+ pull_access_check
+ end
end
context 'when actor is DeployToken' do
@@ -229,9 +236,9 @@ RSpec.describe Gitlab::GitAccess do
end
context 'key is expired' do
- let(:actor) { create(:rsa_key_2048, :expired) }
+ let(:actor) { create(:deploy_key, :expired) }
- it 'does not allow expired keys', :aggregate_failures do
+ it 'does not allow expired keys' do
expect { pull_access_check }.to raise_forbidden('Your SSH key has expired.')
expect { push_access_check }.to raise_forbidden('Your SSH key has expired.')
end
@@ -242,7 +249,7 @@ RSpec.describe Gitlab::GitAccess do
stub_application_setting(rsa_key_restriction: 4096)
end
- it 'does not allow keys which are too small', :aggregate_failures do
+ it 'does not allow keys which are too small' do
expect(actor).not_to be_valid
expect { pull_access_check }.to raise_forbidden('Your SSH key must be at least 4096 bits.')
expect { push_access_check }.to raise_forbidden('Your SSH key must be at least 4096 bits.')
@@ -254,7 +261,7 @@ RSpec.describe Gitlab::GitAccess do
stub_application_setting(rsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE)
end
- it 'does not allow keys which are too small', :aggregate_failures do
+ it 'does not allow keys which are too small' do
expect(actor).not_to be_valid
expect { pull_access_check }.to raise_forbidden(/Your SSH key type is forbidden/)
expect { push_access_check }.to raise_forbidden(/Your SSH key type is forbidden/)
@@ -263,7 +270,7 @@ RSpec.describe Gitlab::GitAccess do
end
it_behaves_like '#check with a key that is not valid' do
- let(:actor) { build(:rsa_key_2048, user: user) }
+ let(:actor) { build(:deploy_key, user: user) }
end
it_behaves_like '#check with a key that is not valid' do
@@ -736,6 +743,15 @@ RSpec.describe Gitlab::GitAccess do
context 'pull code' do
it { expect { pull_access_check }.not_to raise_error }
+
+ it 'logs' do
+ expect(Gitlab::AppJsonLogger).to receive(:info).with(
+ message: 'Actor was :ci',
+ project_id: project.id
+ ).once
+
+ pull_access_check
+ end
end
end
end
@@ -1163,13 +1179,13 @@ RSpec.describe Gitlab::GitAccess do
-> { push_access_check }]
end
- it 'blocks access when the user did not accept terms', :aggregate_failures do
+ it 'blocks access when the user did not accept terms' do
actions.each do |action|
expect { action.call }.to raise_forbidden(/must accept the Terms of Service in order to perform this action/)
end
end
- it 'allows access when the user accepted the terms', :aggregate_failures do
+ it 'allows access when the user accepted the terms' do
accept_terms(user)
actions.each do |action|
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index 784d25f55c1..f359679a930 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -54,6 +54,7 @@ RSpec.describe Gitlab::Git do
with_them do
it { expect(described_class.shas_eql?(sha1, sha2)).to eq(result) }
+
it 'is commutative' do
expect(described_class.shas_eql?(sha2, sha1)).to eq(result)
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index d5d1bef7bff..0d591fe6c43 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -340,7 +340,7 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
describe '#list_new_commits' do
let(:revisions) { [revision] }
let(:gitaly_commits) { create_list(:gitaly_commit, 3) }
- let(:expected_commits) { gitaly_commits.map { |c| Gitlab::Git::Commit.new(repository, c) }}
+ let(:expected_commits) { gitaly_commits.map { |c| Gitlab::Git::Commit.new(repository, c) } }
subject do
client.list_new_commits(revisions)
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index e04895d975f..5d854f0c9d1 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -84,37 +84,6 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
subject
end
- describe '#user_merge_to_ref' do
- let(:first_parent_ref) { 'refs/heads/my-branch' }
- let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
- let(:ref) { 'refs/merge-requests/x/merge' }
- let(:message) { 'validación' }
- let(:response) { Gitaly::UserMergeToRefResponse.new(commit_id: 'new-commit-id') }
-
- 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
- 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
-
context "when pre_receive_error is present" do
let(:response) do
Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "GitLab: something failed")
@@ -131,6 +100,37 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
end
end
+ describe '#user_merge_to_ref' do
+ let(:first_parent_ref) { 'refs/heads/my-branch' }
+ let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
+ let(:ref) { 'refs/merge-requests/x/merge' }
+ let(:message) { 'validación' }
+ let(:response) { Gitaly::UserMergeToRefResponse.new(commit_id: 'new-commit-id') }
+
+ 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
+ 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
+
describe '#user_delete_branch' do
let(:branch_name) { 'my-branch' }
let(:request) do
@@ -551,7 +551,7 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
end
let(:expected_error) { Gitlab::Git::Repository::CreateTreeError }
- let(:expected_error_message) { }
+ let(:expected_error_message) {}
it_behaves_like '#user_cherry_pick with a gRPC error'
end
@@ -559,7 +559,7 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
context 'when a non-detailed gRPC error is raised' do
let(:raised_error) { GRPC::Internal.new('non-detailed error') }
let(:expected_error) { GRPC::Internal }
- let(:expected_error_message) { }
+ let(:expected_error_message) {}
it_behaves_like '#user_cherry_pick with a gRPC error'
end
@@ -813,4 +813,146 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
end
end
end
+
+ describe '#add_tag' do
+ let(:tag_name) { 'some-tag' }
+ let(:tag_message) { nil }
+ let(:target) { 'master' }
+
+ subject(:add_tag) do
+ client.add_tag(tag_name, user, target, tag_message)
+ end
+
+ context 'without tag message' do
+ let(:tag_name) { 'lightweight-tag' }
+
+ it 'creates a lightweight tag' do
+ tag = add_tag
+ expect(tag.name).to eq(tag_name)
+ expect(tag.message).to eq('')
+ end
+ end
+
+ context 'with tag message' do
+ let(:tag_name) { 'annotated-tag' }
+ let(:tag_message) { "tag message" }
+
+ it 'creates an annotated tag' do
+ tag = add_tag
+ expect(tag.name).to eq(tag_name)
+ expect(tag.message).to eq(tag_message)
+ end
+ end
+
+ context 'with preexisting tag' do
+ let(:tag_name) { 'v1.0.0' }
+
+ it 'raises a TagExistsError' do
+ expect { add_tag }.to raise_error(Gitlab::Git::Repository::TagExistsError)
+ end
+ end
+
+ context 'with invalid target' do
+ let(:target) { 'refs/heads/does-not-exist' }
+
+ it 'raises an InvalidRef error' do
+ expect { add_tag }.to raise_error(Gitlab::Git::Repository::InvalidRef)
+ end
+ end
+
+ context 'with pre-receive error' do
+ before do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_create_tag)
+ .and_return(Gitaly::UserCreateTagResponse.new(pre_receive_error: "GitLab: something failed"))
+ end
+
+ it 'raises a PreReceiveError' do
+ expect { add_tag }.to raise_error(Gitlab::Git::PreReceiveError, "something failed")
+ end
+ end
+
+ context 'with internal error' do
+ before do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_create_tag)
+ .and_raise(GRPC::Internal.new('undetailed internal error'))
+ end
+
+ it 'raises an Internal error' do
+ expect { add_tag }.to raise_error do |error|
+ expect(error).to be_a(GRPC::Internal)
+ expect(error.details).to eq('undetailed internal error')
+ end
+ end
+ end
+
+ context 'with structured errors' do
+ before do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_create_tag)
+ .and_raise(structured_error)
+ end
+
+ context 'with ReferenceExistsError' do
+ let(:structured_error) do
+ new_detailed_error(
+ GRPC::Core::StatusCodes::ALREADY_EXISTS,
+ 'tag exists already',
+ Gitaly::UserCreateTagError.new(
+ reference_exists: Gitaly::ReferenceExistsError.new(
+ reference_name: tag_name,
+ oid: 'something'
+ )))
+ end
+
+ it 'raises a TagExistsError' do
+ expect { add_tag }.to raise_error(Gitlab::Git::Repository::TagExistsError)
+ end
+ end
+
+ context 'with AccessCheckError' do
+ let(:structured_error) do
+ new_detailed_error(
+ GRPC::Core::StatusCodes::PERMISSION_DENIED,
+ "error creating tag",
+ Gitaly::UserCreateTagError.new(
+ access_check: Gitaly::AccessCheckError.new(
+ error_message: "You are not allowed to create this tag.",
+ protocol: "web",
+ user_id: "user-15",
+ changes: "df15b32277d2c55c6c595845a87109b09c913c556 5d6e0f935ad9240655f64e883cd98fad6f9a17ee refs/tags/v1.0.0\n"
+ )))
+ end
+
+ it 'raises a PreReceiveError' do
+ expect { add_tag }.to raise_error do |error|
+ expect(error).to be_a(Gitlab::Git::PreReceiveError)
+ expect(error.message).to eq("You are not allowed to create this tag.")
+ end
+ end
+ end
+
+ context 'with CustomHookError' do
+ let(:structured_error) do
+ new_detailed_error(
+ GRPC::Core::StatusCodes::PERMISSION_DENIED,
+ "custom hook error",
+ Gitaly::UserCreateTagError.new(
+ custom_hook: Gitaly::CustomHookError.new(
+ stdout: "some stdout",
+ stderr: "GitLab: some custom hook error message",
+ hook_type: Gitaly::CustomHookError::HookType::HOOK_TYPE_PRERECEIVE
+ )))
+ end
+
+ it 'raises a PreReceiveError' do
+ expect { add_tag }.to raise_error do |error|
+ expect(error).to be_a(Gitlab::Git::PreReceiveError)
+ expect(error.message).to eq("some custom hook error message")
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index 566bdbacf4a..277276bb1d3 100644
--- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -120,6 +120,28 @@ RSpec.describe Gitlab::GitalyClient::RefService do
expect(client.find_tag('')).to be_nil
end
end
+
+ context 'when Gitaly returns an Internal error' do
+ it 'raises an Internal error' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:find_tag)
+ .and_raise(GRPC::Internal.new('something went wrong'))
+
+ expect { client.find_tag('v1.0.0') }.to raise_error(GRPC::Internal)
+ end
+ end
+
+ context 'when Gitaly returns a tag_not_found error' do
+ it 'raises an UnknownRef error' do
+ expect_any_instance_of(Gitaly::RefService::Stub)
+ .to receive(:find_tag)
+ .and_raise(new_detailed_error(GRPC::Core::StatusCodes::NOT_FOUND,
+ "tag was not found",
+ Gitaly::FindTagError.new(tag_not_found: Gitaly::ReferenceNotFoundError.new)))
+
+ expect { client.find_tag('v1.0.0') }.to raise_error(Gitlab::Git::UnknownRef, 'tag does not exist: v1.0.0')
+ end
+ end
end
describe '#default_branch_name' do
@@ -286,7 +308,7 @@ RSpec.describe Gitlab::GitalyClient::RefService do
end
context 'with a invalid format error' do
- let(:invalid_refs) {['\invali.\d/1', '\.invali/d/2']}
+ let(:invalid_refs) { ['\invali.\d/1', '\.invali/d/2'] }
let(:invalid_reference_format_error) do
new_detailed_error(
GRPC::Core::StatusCodes::INVALID_ARGUMENT,
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 39de9a65390..63d32cb906f 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -276,32 +276,12 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
end
describe '#disconnect_alternates' do
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository }
- let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) }
- let(:pool_repository) { create(:pool_repository) }
- let(:object_pool) { pool_repository.object_pool }
- let(:object_pool_service) { Gitlab::GitalyClient::ObjectPoolService.new(object_pool) }
-
- before do
- object_pool_service.create(repository) # rubocop:disable Rails/SaveBang
- object_pool_service.link_repository(repository)
- end
-
- it 'deletes the alternates file' do
- repository.disconnect_alternates
-
- alternates_file = File.join(repository_path, "objects", "info", "alternates")
+ it 'sends a disconnect_git_alternates message' do
+ expect_any_instance_of(Gitaly::ObjectPoolService::Stub)
+ .to receive(:disconnect_git_alternates)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
- expect(File.exist?(alternates_file)).to be_falsey
- end
-
- context 'when called twice' do
- it "doesn't raise an error" do
- repository.disconnect_alternates
-
- expect { repository.disconnect_alternates }.not_to raise_error
- end
+ client.disconnect_alternates
end
end
@@ -351,4 +331,16 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
client.set_full_path(path)
end
end
+
+ describe '#full_path' do
+ let(:path) { 'repo/path' }
+
+ it 'sends a full_path message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:full_path)
+ .and_return(double(path: path))
+
+ expect(client.full_path).to eq(path)
+ end
+ end
end
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index c4d05e92633..2bd3910ad87 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -208,7 +208,7 @@ RSpec.describe Gitlab::GithubImport::Client do
expect(client).to receive(:requests_remaining?).and_return(true)
- client.with_rate_limit { }
+ client.with_rate_limit {}
end
it 'ignores rate limiting when disabled' do
diff --git a/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb b/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
new file mode 100644
index 00000000000..41fe5fbdbbd
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::BaseImporter do
+ let(:project) { instance_double('Project') }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:issue_event) { instance_double('Gitlab::GithubImport::Representation::IssueEvent') }
+ let(:importer_class) { Class.new(described_class) }
+ let(:importer_instance) { importer_class.new(project, client) }
+
+ describe '#execute' do
+ it { expect { importer_instance.execute(issue_event) }.to raise_error(NotImplementedError) }
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
new file mode 100644
index 00000000000..2f6f727dc38
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee do
+ subject(:importer) { described_class.new(project, client) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:assignee) { create(:user) }
+ let_it_be(:assigner) { create(:user) }
+
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:issue) { create(:issue, project: project) }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'event' => event_type,
+ 'commit_id' => nil,
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'assigner' => { 'id' => assigner.id, 'login' => assigner.username },
+ 'assignee' => { 'id' => assignee.id, 'login' => assignee.username },
+ 'issue' => { 'number' => issue.iid }
+ )
+ end
+
+ let(:note_attrs) do
+ {
+ noteable_id: issue.id,
+ noteable_type: Issue.name,
+ project_id: project.id,
+ author_id: assigner.id,
+ system: true,
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ let(:expected_system_note_metadata_attrs) do
+ {
+ action: "assignee",
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ shared_examples 'new note' do
+ it 'creates expected note' do
+ expect { importer.execute(issue_event) }.to change { issue.notes.count }
+ .from(0).to(1)
+
+ expect(issue.notes.last)
+ .to have_attributes(expected_note_attrs)
+ end
+
+ it 'creates expected system note metadata' do
+ expect { importer.execute(issue_event) }.to change { SystemNoteMetadata.count }
+ .from(0).to(1)
+
+ expect(SystemNoteMetadata.last)
+ .to have_attributes(
+ expected_system_note_metadata_attrs.merge(
+ note_id: Note.last.id
+ )
+ )
+ end
+ end
+
+ describe '#execute' do
+ before do
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(assignee.id, assignee.username).and_return(assignee.id)
+ allow(finder).to receive(:find).with(assigner.id, assigner.username).and_return(assigner.id)
+ end
+ end
+
+ context 'when importing an assigned event' do
+ let(:event_type) { 'assigned' }
+ let(:expected_note_attrs) { note_attrs.merge(note: "assigned to @#{assignee.username}") }
+
+ it_behaves_like 'new note'
+ end
+
+ context 'when importing an unassigned event' do
+ let(:event_type) { 'unassigned' }
+ let(:expected_note_attrs) { note_attrs.merge(note: "unassigned @#{assigner.username}") }
+
+ it_behaves_like 'new note'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
index b773598853d..e21672aa430 100644
--- a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
@@ -3,23 +3,25 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:issue) { create(:issue, project: project) }
let!(:label) { create(:label, project: project) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => event_type,
'commit_id' => nil,
'label_title' => label.title,
'issue_db_id' => issue.id,
- 'created_at' => '2022-04-26 18:30:53 UTC'
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -43,6 +45,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
context 'when importing a labeled event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
new file mode 100644
index 00000000000..2687627fc23
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone do
+ subject(:importer) { described_class.new(project, client) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:issue) { create(:issue, project: project) }
+ let!(:milestone) { create(:milestone, project: project) }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'actor' => { 'id' => user.id, 'login' => user.username },
+ 'event' => event_type,
+ 'commit_id' => nil,
+ 'milestone_title' => milestone.title,
+ 'issue_db_id' => issue.id,
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'issue' => { 'number' => issue.iid }
+ )
+ end
+
+ let(:event_attrs) do
+ {
+ user_id: user.id,
+ issue_id: issue.id,
+ milestone_id: milestone.id,
+ state: 'opened',
+ created_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ shared_examples 'new event' do
+ it 'creates a new milestone event' do
+ expect { importer.execute(issue_event) }.to change { issue.resource_milestone_events.count }
+ .from(0).to(1)
+ expect(issue.resource_milestone_events.last)
+ .to have_attributes(expected_event_attrs)
+ end
+ end
+
+ describe '#execute' do
+ before do
+ allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(milestone.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
+ end
+
+ context 'when importing a milestoned event' do
+ let(:event_type) { 'milestoned' }
+ let(:expected_event_attrs) { event_attrs.merge(action: 'add') }
+
+ it_behaves_like 'new event'
+ end
+
+ context 'when importing demilestoned event' do
+ let(:event_type) { 'demilestoned' }
+ let(:expected_event_attrs) { event_attrs.merge(action: 'remove') }
+
+ it_behaves_like 'new event'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
index 116917d3e06..9a49d80a8bb 100644
--- a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
@@ -3,11 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:issue) { create(:issue, project: project) }
let(:commit_id) { nil }
@@ -16,11 +17,11 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'closed',
'created_at' => '2022-04-26 18:30:53 UTC',
'commit_id' => commit_id,
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -45,6 +46,15 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
}.stringify_keys
end
+ before do
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
+ end
+
it 'creates expected event and state event' do
importer.execute(issue_event)
diff --git a/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
index 118c482a7d9..68e001c7364 100644
--- a/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
@@ -3,15 +3,16 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_gitlab_redis_cache do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:sawyer_stub) { Struct.new(:iid, :issuable_type, keyword_init: true) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:issue) { create(:issue, project: project) }
- let(:referenced_in) { build_stubbed(:issue, project: project) }
+ let(:issue_iid) { 999 }
+ let(:issue) { create(:issue, project: project, iid: issue_iid) }
+ let(:referenced_in) { build_stubbed(:issue, project: project, iid: issue_iid + 1) }
let(:commit_id) { nil }
let(:issue_event) do
@@ -19,7 +20,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'cross-referenced',
'source' => {
'type' => 'issue',
@@ -29,7 +30,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
}
},
'created_at' => '2022-04-26 18:30:53 UTC',
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -38,7 +39,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
{
system: true,
noteable_type: Issue.name,
- noteable_id: issue_event.issue_db_id,
+ noteable_id: issue.id,
project_id: project.id,
author_id: user.id,
note: expected_note_body,
@@ -47,12 +48,16 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
end
context 'when referenced in other issue' do
- let(:expected_note_body) { "mentioned in issue ##{issue.iid}" }
+ let(:expected_note_body) { "mentioned in issue ##{referenced_in.iid}" }
before do
- other_issue_resource = sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'Issue')
- Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
- .cache_database_id(referenced_in.iid)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(referenced_in.iid)
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected note' do
@@ -71,10 +76,13 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
let(:expected_note_body) { "mentioned in merge request !#{referenced_in.iid}" }
before do
- other_issue_resource =
- sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'MergeRequest')
- Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
- .cache_database_id(referenced_in.iid)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(referenced_in.iid)
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected note' do
@@ -87,7 +95,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
end
context 'when referenced in out of project issue/pull_request' do
- it 'creates expected note' do
+ it 'does not create expected note' do
importer.execute(issue_event)
expect(issue.notes.count).to eq 0
diff --git a/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
index a8c3fbcb05d..316ea798965 100644
--- a/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
@@ -3,23 +3,24 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'renamed',
'commit_id' => nil,
'created_at' => '2022-04-26 18:30:53 UTC',
'old_title' => 'old title',
'new_title' => 'new title',
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -45,6 +46,15 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
end
describe '#execute' do
+ before do
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
+ end
+
it 'creates expected note' do
expect { importer.execute(issue_event) }.to change { issue.notes.count }
.from(0).to(1)
diff --git a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
index 81653b0ecdc..2461dbb9701 100644
--- a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
@@ -3,11 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_failures do
- subject(:importer) { described_class.new(project, user.id) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:issue) { create(:issue, project: project) }
let(:issue_event) do
@@ -15,10 +16,10 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail
'id' => 6501124486,
'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
- 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'reopened',
'created_at' => '2022-04-26 18:30:53 UTC',
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -42,6 +43,15 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail
}.stringify_keys
end
+ before do
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
+ end
+
it 'creates expected event and state event' do
importer.execute(issue_event)
diff --git a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
index da32a3b3766..33d5fbf13a0 100644
--- a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles
expect(importer_class)
- .to receive(:new).with(project, user.id)
+ .to receive(:new).with(project, client)
.and_return(specific_importer)
expect(specific_importer).to receive(:execute).with(issue_event)
@@ -43,12 +43,6 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
describe '#execute' do
before do
- allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
- allow(finder).to receive(:author_id_for)
- .with(issue_event, author_key: :actor)
- .and_return(user.id, true)
- end
-
issue_event.attributes[:issue_db_id] = issue.id
end
@@ -87,6 +81,20 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
Gitlab::GithubImport::Importer::Events::Renamed
end
+ context "when it's milestoned issue event" do
+ let(:event_name) { 'milestoned' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::ChangedMilestone
+ end
+
+ context "when it's demilestoned issue event" do
+ let(:event_name) { 'demilestoned' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::ChangedMilestone
+ end
+
context "when it's cross-referenced issue event" do
let(:event_name) { 'cross-referenced' }
@@ -94,6 +102,20 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
Gitlab::GithubImport::Importer::Events::CrossReferenced
end
+ context "when it's assigned issue event" do
+ let(:event_name) { 'assigned' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::ChangedAssignee
+ end
+
+ context "when it's unassigned issue event" do
+ let(:event_name) { 'unassigned' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::ChangedAssignee
+ end
+
context "when it's unknown issue event" do
let(:event_name) { 'fake' }
diff --git a/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb
new file mode 100644
index 00000000000..8d4c1b01e50
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::IssueEventsImporter do
+ subject(:importer) { described_class.new(project, client, parallel: parallel) }
+
+ let(:project) { instance_double(Project, id: 4, import_source: 'foo/bar') }
+ let(:client) { instance_double(Gitlab::GithubImport::Client) }
+
+ let(:parallel) { true }
+ let(:issue_event) do
+ struct = Struct.new(
+ :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label, :rename, :milestone,
+ :source, :assignee, :assigner, :issue, :created_at, :performed_via_github_app,
+ keyword_init: true
+ )
+ struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
+ end
+
+ describe '#parallel?' do
+ context 'when running in parallel mode' do
+ it { expect(importer).to be_parallel }
+ end
+
+ context 'when running in sequential mode' do
+ let(:parallel) { false }
+
+ it { expect(importer).not_to be_parallel }
+ end
+ end
+
+ describe '#execute' do
+ context 'when running in parallel mode' do
+ it 'imports events in parallel' do
+ expect(importer).to receive(:parallel_import)
+
+ importer.execute
+ end
+ end
+
+ context 'when running in sequential mode' do
+ let(:parallel) { false }
+
+ it 'imports notes in sequence' do
+ expect(importer).to receive(:sequential_import)
+
+ importer.execute
+ end
+ end
+ end
+
+ describe '#sequential_import' do
+ let(:parallel) { false }
+
+ it 'imports each event in sequence' do
+ event_importer = instance_double(Gitlab::GithubImport::Importer::IssueEventImporter)
+
+ allow(importer).to receive(:each_object_to_import).and_yield(issue_event)
+
+ expect(Gitlab::GithubImport::Importer::IssueEventImporter)
+ .to receive(:new)
+ .with(
+ an_instance_of(Gitlab::GithubImport::Representation::IssueEvent),
+ project,
+ client
+ )
+ .and_return(event_importer)
+
+ expect(event_importer).to receive(:execute)
+
+ importer.sequential_import
+ end
+ end
+
+ describe '#parallel_import' do
+ it 'imports each note in parallel' do
+ allow(importer).to receive(:each_object_to_import).and_yield(issue_event)
+
+ expect(Gitlab::GithubImport::ImportIssueEventWorker).to receive(:bulk_perform_in).with(
+ 1.second, [
+ [project.id, an_instance_of(Hash), an_instance_of(String)]
+ ], batch_size: 1000, batch_delay: 1.minute
+ )
+
+ waiter = importer.parallel_import
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(1)
+ end
+ end
+
+ describe '#importer_class' do
+ it { expect(importer.importer_class).to eq Gitlab::GithubImport::Importer::IssueEventImporter }
+ end
+
+ describe '#representation_class' do
+ it { expect(importer.representation_class).to eq Gitlab::GithubImport::Representation::IssueEvent }
+ end
+
+ describe '#sidekiq_worker_class' do
+ it { expect(importer.sidekiq_worker_class).to eq Gitlab::GithubImport::ImportIssueEventWorker }
+ end
+
+ describe '#object_type' do
+ it { expect(importer.object_type).to eq :issue_event }
+ end
+
+ describe '#collection_method' do
+ it { expect(importer.collection_method).to eq :repository_issue_events }
+ end
+
+ describe '#id_for_already_imported_cache' do
+ it 'returns the ID of the given note' do
+ expect(importer.id_for_already_imported_cache(issue_event)).to eq(issue_event.id)
+ end
+ end
+
+ describe '#collection_options' do
+ it { expect(importer.collection_options).to eq({}) }
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
index 570d26cdf2d..1692aac49f2 100644
--- a/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_importer_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redis_cache do
+ let_it_be(:work_item_type_id) { ::WorkItems::Type.default_issue_type.id }
+
let(:project) { create(:project) }
let(:client) { double(:client) }
let(:user) { create(:user) }
@@ -25,7 +27,8 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
author: Gitlab::GithubImport::Representation::User.new(id: 4, login: 'alice'),
created_at: created_at,
updated_at: updated_at,
- pull_request: false
+ pull_request: false,
+ work_item_type_id: work_item_type_id
)
end
@@ -116,6 +119,17 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
.and_return(milestone.id)
end
+ it 'creates issues with a work item type id' do
+ allow(importer.user_finder)
+ .to receive(:author_id_for)
+ .with(issue)
+ .and_return([user.id, true])
+
+ issue_id = importer.create_issue
+
+ expect(Issue.find(issue_id).work_item_type_id).to eq(work_item_type_id)
+ end
+
context 'when the issue author could be found' do
it 'creates the issue with the found author as the issue author' do
allow(importer.user_finder)
@@ -136,7 +150,8 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
milestone_id: milestone.id,
state_id: 1,
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ work_item_type_id: work_item_type_id
},
project.issues
)
@@ -166,7 +181,8 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueImporter, :clean_gitlab_redi
milestone_id: milestone.id,
state_id: 1,
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ work_item_type_id: work_item_type_id
},
project.issues
)
diff --git a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
index 6dfd4424342..251829b83a0 100644
--- a/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/lfs_objects_importer_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Gitlab::GithubImport::Importer::LfsObjectsImporter do
let(:lfs_attributes) do
{
- oid: 'oid',
+ oid: 'a' * 64,
size: 1,
link: 'http://www.gitlab.com/lfs_objects/oid'
}
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
index c1b0f4df29a..c5846fa7a87 100644
--- a/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_requests_importer_spec.rb
@@ -149,7 +149,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsImporter do
expect(importer)
.to receive(:update_repository)
- importer.each_object_to_import { }
+ importer.each_object_to_import {}
end
end
diff --git a/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
index 087faeffe02..bb1ee79ad93 100644
--- a/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
@@ -53,7 +53,7 @@ RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
describe '#each_object_to_import', :clean_gitlab_redis_cache do
let(:issue_event) do
- struct = Struct.new(:id, :event, :created_at, :issue_db_id, keyword_init: true)
+ struct = Struct.new(:id, :event, :created_at, :issue, keyword_init: true)
struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
end
@@ -81,7 +81,7 @@ RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
counter = 0
subject.each_object_to_import do |object|
expect(object).to eq issue_event
- expect(issue_event.issue_db_id).to eq issue.id
+ expect(issue_event.issue['number']).to eq issue.iid
counter += 1
end
expect(counter).to eq 1
diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
index 3afd006109b..d550f15e8c5 100644
--- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do
let(:project) { double(:project, id: 4, group: nil) }
let(:issue) do
- double(:issue, issuable_type: MergeRequest, iid: 1)
+ double(:issue, issuable_type: MergeRequest, issuable_id: 1)
end
let(:finder) { described_class.new(project, issue) }
diff --git a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
index 999f8ffb21e..738e7c88d7d 100644
--- a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
@@ -243,7 +243,7 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do
expect(repr_class)
.to receive(:from_api_response)
- .with(object)
+ .with(object, {})
.and_return(repr_instance)
expect(importer)
@@ -281,7 +281,7 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do
allow(repr_class)
.to receive(:from_api_response)
- .with(object)
+ .with(object, {})
.and_return({ title: 'Foo' })
end
diff --git a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
index 23da8276f64..d3a98035e73 100644
--- a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
@@ -25,8 +25,8 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
expect(issue_event.source).to eq({ type: 'issue', id: 123456 })
end
- it 'includes the issue_db_id' do
- expect(issue_event.issue_db_id).to eq(100500)
+ it 'includes the issue data' do
+ expect(issue_event.issue).to eq({ number: 2, pull_request: pull_request })
end
context 'when actor data present' do
@@ -77,11 +77,66 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
end
end
+ context 'when milestone data is present' do
+ it 'includes the milestone_title' do
+ expect(issue_event.milestone_title).to eq('milestone title')
+ end
+ end
+
+ context 'when milestone data is empty' do
+ let(:with_milestone) { false }
+
+ it 'does not return such info' do
+ expect(issue_event.milestone_title).to eq nil
+ end
+ end
+
+ context 'when assignee and assigner data is present' do
+ it 'includes assignee and assigner details' do
+ expect(issue_event.assignee)
+ .to be_an_instance_of(Gitlab::GithubImport::Representation::User)
+ expect(issue_event.assignee.id).to eq(5)
+ expect(issue_event.assignee.login).to eq('tom')
+
+ expect(issue_event.assigner)
+ .to be_an_instance_of(Gitlab::GithubImport::Representation::User)
+ expect(issue_event.assigner.id).to eq(6)
+ expect(issue_event.assigner.login).to eq('jerry')
+ end
+ end
+
+ context 'when assignee and assigner data is empty' do
+ let(:with_assignee) { false }
+
+ it 'does not return such info' do
+ expect(issue_event.assignee).to eq nil
+ expect(issue_event.assigner).to eq nil
+ end
+ end
+
it 'includes the created timestamp' do
expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC')
end
end
+ describe '#issuable_id' do
+ it 'returns issuable_id' do
+ expect(issue_event.issuable_id).to eq(2)
+ end
+ end
+
+ describe '#issuable_type' do
+ context 'when event related to issue' do
+ it { expect(issue_event.issuable_type).to eq('Issue') }
+ end
+
+ context 'when event related to pull request' do
+ let(:pull_request) { { url: FFaker::Internet.http_url } }
+
+ it { expect(issue_event.issuable_type).to eq('MergeRequest') }
+ end
+ end
+
describe '#github_identifiers' do
it 'returns a hash with needed identifiers' do
expect(issue_event.github_identifiers).to eq({ id: 6501124486 })
@@ -92,8 +147,8 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
describe '.from_api_response' do
let(:response) do
event_resource = Struct.new(
- :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label,
- :rename, :issue_db_id, :created_at, :performed_via_github_app, :source,
+ :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label, :rename, :milestone,
+ :source, :assignee, :assigner, :issue, :created_at, :performed_via_github_app,
keyword_init: true
)
user_resource = Struct.new(:id, :login, keyword_init: true)
@@ -106,10 +161,13 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\
'/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ label: with_label ? { name: 'label title' } : nil,
rename: with_rename ? { from: 'old title', to: 'new title' } : nil,
+ milestone: with_milestone ? { title: 'milestone title' } : nil,
source: { type: 'issue', id: 123456 },
- issue_db_id: 100500,
- label: with_label ? { name: 'label title' } : nil,
+ assignee: with_assignee ? user_resource.new(id: 5, login: 'tom') : nil,
+ assigner: with_assignee ? user_resource.new(id: 6, login: 'jerry') : nil,
+ issue: { 'number' => 2, 'pull_request' => pull_request },
created_at: '2022-04-26 18:30:53 UTC',
performed_via_github_app: nil
)
@@ -118,6 +176,9 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_actor) { true }
let(:with_label) { true }
let(:with_rename) { true }
+ let(:with_milestone) { true }
+ let(:with_assignee) { true }
+ let(:pull_request) { nil }
it_behaves_like 'an IssueEvent' do
let(:issue_event) { described_class.from_api_response(response) }
@@ -139,8 +200,11 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
'label_title' => (with_label ? 'label title' : nil),
'old_title' => with_rename ? 'old title' : nil,
'new_title' => with_rename ? 'new title' : nil,
+ 'milestone_title' => (with_milestone ? 'milestone title' : nil),
'source' => { 'type' => 'issue', 'id' => 123456 },
- "issue_db_id" => 100500,
+ 'assignee' => (with_assignee ? { 'id' => 5, 'login' => 'tom' } : nil),
+ 'assigner' => (with_assignee ? { 'id' => 6, 'login' => 'jerry' } : nil),
+ 'issue' => { 'number' => 2, 'pull_request' => pull_request },
'created_at' => '2022-04-26 18:30:53 UTC',
'performed_via_github_app' => nil
}
@@ -149,6 +213,9 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_actor) { true }
let(:with_label) { true }
let(:with_rename) { true }
+ let(:with_milestone) { true }
+ let(:with_assignee) { true }
+ let(:pull_request) { nil }
let(:issue_event) { described_class.from_json_hash(hash) }
end
diff --git a/spec/lib/gitlab/github_import/representation/issue_spec.rb b/spec/lib/gitlab/github_import/representation/issue_spec.rb
index f3052efea70..5898518343a 100644
--- a/spec/lib/gitlab/github_import/representation/issue_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/issue_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Representation::Issue do
+ let_it_be(:work_item_type_id) { ::WorkItems::Type.default_issue_type.id }
+
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
@@ -60,6 +62,10 @@ RSpec.describe Gitlab::GithubImport::Representation::Issue do
expect(issue.updated_at).to eq(updated_at)
end
+ it 'includes the work_item_type_id' do
+ expect(issue.work_item_type_id).to eq(work_item_type_id)
+ end
+
it 'is not a pull request' do
expect(issue.pull_request?).to eq(false)
end
@@ -84,8 +90,10 @@ RSpec.describe Gitlab::GithubImport::Representation::Issue do
)
end
+ let(:additional_data) { { work_item_type_id: work_item_type_id } }
+
it_behaves_like 'an Issue' do
- let(:issue) { described_class.from_api_response(response) }
+ let(:issue) { described_class.from_api_response(response, additional_data) }
end
it 'does not set the user if the response did not include a user' do
@@ -93,7 +101,7 @@ RSpec.describe Gitlab::GithubImport::Representation::Issue do
.to receive(:user)
.and_return(nil)
- issue = described_class.from_api_response(response)
+ issue = described_class.from_api_response(response, additional_data)
expect(issue.author).to be_nil
end
@@ -113,7 +121,8 @@ RSpec.describe Gitlab::GithubImport::Representation::Issue do
'author' => { 'id' => 4, 'login' => 'alice' },
'created_at' => created_at.to_s,
'updated_at' => updated_at.to_s,
- 'pull_request' => false
+ 'pull_request' => false,
+ 'work_item_type_id' => work_item_type_id
}
end
diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb
index 8eb6eedd72d..d85e298785c 100644
--- a/spec/lib/gitlab/github_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/user_finder_spec.rb
@@ -15,32 +15,64 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
let(:finder) { described_class.new(project, client) }
describe '#author_id_for' do
- it 'returns the user ID for the author of an object' do
- user = double(:user, id: 4, login: 'kittens')
- note = double(:note, author: user)
+ context 'with default author_key' do
+ it 'returns the user ID for the author of an object' do
+ user = double(:user, id: 4, login: 'kittens')
+ note = double(:note, author: user)
- expect(finder).to receive(:user_id_for).with(user).and_return(42)
+ expect(finder).to receive(:user_id_for).with(user).and_return(42)
- expect(finder.author_id_for(note)).to eq([42, true])
- end
+ expect(finder.author_id_for(note)).to eq([42, true])
+ end
- it 'returns the ID of the project creator if no user ID could be found' do
- user = double(:user, id: 4, login: 'kittens')
- note = double(:note, author: user)
+ it 'returns the ID of the project creator if no user ID could be found' do
+ user = double(:user, id: 4, login: 'kittens')
+ note = double(:note, author: user)
- expect(finder).to receive(:user_id_for).with(user).and_return(nil)
+ expect(finder).to receive(:user_id_for).with(user).and_return(nil)
- expect(finder.author_id_for(note)).to eq([project.creator_id, false])
- end
+ expect(finder.author_id_for(note)).to eq([project.creator_id, false])
+ end
+
+ it 'returns the ID of the ghost user when the object has no user' do
+ note = double(:note, author: nil)
- it 'returns the ID of the ghost user when the object has no user' do
- note = double(:note, author: nil)
+ expect(finder.author_id_for(note)).to eq([User.ghost.id, true])
+ end
- expect(finder.author_id_for(note)).to eq([User.ghost.id, true])
+ it 'returns the ID of the ghost user when the given object is nil' do
+ expect(finder.author_id_for(nil)).to eq([User.ghost.id, true])
+ end
end
- it 'returns the ID of the ghost user when the given object is nil' do
- expect(finder.author_id_for(nil)).to eq([User.ghost.id, true])
+ context 'with a non-default author_key' do
+ let(:user) { double(:user, id: 4, login: 'kittens') }
+
+ shared_examples 'user ID finder' do |author_key|
+ it 'returns the user ID for an object' do
+ expect(finder).to receive(:user_id_for).with(user).and_return(42)
+
+ expect(finder.author_id_for(issue_event, author_key: author_key)).to eq([42, true])
+ end
+ end
+
+ context 'when the author_key parameter is :actor' do
+ let(:issue_event) { double('Gitlab::GithubImport::Representation::IssueEvent', actor: user) }
+
+ it_behaves_like 'user ID finder', :actor
+ end
+
+ context 'when the author_key parameter is :assignee' do
+ let(:issue_event) { double('Gitlab::GithubImport::Representation::IssueEvent', assignee: user) }
+
+ it_behaves_like 'user ID finder', :assignee
+ end
+
+ context 'when the author_key parameter is :assigner' do
+ let(:issue_event) { double('Gitlab::GithubImport::Representation::IssueEvent', assigner: user) }
+
+ it_behaves_like 'user ID finder', :assigner
+ end
end
end
diff --git a/spec/lib/gitlab/global_id/deprecations_spec.rb b/spec/lib/gitlab/global_id/deprecations_spec.rb
index 22a4766c0a0..3824473c95b 100644
--- a/spec/lib/gitlab/global_id/deprecations_spec.rb
+++ b/spec/lib/gitlab/global_id/deprecations_spec.rb
@@ -1,12 +1,21 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
+require 'graphql'
+require_relative '../../../../app/graphql/types/base_scalar'
+require_relative '../../../../app/graphql/types/global_id_type'
+require_relative '../../../support/helpers/global_id_deprecation_helpers'
RSpec.describe Gitlab::GlobalId::Deprecations do
include GlobalIDDeprecationHelpers
- let_it_be(:deprecation_1) { described_class::Deprecation.new(old_model_name: 'Foo::Model', new_model_name: 'Bar', milestone: '9.0') }
- let_it_be(:deprecation_2) { described_class::Deprecation.new(old_model_name: 'Baz', new_model_name: 'Qux::Model', milestone: '10.0') }
+ let(:deprecation_1) do
+ described_class::NameDeprecation.new(old_name: 'Foo::Model', new_name: 'Bar', milestone: '9.0')
+ end
+
+ let(:deprecation_2) do
+ described_class::NameDeprecation.new(old_name: 'Baz', new_name: 'Qux::Model', milestone: '10.0')
+ end
before do
stub_global_id_deprecations(deprecation_1, deprecation_2)
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index 72c6c8efb5e..e64555f1079 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -218,7 +218,7 @@ RSpec.describe Gitlab::Gpg do
expect(Retriable).to receive(:sleep).at_least(:twice)
expect(FileUtils).to receive(:remove_entry).with(tmp_dir).at_least(:twice).and_raise('Deletion failed')
- expect { described_class.using_tmp_keychain { } }.to raise_error(described_class::CleanupError)
+ expect { described_class.using_tmp_keychain {} }.to raise_error(described_class::CleanupError)
end
it 'does not attempt multiple times when the deletion succeeds' do
@@ -226,7 +226,7 @@ RSpec.describe Gitlab::Gpg do
expect(FileUtils).to receive(:remove_entry).with(tmp_dir).once.and_raise('Deletion failed')
expect(FileUtils).to receive(:remove_entry).with(tmp_dir).and_call_original
- expect { described_class.using_tmp_keychain { } }.not_to raise_error
+ expect { described_class.using_tmp_keychain {} }.not_to raise_error
expect(File.exist?(tmp_dir)).to be false
end
diff --git a/spec/lib/gitlab/grape_logging/loggers/token_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/token_logger_spec.rb
new file mode 100644
index 00000000000..d2022a28a90
--- /dev/null
+++ b/spec/lib/gitlab/grape_logging/loggers/token_logger_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GrapeLogging::Loggers::TokenLogger do
+ subject { described_class.new }
+
+ describe ".parameters" do
+ let(:token_id) { 1 }
+ let(:token_type) { "PersonalAccessToken" }
+
+ describe 'when no token information is available' do
+ let(:mock_request) { instance_double(ActionDispatch::Request, 'env', env: {}) }
+
+ it 'returns an empty hash' do
+ expect(subject.parameters(mock_request, nil)).to eq({})
+ end
+ end
+
+ describe 'when token information is available' do
+ let(:mock_request) do
+ instance_double(ActionDispatch::Request, 'env',
+ env: {
+ 'gitlab.api.token' => { 'token_id': token_id, 'token_type': token_type }
+ }
+ )
+ end
+
+ it 'adds the token information to log parameters' do
+ expect(subject.parameters(mock_request, nil)).to eq( { 'token_id': 1, 'token_type': "PersonalAccessToken" })
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/deprecation_spec.rb b/spec/lib/gitlab/graphql/deprecation_spec.rb
index 2931e28a6ee..c9b47219198 100644
--- a/spec/lib/gitlab/graphql/deprecation_spec.rb
+++ b/spec/lib/gitlab/graphql/deprecation_spec.rb
@@ -6,30 +6,57 @@ require 'active_model'
RSpec.describe ::Gitlab::Graphql::Deprecation do
let(:options) { {} }
- subject(:deprecation) { described_class.parse(options) }
+ subject(:deprecation) { described_class.new(**options) }
describe '.parse' do
- context 'with nil' do
- let(:options) { nil }
+ subject(:parsed_deprecation) { described_class.parse(**options) }
- it 'parses to nil' do
- expect(deprecation).to be_nil
+ context 'with no arguments' do
+ it 'returns nil' do
+ expect(parsed_deprecation).to be_nil
end
end
- context 'with empty options' do
- let(:options) { {} }
+ context 'with an incomplete `deprecated` argument' do
+ let(:options) { { deprecated: {} } }
- it 'parses to an empty deprecation' do
- expect(deprecation).to eq(described_class.new)
+ it 'parses as an invalid deprecation' do
+ expect(parsed_deprecation).not_to be_valid
+ expect(parsed_deprecation).to eq(described_class.new)
end
end
- context 'with defined options' do
- let(:options) { { reason: :renamed, milestone: '10.10' } }
+ context 'with a `deprecated` argument' do
+ let(:options) { { deprecated: { reason: :renamed, milestone: '10.10' } } }
+
+ it 'parses as a deprecation' do
+ expect(parsed_deprecation).to be_valid
+ expect(parsed_deprecation).to eq(
+ described_class.new(reason: 'This was renamed', milestone: '10.10')
+ )
+ end
+ end
+
+ context 'with an `alpha` argument' do
+ let(:options) { { alpha: { milestone: '10.10' } } }
+
+ it 'parses as an alpha' do
+ expect(parsed_deprecation).to be_valid
+ expect(parsed_deprecation).to eq(
+ described_class.new(reason: :alpha, milestone: '10.10')
+ )
+ end
+ end
+
+ context 'with both `deprecated` and `alpha` arguments' do
+ let(:options) do
+ { alpha: { milestone: '10.10' }, deprecated: { reason: :renamed, milestone: '10.10' } }
+ end
- it 'assigns the properties' do
- expect(deprecation).to eq(described_class.new(reason: 'This was renamed', milestone: '10.10'))
+ it 'raises an error' do
+ expect { parsed_deprecation }.to raise_error(ArgumentError,
+ '`alpha` and `deprecated` arguments cannot be passed at the same time'
+ )
end
end
end
@@ -210,4 +237,20 @@ RSpec.describe ::Gitlab::Graphql::Deprecation do
end
end
end
+
+ describe '#alpha?' do
+ let(:options) { { milestone: '10.10', reason: reason } }
+
+ context 'when `reason` is `:alpha`' do
+ let(:reason) { described_class::REASON_ALPHA }
+
+ it { is_expected.to be_alpha }
+ end
+
+ context 'when `reason` is not `:alpha`' do
+ let(:reason) { described_class::REASON_RENAMED }
+
+ it { is_expected.not_to be_alpha }
+ end
+ end
end
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb
deleted file mode 100644
index eecdaa3409f..00000000000
--- a/spec/lib/gitlab/graphql/pagination/keyset/conditions/not_null_condition_spec.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Graphql::Pagination::Keyset::Conditions::NotNullCondition do
- describe '#build' do
- let(:operators) { ['>', '>'] }
- let(:before_or_after) { :after }
- let(:condition) { described_class.new(arel_table, order_list, values, operators, before_or_after) }
-
- context 'when there is only one ordering field' do
- let(:arel_table) { Issue.arel_table }
- let(:order_list) { [double(named_function: nil, attribute_name: 'id')] }
- let(:values) { [500] }
- let(:operators) { ['>'] }
-
- it 'generates a single condition sql' do
- expected_sql = <<~SQL
- ("issues"."id" > 500)
- SQL
-
- expect(condition.build.squish).to eq expected_sql.squish
- end
- end
-
- context 'when ordering by a column attribute' do
- let(:arel_table) { Issue.arel_table }
- let(:order_list) { [double(named_function: nil, attribute_name: 'relative_position'), double(named_function: nil, attribute_name: 'id')] }
- let(:values) { [1500, 500] }
-
- shared_examples ':after condition' do
- it 'generates :after sql' do
- expected_sql = <<~SQL
- ("issues"."relative_position" > 1500)
- OR (
- "issues"."relative_position" = 1500
- AND
- "issues"."id" > 500
- )
- OR ("issues"."relative_position" IS NULL)
- SQL
-
- expect(condition.build.squish).to eq expected_sql.squish
- end
- end
-
- context 'when :after' do
- it_behaves_like ':after condition'
- end
-
- context 'when :before' do
- let(:before_or_after) { :before }
-
- it 'generates :before sql' do
- expected_sql = <<~SQL
- ("issues"."relative_position" > 1500)
- OR (
- "issues"."relative_position" = 1500
- AND
- "issues"."id" > 500
- )
- SQL
-
- expect(condition.build.squish).to eq expected_sql.squish
- end
- end
-
- context 'when :foo' do
- let(:before_or_after) { :foo }
-
- it_behaves_like ':after condition'
- end
- end
-
- context 'when ordering by LOWER' do
- let(:arel_table) { Project.arel_table }
- let(:relation) { Project.order(arel_table['name'].lower.asc).order(:id) }
- let(:order_list) { Gitlab::Graphql::Pagination::Keyset::OrderInfo.build_order_list(relation) }
- let(:values) { ['Test', 500] }
-
- context 'when :after' do
- it 'generates :after sql' do
- expected_sql = <<~SQL
- (LOWER("projects"."name") > 'test')
- OR (
- LOWER("projects"."name") = 'test'
- AND
- "projects"."id" > 500
- )
- OR (LOWER("projects"."name") IS NULL)
- SQL
-
- expect(condition.build.squish).to eq expected_sql.squish
- end
- end
-
- context 'when :before' do
- let(:before_or_after) { :before }
-
- it 'generates :before sql' do
- expected_sql = <<~SQL
- (LOWER("projects"."name") > 'test')
- OR (
- LOWER("projects"."name") = 'test'
- AND
- "projects"."id" > 500
- )
- SQL
-
- expect(condition.build.squish).to eq expected_sql.squish
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb
deleted file mode 100644
index 582f96299ec..00000000000
--- a/spec/lib/gitlab/graphql/pagination/keyset/conditions/null_condition_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Graphql::Pagination::Keyset::Conditions::NullCondition do
- describe '#build' do
- let(:values) { [nil, 500] }
- let(:operators) { [nil, '>'] }
- let(:before_or_after) { :after }
- let(:condition) { described_class.new(arel_table, order_list, values, operators, before_or_after) }
-
- context 'when ordering by a column attribute' do
- let(:arel_table) { Issue.arel_table }
- let(:order_list) { [double(named_function: nil, attribute_name: 'relative_position'), double(named_function: nil, attribute_name: 'id')] }
-
- shared_examples ':after condition' do
- it 'generates sql' do
- expected_sql = <<~SQL
- (
- "issues"."relative_position" IS NULL
- AND
- "issues"."id" > 500
- )
- SQL
-
- expect(condition.build.squish).to eq expected_sql.squish
- end
- end
-
- context 'when :after' do
- it_behaves_like ':after condition'
- end
-
- context 'when :before' do
- let(:before_or_after) { :before }
-
- it 'generates :before sql' do
- expected_sql = <<~SQL
- (
- "issues"."relative_position" IS NULL
- AND
- "issues"."id" > 500
- )
- OR ("issues"."relative_position" IS NOT NULL)
- SQL
-
- expect(condition.build.squish).to eq expected_sql.squish
- end
- end
-
- context 'when :foo' do
- let(:before_or_after) { :foo }
-
- it_behaves_like ':after condition'
- end
- end
-
- context 'when ordering by LOWER' do
- let(:arel_table) { Project.arel_table }
- let(:relation) { Project.order(arel_table['name'].lower.asc).order(:id) }
- let(:order_list) { Gitlab::Graphql::Pagination::Keyset::OrderInfo.build_order_list(relation) }
-
- context 'when :after' do
- it 'generates sql' do
- expected_sql = <<~SQL
- (
- LOWER("projects"."name") IS NULL
- AND
- "projects"."id" > 500
- )
- SQL
-
- expect(condition.build.squish).to eq expected_sql.squish
- end
- end
-
- context 'when :before' do
- let(:before_or_after) { :before }
-
- it 'generates :before sql' do
- expected_sql = <<~SQL
- (
- LOWER("projects"."name") IS NULL
- AND
- "projects"."id" > 500
- )
- OR (LOWER("projects"."name") IS NOT NULL)
- SQL
-
- expect(condition.build.squish).to eq expected_sql.squish
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb
deleted file mode 100644
index 8a2b5ae0d38..00000000000
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_generic_keyset_spec.rb
+++ /dev/null
@@ -1,415 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
- include GraphqlHelpers
-
- # https://gitlab.com/gitlab-org/gitlab/-/issues/334973
- # The spec will be merged with connection_spec.rb in the future.
- let(:nodes) { Project.all.order(id: :asc) }
- let(:arguments) { {} }
- let(:context) { GraphQL::Query::Context.new(query: query_double, values: nil, object: nil) }
-
- let_it_be(:column_order_id) { Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(attribute_name: 'id', order_expression: Project.arel_table[:id].asc) }
- let_it_be(:column_order_id_desc) { Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(attribute_name: 'id', order_expression: Project.arel_table[:id].desc) }
- let_it_be(:column_order_updated_at) { Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(attribute_name: 'updated_at', order_expression: Project.arel_table[:updated_at].asc) }
- let_it_be(:column_order_created_at) { Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(attribute_name: 'created_at', order_expression: Project.arel_table[:created_at].asc) }
- let_it_be(:column_order_last_repo) do
- Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
- attribute_name: 'last_repository_check_at',
- column_expression: Project.arel_table[:last_repository_check_at],
- order_expression: Project.arel_table[:last_repository_check_at].asc.nulls_last,
- reversed_order_expression: Project.arel_table[:last_repository_check_at].desc.nulls_last,
- order_direction: :asc,
- nullable: :nulls_last,
- distinct: false)
- end
-
- let_it_be(:column_order_last_repo_desc) do
- Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
- attribute_name: 'last_repository_check_at',
- column_expression: Project.arel_table[:last_repository_check_at],
- order_expression: Project.arel_table[:last_repository_check_at].desc.nulls_last,
- reversed_order_expression: Project.arel_table[:last_repository_check_at].asc.nulls_last,
- order_direction: :desc,
- nullable: :nulls_last,
- distinct: false)
- end
-
- subject(:connection) do
- described_class.new(nodes, **{ context: context, max_page_size: 3 }.merge(arguments))
- end
-
- def encoded_cursor(node)
- described_class.new(nodes, context: context).cursor_for(node)
- end
-
- def decoded_cursor(cursor)
- Gitlab::Json.parse(Base64Bp.urlsafe_decode64(cursor))
- end
-
- describe "With generic keyset order support" do
- let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_id])) }
-
- it_behaves_like 'a connection with collection methods'
-
- it_behaves_like 'a redactable connection' do
- let_it_be(:projects) { create_list(:project, 2) }
- let(:unwanted) { projects.second }
- end
-
- describe '#cursor_for' do
- let(:project) { create(:project) }
- let(:cursor) { connection.cursor_for(project) }
-
- it 'returns an encoded ID' do
- expect(decoded_cursor(cursor)).to eq('id' => project.id.to_s)
- end
-
- context 'when an order is specified' do
- let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_id])) }
-
- it 'returns the encoded value of the order' do
- expect(decoded_cursor(cursor)).to include('id' => project.id.to_s)
- end
- end
-
- context 'when multiple orders are specified' do
- let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_updated_at, column_order_created_at, column_order_id])) }
-
- it 'returns the encoded value of the order' do
- expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect))
- end
- end
- end
-
- describe '#sliced_nodes' do
- let(:projects) { create_list(:project, 4) }
-
- context 'when before is passed' do
- let(:arguments) { { before: encoded_cursor(projects[1]) } }
-
- it 'only returns the project before the selected one' do
- expect(subject.sliced_nodes).to contain_exactly(projects.first)
- end
-
- context 'when the sort order is descending' do
- let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_id_desc])) }
-
- it 'returns the correct nodes' do
- expect(subject.sliced_nodes).to contain_exactly(*projects[2..])
- end
- end
- end
-
- context 'when after is passed' do
- let(:arguments) { { after: encoded_cursor(projects[1]) } }
-
- it 'only returns the project before the selected one' do
- expect(subject.sliced_nodes).to contain_exactly(*projects[2..])
- end
-
- context 'when the sort order is descending' do
- let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_id_desc])) }
-
- it 'returns the correct nodes' do
- expect(subject.sliced_nodes).to contain_exactly(projects.first)
- end
- end
- end
-
- context 'when both before and after are passed' do
- let(:arguments) do
- {
- after: encoded_cursor(projects[1]),
- before: encoded_cursor(projects[3])
- }
- end
-
- it 'returns the expected set' do
- expect(subject.sliced_nodes).to contain_exactly(projects[2])
- end
- end
-
- shared_examples 'nodes are in ascending order' do
- context 'when no cursor is passed' do
- let(:arguments) { {} }
-
- it 'returns projects in ascending order' do
- expect(subject.sliced_nodes).to eq(ascending_nodes)
- end
- end
-
- context 'when before cursor value is not NULL' do
- let(:arguments) { { before: encoded_cursor(ascending_nodes[2]) } }
-
- it 'returns all projects before the cursor' do
- expect(subject.sliced_nodes).to eq(ascending_nodes.first(2))
- end
- end
-
- context 'when after cursor value is not NULL' do
- let(:arguments) { { after: encoded_cursor(ascending_nodes[1]) } }
-
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq(ascending_nodes.last(3))
- end
- end
-
- context 'when before and after cursor' do
- let(:arguments) { { before: encoded_cursor(ascending_nodes.last), after: encoded_cursor(ascending_nodes.first) } }
-
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq(ascending_nodes[1..3])
- end
- end
- end
-
- shared_examples 'nodes are in descending order' do
- context 'when no cursor is passed' do
- let(:arguments) { {} }
-
- it 'only returns projects in descending order' do
- expect(subject.sliced_nodes).to eq(descending_nodes)
- end
- end
-
- context 'when before cursor value is not NULL' do
- let(:arguments) { { before: encoded_cursor(descending_nodes[2]) } }
-
- it 'returns all projects before the cursor' do
- expect(subject.sliced_nodes).to eq(descending_nodes.first(2))
- end
- end
-
- context 'when after cursor value is not NULL' do
- let(:arguments) { { after: encoded_cursor(descending_nodes[1]) } }
-
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq(descending_nodes.last(3))
- end
- end
-
- context 'when before and after cursor' do
- let(:arguments) { { before: encoded_cursor(descending_nodes.last), after: encoded_cursor(descending_nodes.first) } }
-
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq(descending_nodes[1..3])
- end
- end
- end
-
- context 'when multiple orders with nil values are defined' do
- let_it_be(:project1) { create(:project, last_repository_check_at: 10.days.ago) } # Asc: project5 Desc: project3
- let_it_be(:project2) { create(:project, last_repository_check_at: nil) } # Asc: project1 Desc: project1
- let_it_be(:project3) { create(:project, last_repository_check_at: 5.days.ago) } # Asc: project3 Desc: project5
- let_it_be(:project4) { create(:project, last_repository_check_at: nil) } # Asc: project2 Desc: project2
- let_it_be(:project5) { create(:project, last_repository_check_at: 20.days.ago) } # Asc: project4 Desc: project4
-
- context 'when ascending' do
- let_it_be(:order) { Gitlab::Pagination::Keyset::Order.build([column_order_last_repo, column_order_id]) }
- let_it_be(:nodes) { Project.order(order) }
- let_it_be(:ascending_nodes) { [project5, project1, project3, project2, project4] }
-
- it_behaves_like 'nodes are in ascending order'
-
- context 'when before cursor value is NULL' do
- let(:arguments) { { before: encoded_cursor(project4) } }
-
- it 'returns all projects before the cursor' do
- expect(subject.sliced_nodes).to eq([project5, project1, project3, project2])
- end
- end
-
- context 'when after cursor value is NULL' do
- let(:arguments) { { after: encoded_cursor(project2) } }
-
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq([project4])
- end
- end
- end
-
- context 'when descending' do
- let_it_be(:order) { Gitlab::Pagination::Keyset::Order.build([column_order_last_repo_desc, column_order_id]) }
- let_it_be(:nodes) { Project.order(order) }
- let_it_be(:descending_nodes) { [project3, project1, project5, project2, project4] }
-
- it_behaves_like 'nodes are in descending order'
-
- context 'when before cursor value is NULL' do
- let(:arguments) { { before: encoded_cursor(project4) } }
-
- it 'returns all projects before the cursor' do
- expect(subject.sliced_nodes).to eq([project3, project1, project5, project2])
- end
- end
-
- context 'when after cursor value is NULL' do
- let(:arguments) { { after: encoded_cursor(project2) } }
-
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq([project4])
- end
- end
- end
- end
-
- context 'when ordering by similarity' do
- let_it_be(:project1) { create(:project, name: 'test') }
- let_it_be(:project2) { create(:project, name: 'testing') }
- let_it_be(:project3) { create(:project, name: 'tests') }
- let_it_be(:project4) { create(:project, name: 'testing stuff') }
- let_it_be(:project5) { create(:project, name: 'test') }
-
- let_it_be(:nodes) do
- # Note: sorted_by_similarity_desc scope internally supports the generic keyset order.
- Project.sorted_by_similarity_desc('test', include_in_select: true)
- end
-
- let_it_be(:descending_nodes) { nodes.to_a }
-
- it_behaves_like 'nodes are in descending order'
- end
-
- context 'when an invalid cursor is provided' do
- let(:arguments) { { before: Base64Bp.urlsafe_encode64('invalidcursor', padding: false) } }
-
- it 'raises an error' do
- expect { subject.sliced_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
- end
- end
- end
-
- 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
- let(:paged_nodes_size) { 3 }
- end
-
- context 'when both are passed' do
- let(:arguments) { { first: 2, last: 2 } }
-
- it 'raises an error' do
- expect { paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
- end
- end
-
- context 'when primary key is not in original order' do
- let(:nodes) { Project.order(last_repository_check_at: :desc) }
-
- it 'is added to end' do
- sliced = subject.sliced_nodes
-
- order_sql = sliced.order_values.last.to_sql
-
- expect(order_sql).to end_with(Project.arel_table[:id].desc.to_sql)
- end
- end
-
- context 'when there is no primary key' do
- before do
- stub_const('NoPrimaryKey', Class.new(ActiveRecord::Base))
- NoPrimaryKey.class_eval do
- self.table_name = 'no_primary_key'
- self.primary_key = nil
- end
- end
-
- let(:nodes) { NoPrimaryKey.all }
-
- it 'raises an error' do
- expect(NoPrimaryKey.primary_key).to be_nil
- expect { subject.sliced_nodes }.to raise_error(ArgumentError, 'Relation must have a primary key')
- end
- end
- end
-
- describe '#has_previous_page and #has_next_page' do
- # using a list of 5 items with a max_page of 3
- let_it_be(:project_list) { create_list(:project, 5) }
- let_it_be(:nodes) { Project.order(Gitlab::Pagination::Keyset::Order.build([column_order_id])) }
-
- context 'when default query' do
- let(:arguments) { {} }
-
- it 'has no previous, but a next' do
- expect(subject.has_previous_page).to be_falsey
- expect(subject.has_next_page).to be_truthy
- end
- end
-
- context 'when before is first item' do
- let(:arguments) { { before: encoded_cursor(project_list.first) } }
-
- it 'has no previous, but a next' do
- expect(subject.has_previous_page).to be_falsey
- expect(subject.has_next_page).to be_truthy
- end
- end
-
- describe 'using `before`' do
- context 'when before is the last item' do
- let(:arguments) { { before: encoded_cursor(project_list.last) } }
-
- it 'has no previous, but a next' do
- expect(subject.has_previous_page).to be_falsey
- expect(subject.has_next_page).to be_truthy
- end
- end
-
- context 'when before and last specified' do
- let(:arguments) { { before: encoded_cursor(project_list.last), last: 2 } }
-
- it 'has a previous and a next' do
- expect(subject.has_previous_page).to be_truthy
- expect(subject.has_next_page).to be_truthy
- end
- end
-
- context 'when before and last does request all remaining nodes' do
- let(:arguments) { { before: encoded_cursor(project_list[1]), last: 3 } }
-
- it 'has a previous and a next' do
- expect(subject.has_previous_page).to be_falsey
- expect(subject.has_next_page).to be_truthy
- expect(subject.nodes).to eq [project_list[0]]
- end
- end
- end
-
- describe 'using `after`' do
- context 'when after is the first item' do
- let(:arguments) { { after: encoded_cursor(project_list.first) } }
-
- it 'has a previous, and a next' do
- expect(subject.has_previous_page).to be_truthy
- expect(subject.has_next_page).to be_truthy
- end
- end
-
- context 'when after and first specified' do
- let(:arguments) { { after: encoded_cursor(project_list.first), first: 2 } }
-
- it 'has a previous and a next' do
- expect(subject.has_previous_page).to be_truthy
- expect(subject.has_next_page).to be_truthy
- end
- end
-
- context 'when before and last does request all remaining nodes' do
- let(:arguments) { { after: encoded_cursor(project_list[2]), last: 3 } }
-
- it 'has a previous but no next' do
- expect(subject.has_previous_page).to be_truthy
- expect(subject.has_next_page).to be_falsey
- end
- end
- end
- 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 6574b3e3131..b54c618d8e0 100644
--- a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb
@@ -5,10 +5,38 @@ require 'spec_helper'
RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
include GraphqlHelpers
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/334973
+ # The spec will be merged with connection_spec.rb in the future.
let(:nodes) { Project.all.order(id: :asc) }
let(:arguments) { {} }
let(:context) { GraphQL::Query::Context.new(query: query_double, values: nil, object: nil) }
+ let_it_be(:column_order_id) { Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(attribute_name: 'id', order_expression: Project.arel_table[:id].asc) }
+ let_it_be(:column_order_id_desc) { Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(attribute_name: 'id', order_expression: Project.arel_table[:id].desc) }
+ let_it_be(:column_order_updated_at) { Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(attribute_name: 'updated_at', order_expression: Project.arel_table[:updated_at].asc) }
+ let_it_be(:column_order_created_at) { Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(attribute_name: 'created_at', order_expression: Project.arel_table[:created_at].asc) }
+ let_it_be(:column_order_last_repo) do
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'last_repository_check_at',
+ column_expression: Project.arel_table[:last_repository_check_at],
+ order_expression: Project.arel_table[:last_repository_check_at].asc.nulls_last,
+ reversed_order_expression: Project.arel_table[:last_repository_check_at].desc.nulls_last,
+ order_direction: :asc,
+ nullable: :nulls_last,
+ distinct: false)
+ end
+
+ let_it_be(:column_order_last_repo_desc) do
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'last_repository_check_at',
+ column_expression: Project.arel_table[:last_repository_check_at],
+ order_expression: Project.arel_table[:last_repository_check_at].desc.nulls_last,
+ reversed_order_expression: Project.arel_table[:last_repository_check_at].asc.nulls_last,
+ order_direction: :desc,
+ nullable: :nulls_last,
+ distinct: false)
+ end
+
subject(:connection) do
described_class.new(nodes, **{ context: context, max_page_size: 3 }.merge(arguments))
end
@@ -21,414 +49,293 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
Gitlab::Json.parse(Base64Bp.urlsafe_decode64(cursor))
end
- # see: https://gitlab.com/gitlab-org/gitlab/-/issues/297358
- context 'the relation has been preloaded' do
- let(:projects) { Project.all.preload(:issues) }
- let(:nodes) { projects.first.issues }
-
- before do
- project = create(:project)
- create_list(:issue, 3, project: project)
- end
-
- it 'is loaded' do
- expect(nodes).to be_loaded
- end
-
- it 'does not error when accessing pagination information' do
- connection.first = 2
-
- expect(connection).to have_attributes(
- has_previous_page: false,
- has_next_page: true
- )
- end
-
- it 'can generate cursors' do
- connection.send(:ordered_items) # necessary to generate the order-list
-
- expect(connection.cursor_for(nodes.first)).to be_a(String)
- end
-
- it 'can read the next page' do
- connection.send(:ordered_items) # necessary to generate the order-list
- ordered = nodes.reorder(id: :desc)
- next_page = described_class.new(nodes,
- context: context,
- max_page_size: 3,
- after: connection.cursor_for(ordered.second))
-
- expect(next_page.sliced_nodes).to contain_exactly(ordered.third)
- end
- end
-
- it_behaves_like 'a connection with collection methods'
-
- it_behaves_like 'a redactable connection' do
- let_it_be(:projects) { create_list(:project, 2) }
- let(:unwanted) { projects.second }
- end
-
- describe '#cursor_for' do
- let(:project) { create(:project) }
- let(:cursor) { connection.cursor_for(project) }
-
- it 'returns an encoded ID' do
- expect(decoded_cursor(cursor)).to eq('id' => project.id.to_s)
- end
-
- context 'when SimpleOrderBuilder cannot build keyset paginated query' do
- it 'increments the `old_keyset_pagination_usage` counter', :prometheus do
- expect(Gitlab::Pagination::Keyset::SimpleOrderBuilder).to receive(:build).and_return([false, nil])
-
- decoded_cursor(cursor)
-
- counter = Gitlab::Metrics.registry.get(:old_keyset_pagination_usage)
- expect(counter.get(model: 'Project')).to eq(1)
- end
- end
-
- context 'when an order is specified' do
- let(:nodes) { Project.order(:updated_at) }
+ describe "with generic keyset order support" do
+ let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_id])) }
- it 'returns the encoded value of the order' do
- expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect))
- end
-
- it 'includes the :id even when not specified in the order' do
- expect(decoded_cursor(cursor)).to include('id' => project.id.to_s)
- end
- end
+ it_behaves_like 'a connection with collection methods'
- context 'when multiple orders are specified' do
- let(:nodes) { Project.order(:updated_at).order(:created_at) }
-
- it 'returns the encoded value of the order' do
- expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect))
- end
+ it_behaves_like 'a redactable connection' do
+ let_it_be(:projects) { create_list(:project, 2) }
+ let(:unwanted) { projects.second }
end
- context 'when multiple orders with SQL are specified' do
- let(:nodes) { Project.order(Arel.sql('projects.updated_at IS NULL')).order(:updated_at).order(:id) }
+ describe '#cursor_for' do
+ let(:project) { create(:project) }
+ let(:cursor) { connection.cursor_for(project) }
- it 'returns the encoded value of the order' do
- expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect))
+ it 'returns an encoded ID' do
+ expect(decoded_cursor(cursor)).to eq('id' => project.id.to_s)
end
- end
- end
- describe '#sliced_nodes' do
- let(:projects) { create_list(:project, 4) }
+ context 'when an order is specified' do
+ let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_id])) }
- context 'when before is passed' do
- let(:arguments) { { before: encoded_cursor(projects[1]) } }
-
- it 'only returns the project before the selected one' do
- expect(subject.sliced_nodes).to contain_exactly(projects.first)
+ it 'returns the encoded value of the order' do
+ expect(decoded_cursor(cursor)).to include('id' => project.id.to_s)
+ end
end
- context 'when the sort order is descending' do
- let(:nodes) { Project.all.order(id: :desc) }
+ context 'when multiple orders are specified' do
+ let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_updated_at, column_order_created_at, column_order_id])) }
- it 'returns the correct nodes' do
- expect(subject.sliced_nodes).to contain_exactly(*projects[2..])
+ it 'returns the encoded value of the order' do
+ expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect))
end
end
end
- context 'when after is passed' do
- let(:arguments) { { after: encoded_cursor(projects[1]) } }
+ describe '#sliced_nodes' do
+ let(:projects) { create_list(:project, 4) }
- it 'only returns the project before the selected one' do
- expect(subject.sliced_nodes).to contain_exactly(*projects[2..])
- end
+ context 'when before is passed' do
+ let(:arguments) { { before: encoded_cursor(projects[1]) } }
- context 'when the sort order is descending' do
- let(:nodes) { Project.all.order(id: :desc) }
-
- it 'returns the correct nodes' do
+ it 'only returns the project before the selected one' do
expect(subject.sliced_nodes).to contain_exactly(projects.first)
end
- end
- end
- context 'when both before and after are passed' do
- let(:arguments) do
- {
- after: encoded_cursor(projects[1]),
- before: encoded_cursor(projects[3])
- }
- end
+ context 'when the sort order is descending' do
+ let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_id_desc])) }
- it 'returns the expected set' do
- expect(subject.sliced_nodes).to contain_exactly(projects[2])
+ it 'returns the correct nodes' do
+ expect(subject.sliced_nodes).to contain_exactly(*projects[2..])
+ end
+ end
end
- end
- shared_examples 'nodes are in ascending order' do
- context 'when no cursor is passed' do
- let(:arguments) { {} }
+ context 'when after is passed' do
+ let(:arguments) { { after: encoded_cursor(projects[1]) } }
- it 'returns projects in ascending order' do
- expect(subject.sliced_nodes).to eq(ascending_nodes)
+ it 'only returns the project before the selected one' do
+ expect(subject.sliced_nodes).to contain_exactly(*projects[2..])
end
- end
- context 'when before cursor value is not NULL' do
- let(:arguments) { { before: encoded_cursor(ascending_nodes[2]) } }
+ context 'when the sort order is descending' do
+ let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_id_desc])) }
- it 'returns all projects before the cursor' do
- expect(subject.sliced_nodes).to eq(ascending_nodes.first(2))
+ it 'returns the correct nodes' do
+ expect(subject.sliced_nodes).to contain_exactly(projects.first)
+ end
end
end
- context 'when after cursor value is not NULL' do
- let(:arguments) { { after: encoded_cursor(ascending_nodes[1]) } }
+ context 'when both before and after are passed' do
+ let(:arguments) do
+ {
+ after: encoded_cursor(projects[1]),
+ before: encoded_cursor(projects[3])
+ }
+ end
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq(ascending_nodes.last(3))
+ it 'returns the expected set' do
+ expect(subject.sliced_nodes).to contain_exactly(projects[2])
end
end
- context 'when before and after cursor' do
- let(:arguments) { { before: encoded_cursor(ascending_nodes.last), after: encoded_cursor(ascending_nodes.first) } }
+ shared_examples 'nodes are in ascending order' do
+ context 'when no cursor is passed' do
+ let(:arguments) { {} }
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq(ascending_nodes[1..3])
+ it 'returns projects in ascending order' do
+ expect(subject.sliced_nodes).to eq(ascending_nodes)
+ end
end
- end
- end
- shared_examples 'nodes are in descending order' do
- context 'when no cursor is passed' do
- let(:arguments) { {} }
+ context 'when before cursor value is not NULL' do
+ let(:arguments) { { before: encoded_cursor(ascending_nodes[2]) } }
- it 'only returns projects in descending order' do
- expect(subject.sliced_nodes).to eq(descending_nodes)
+ it 'returns all projects before the cursor' do
+ expect(subject.sliced_nodes).to eq(ascending_nodes.first(2))
+ end
end
- end
- context 'when before cursor value is not NULL' do
- let(:arguments) { { before: encoded_cursor(descending_nodes[2]) } }
+ context 'when after cursor value is not NULL' do
+ let(:arguments) { { after: encoded_cursor(ascending_nodes[1]) } }
- it 'returns all projects before the cursor' do
- expect(subject.sliced_nodes).to eq(descending_nodes.first(2))
+ it 'returns all projects after the cursor' do
+ expect(subject.sliced_nodes).to eq(ascending_nodes.last(3))
+ end
end
- end
- context 'when after cursor value is not NULL' do
- let(:arguments) { { after: encoded_cursor(descending_nodes[1]) } }
+ context 'when before and after cursor' do
+ let(:arguments) { { before: encoded_cursor(ascending_nodes.last), after: encoded_cursor(ascending_nodes.first) } }
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq(descending_nodes.last(3))
+ it 'returns all projects after the cursor' do
+ expect(subject.sliced_nodes).to eq(ascending_nodes[1..3])
+ end
end
end
- context 'when before and after cursor' do
- let(:arguments) { { before: encoded_cursor(descending_nodes.last), after: encoded_cursor(descending_nodes.first) } }
+ shared_examples 'nodes are in descending order' do
+ context 'when no cursor is passed' do
+ let(:arguments) { {} }
- it 'returns all projects after the cursor' do
- expect(subject.sliced_nodes).to eq(descending_nodes[1..3])
+ it 'only returns projects in descending order' do
+ expect(subject.sliced_nodes).to eq(descending_nodes)
+ end
end
- end
- end
- context 'when ordering uses LOWER' do
- let!(:project1) { create(:project, name: 'A') } # Asc: project1 Desc: project4
- let!(:project2) { create(:project, name: 'c') } # Asc: project5 Desc: project2
- let!(:project3) { create(:project, name: 'b') } # Asc: project3 Desc: project3
- let!(:project4) { create(:project, name: 'd') } # Asc: project2 Desc: project5
- let!(:project5) { create(:project, name: 'a') } # Asc: project4 Desc: project1
+ context 'when before cursor value is not NULL' do
+ let(:arguments) { { before: encoded_cursor(descending_nodes[2]) } }
- context 'when ascending' do
- let(:nodes) do
- Project.order(Arel::Table.new(:projects)['name'].lower.asc).order(id: :asc)
+ it 'returns all projects before the cursor' do
+ expect(subject.sliced_nodes).to eq(descending_nodes.first(2))
+ end
end
- let(:ascending_nodes) { [project1, project5, project3, project2, project4] }
+ context 'when after cursor value is not NULL' do
+ let(:arguments) { { after: encoded_cursor(descending_nodes[1]) } }
- it_behaves_like 'nodes are in ascending order'
- end
-
- context 'when descending' do
- let(:nodes) do
- Project.order(Arel::Table.new(:projects)['name'].lower.desc).order(id: :desc)
+ it 'returns all projects after the cursor' do
+ expect(subject.sliced_nodes).to eq(descending_nodes.last(3))
+ end
end
- let(:descending_nodes) { [project4, project2, project3, project5, project1] }
+ context 'when before and after cursor' do
+ let(:arguments) { { before: encoded_cursor(descending_nodes.last), after: encoded_cursor(descending_nodes.first) } }
- it_behaves_like 'nodes are in descending order'
+ it 'returns all projects after the cursor' do
+ expect(subject.sliced_nodes).to eq(descending_nodes[1..3])
+ end
+ end
end
- end
- context 'NULLS order' do
- using RSpec::Parameterized::TableSyntax
+ context 'when multiple orders with nil values are defined' do
+ let_it_be(:project1) { create(:project, last_repository_check_at: 10.days.ago) } # Asc: project5 Desc: project3
+ let_it_be(:project2) { create(:project, last_repository_check_at: nil) } # Asc: project1 Desc: project1
+ let_it_be(:project3) { create(:project, last_repository_check_at: 5.days.ago) } # Asc: project3 Desc: project5
+ let_it_be(:project4) { create(:project, last_repository_check_at: nil) } # Asc: project2 Desc: project2
+ let_it_be(:project5) { create(:project, last_repository_check_at: 20.days.ago) } # Asc: project4 Desc: project4
- let_it_be(:issue1) { create(:issue, relative_position: nil) }
- let_it_be(:issue2) { create(:issue, relative_position: 100) }
- let_it_be(:issue3) { create(:issue, relative_position: 200) }
- let_it_be(:issue4) { create(:issue, relative_position: nil) }
- let_it_be(:issue5) { create(:issue, relative_position: 300) }
+ context 'when ascending' do
+ let_it_be(:order) { Gitlab::Pagination::Keyset::Order.build([column_order_last_repo, column_order_id]) }
+ let_it_be(:nodes) { Project.order(order) }
+ let_it_be(:ascending_nodes) { [project5, project1, project3, project2, project4] }
- context 'when ascending NULLS LAST (ties broken by id DESC implicitly)' do
- let(:ascending_nodes) { [issue2, issue3, issue5, issue4, issue1] }
+ it_behaves_like 'nodes are in ascending order'
- where(:nodes) do
- [
- lazy { Issue.order(Issue.arel_table[:relative_position].asc.nulls_last) }
- ]
- end
+ context 'when before cursor value is NULL' do
+ let(:arguments) { { before: encoded_cursor(project4) } }
- with_them do
- it_behaves_like 'nodes are in ascending order'
- end
- end
+ it 'returns all projects before the cursor' do
+ expect(subject.sliced_nodes).to eq([project5, project1, project3, project2])
+ end
+ end
- context 'when descending NULLS LAST (ties broken by id DESC implicitly)' do
- let(:descending_nodes) { [issue5, issue3, issue2, issue4, issue1] }
+ context 'when after cursor value is NULL' do
+ let(:arguments) { { after: encoded_cursor(project2) } }
- where(:nodes) do
- [
- lazy { Issue.order(Issue.arel_table[:relative_position].desc.nulls_last) }
-]
+ it 'returns all projects after the cursor' do
+ expect(subject.sliced_nodes).to eq([project4])
+ end
+ end
end
- with_them do
+ context 'when descending' do
+ let_it_be(:order) { Gitlab::Pagination::Keyset::Order.build([column_order_last_repo_desc, column_order_id]) }
+ let_it_be(:nodes) { Project.order(order) }
+ let_it_be(:descending_nodes) { [project3, project1, project5, project2, project4] }
+
it_behaves_like 'nodes are in descending order'
- end
- end
- context 'when ascending NULLS FIRST with a tie breaker' do
- let(:ascending_nodes) { [issue1, issue4, issue2, issue3, issue5] }
+ context 'when before cursor value is NULL' do
+ let(:arguments) { { before: encoded_cursor(project4) } }
- where(:nodes) do
- [
- lazy { Issue.order(Issue.arel_table[:relative_position].asc.nulls_first).order(id: :asc) }
-]
- end
+ it 'returns all projects before the cursor' do
+ expect(subject.sliced_nodes).to eq([project3, project1, project5, project2])
+ end
+ end
- with_them do
- it_behaves_like 'nodes are in ascending order'
+ context 'when after cursor value is NULL' do
+ let(:arguments) { { after: encoded_cursor(project2) } }
+
+ it 'returns all projects after the cursor' do
+ expect(subject.sliced_nodes).to eq([project4])
+ end
+ end
end
end
- context 'when descending NULLS FIRST with a tie breaker' do
- let(:descending_nodes) { [issue1, issue4, issue5, issue3, issue2] }
+ context 'when ordering by similarity' do
+ let_it_be(:project1) { create(:project, name: 'test') }
+ let_it_be(:project2) { create(:project, name: 'testing') }
+ let_it_be(:project3) { create(:project, name: 'tests') }
+ let_it_be(:project4) { create(:project, name: 'testing stuff') }
+ let_it_be(:project5) { create(:project, name: 'test') }
- where(:nodes) do
- [
- lazy { Issue.order(Issue.arel_table[:relative_position].desc.nulls_first).order(id: :asc) }
-]
+ let_it_be(:nodes) do
+ # Note: sorted_by_similarity_desc scope internally supports the generic keyset order.
+ Project.sorted_by_similarity_desc('test', include_in_select: true)
end
- with_them do
- it_behaves_like 'nodes are in descending order'
- end
- end
- end
+ let_it_be(:descending_nodes) { nodes.to_a }
- context 'when ordering by similarity' do
- let!(:project1) { create(:project, name: 'test') }
- let!(:project2) { create(:project, name: 'testing') }
- let!(:project3) { create(:project, name: 'tests') }
- let!(:project4) { create(:project, name: 'testing stuff') }
- let!(:project5) { create(:project, name: 'test') }
-
- let(:nodes) do
- Project.sorted_by_similarity_desc('test', include_in_select: true)
+ it_behaves_like 'nodes are in descending order'
end
- let(:descending_nodes) { nodes.to_a }
-
- it_behaves_like 'nodes are in descending order'
- end
+ context 'when an invalid cursor is provided' do
+ let(:arguments) { { before: Base64Bp.urlsafe_encode64('invalidcursor', padding: false) } }
- context 'when an invalid cursor is provided' do
- let(:arguments) { { before: Base64Bp.urlsafe_encode64('invalidcursor', padding: false) } }
-
- it 'raises an error' do
- expect { subject.sliced_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ it 'raises an error' do
+ expect { subject.sliced_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
end
end
- end
- describe '#nodes' do
- let_it_be(:all_nodes) { create_list(:project, 5) }
+ describe '#nodes' do
+ let_it_be(:all_nodes) { create_list(:project, 5) }
- let(:paged_nodes) { subject.nodes }
+ let(:paged_nodes) { subject.nodes }
- it_behaves_like 'connection with paged nodes' do
- let(:paged_nodes_size) { 3 }
- end
-
- context 'when both are passed' do
- let(:arguments) { { first: 2, last: 2 } }
-
- it 'raises an error' do
- expect { paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ it_behaves_like 'connection with paged nodes' do
+ let(:paged_nodes_size) { 3 }
end
- end
- context 'when primary key is not in original order' do
- let(:nodes) { Project.order(last_repository_check_at: :desc) }
+ context 'when both are passed' do
+ let(:arguments) { { first: 2, last: 2 } }
- before do
- stub_feature_flags(new_graphql_keyset_pagination: false)
+ it 'raises an error' do
+ expect { paged_nodes }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
end
- it 'is added to end' do
- sliced = subject.sliced_nodes
+ context 'when primary key is not in original order' do
+ let(:nodes) { Project.order(last_repository_check_at: :desc) }
- order_sql = sliced.order_values.last.to_sql
+ it 'is added to end' do
+ sliced = subject.sliced_nodes
- expect(order_sql).to end_with(Project.arel_table[:id].desc.to_sql)
- end
- end
+ order_sql = sliced.order_values.last.to_sql
- context 'when there is no primary key' do
- before do
- stub_const('NoPrimaryKey', Class.new(ActiveRecord::Base))
- NoPrimaryKey.class_eval do
- self.table_name = 'no_primary_key'
- self.primary_key = nil
+ expect(order_sql).to end_with(Project.arel_table[:id].desc.to_sql)
end
end
- let(:nodes) { NoPrimaryKey.all }
-
- it 'raises an error' do
- expect(NoPrimaryKey.primary_key).to be_nil
- expect { subject.sliced_nodes }.to raise_error(ArgumentError, 'Relation must have a primary key')
- end
- end
- end
-
- describe '#has_previous_page and #has_next_page' do
- # using a list of 5 items with a max_page of 3
- let_it_be(:project_list) { create_list(:project, 5) }
- let_it_be(:nodes) { Project.order(:id) }
+ context 'when there is no primary key' do
+ before do
+ stub_const('NoPrimaryKey', Class.new(ActiveRecord::Base))
+ NoPrimaryKey.class_eval do
+ self.table_name = 'no_primary_key'
+ self.primary_key = nil
+ end
+ end
- context 'when default query' do
- let(:arguments) { {} }
+ let(:nodes) { NoPrimaryKey.all }
- it 'has no previous, but a next' do
- expect(subject.has_previous_page).to be_falsey
- expect(subject.has_next_page).to be_truthy
+ it 'raises an error' do
+ expect(NoPrimaryKey.primary_key).to be_nil
+ expect { subject.sliced_nodes }.to raise_error(ArgumentError, 'Relation must have a primary key')
+ end
end
end
- context 'when before is first item' do
- let(:arguments) { { before: encoded_cursor(project_list.first) } }
+ describe '#has_previous_page and #has_next_page' do
+ # using a list of 5 items with a max_page of 3
+ let_it_be(:project_list) { create_list(:project, 5) }
+ let_it_be(:nodes) { Project.order(Gitlab::Pagination::Keyset::Order.build([column_order_id])) }
- it 'has no previous, but a next' do
- expect(subject.has_previous_page).to be_falsey
- expect(subject.has_next_page).to be_truthy
- end
- end
-
- describe 'using `before`' do
- context 'when before is the last item' do
- let(:arguments) { { before: encoded_cursor(project_list.last) } }
+ context 'when default query' do
+ let(:arguments) { {} }
it 'has no previous, but a next' do
expect(subject.has_previous_page).to be_falsey
@@ -436,51 +343,71 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do
end
end
- context 'when before and last specified' do
- let(:arguments) { { before: encoded_cursor(project_list.last), last: 2 } }
+ context 'when before is first item' do
+ let(:arguments) { { before: encoded_cursor(project_list.first) } }
- it 'has a previous and a next' do
- expect(subject.has_previous_page).to be_truthy
+ it 'has no previous, but a next' do
+ expect(subject.has_previous_page).to be_falsey
expect(subject.has_next_page).to be_truthy
end
end
- context 'when before and last does request all remaining nodes' do
- let(:arguments) { { before: encoded_cursor(project_list[1]), last: 3 } }
+ describe 'using `before`' do
+ context 'when before is the last item' do
+ let(:arguments) { { before: encoded_cursor(project_list.last) } }
- it 'has a previous and a next' do
- expect(subject.has_previous_page).to be_falsey
- expect(subject.has_next_page).to be_truthy
- expect(subject.nodes).to eq [project_list[0]]
+ it 'has no previous, but a next' do
+ expect(subject.has_previous_page).to be_falsey
+ expect(subject.has_next_page).to be_truthy
+ end
end
- end
- end
- describe 'using `after`' do
- context 'when after is the first item' do
- let(:arguments) { { after: encoded_cursor(project_list.first) } }
+ context 'when before and last specified' do
+ let(:arguments) { { before: encoded_cursor(project_list.last), last: 2 } }
- it 'has a previous, and a next' do
- expect(subject.has_previous_page).to be_truthy
- expect(subject.has_next_page).to be_truthy
+ it 'has a previous and a next' do
+ expect(subject.has_previous_page).to be_truthy
+ expect(subject.has_next_page).to be_truthy
+ end
end
- end
- context 'when after and first specified' do
- let(:arguments) { { after: encoded_cursor(project_list.first), first: 2 } }
+ context 'when before and last does request all remaining nodes' do
+ let(:arguments) { { before: encoded_cursor(project_list[1]), last: 3 } }
- it 'has a previous and a next' do
- expect(subject.has_previous_page).to be_truthy
- expect(subject.has_next_page).to be_truthy
+ it 'has a previous and a next' do
+ expect(subject.has_previous_page).to be_falsey
+ expect(subject.has_next_page).to be_truthy
+ expect(subject.nodes).to eq [project_list[0]]
+ end
end
end
- context 'when before and last does request all remaining nodes' do
- let(:arguments) { { after: encoded_cursor(project_list[2]), last: 3 } }
+ describe 'using `after`' do
+ context 'when after is the first item' do
+ let(:arguments) { { after: encoded_cursor(project_list.first) } }
+
+ it 'has a previous, and a next' do
+ expect(subject.has_previous_page).to be_truthy
+ expect(subject.has_next_page).to be_truthy
+ end
+ end
+
+ context 'when after and first specified' do
+ let(:arguments) { { after: encoded_cursor(project_list.first), first: 2 } }
+
+ it 'has a previous and a next' do
+ expect(subject.has_previous_page).to be_truthy
+ expect(subject.has_next_page).to be_truthy
+ end
+ end
+
+ context 'when before and last does request all remaining nodes' do
+ let(:arguments) { { after: encoded_cursor(project_list[2]), last: 3 } }
- it 'has a previous but no next' do
- expect(subject.has_previous_page).to be_truthy
- expect(subject.has_next_page).to be_falsey
+ it 'has a previous but no next' do
+ expect(subject.has_previous_page).to be_truthy
+ expect(subject.has_next_page).to be_falsey
+ end
end
end
end
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
deleted file mode 100644
index 40ee47ece49..00000000000
--- a/spec/lib/gitlab/graphql/pagination/keyset/order_info_spec.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Graphql::Pagination::Keyset::OrderInfo do
- describe '#build_order_list' do
- let(:order_list) { described_class.build_order_list(relation) }
-
- context 'when multiple orders with SQL is specified' do
- let(:relation) { Project.order(Arel.sql('projects.updated_at IS NULL')).order(:updated_at).order(:id) }
-
- it 'ignores the SQL order' do
- expect(order_list.count).to eq 2
- expect(order_list.first.attribute_name).to eq 'updated_at'
- expect(order_list.first.operator_for(:after)).to eq '>'
- expect(order_list.last.attribute_name).to eq 'id'
- expect(order_list.last.operator_for(:after)).to eq '>'
- end
- end
-
- context 'when order contains NULLS LAST' do
- let(:relation) { Project.order(Arel.sql('projects.updated_at Asc Nulls Last')).order(:id) }
-
- it 'does not ignore the SQL order' do
- expect(order_list.count).to eq 2
- expect(order_list.first.attribute_name).to eq 'projects.updated_at'
- expect(order_list.first.operator_for(:after)).to eq '>'
- expect(order_list.last.attribute_name).to eq 'id'
- expect(order_list.last.operator_for(:after)).to eq '>'
- end
- end
-
- context 'when order contains invalid formatted NULLS LAST ' do
- let(:relation) { Project.order(Arel.sql('projects.updated_at created_at Asc Nulls Last')).order(:id) }
-
- it 'ignores the SQL order' do
- expect(order_list.count).to eq 1
- end
- end
-
- context 'when order contains LOWER' do
- let(:relation) { Project.order(Arel::Table.new(:projects)['name'].lower.asc).order(:id) }
-
- it 'does not ignore the SQL order' do
- expect(order_list.count).to eq 2
- expect(order_list.first.attribute_name).to eq 'name'
- expect(order_list.first.named_function).to be_kind_of(Arel::Nodes::NamedFunction)
- expect(order_list.first.named_function.to_sql).to eq 'LOWER("projects"."name")'
- expect(order_list.first.operator_for(:after)).to eq '>'
- expect(order_list.last.attribute_name).to eq 'id'
- expect(order_list.last.operator_for(:after)).to eq '>'
- end
- end
-
- context 'when ordering by CASE', :aggregate_failuers do
- let(:relation) { Project.order(Arel::Nodes::Case.new(Project.arel_table[:pending_delete]).when(true).then(100).else(1000).asc) }
-
- it 'assigns the right attribute name, named function, and direction' do
- expect(order_list.count).to eq 1
- expect(order_list.first.attribute_name).to eq 'case_order_value'
- expect(order_list.first.named_function).to be_kind_of(Arel::Nodes::Case)
- expect(order_list.first.sort_direction).to eq :asc
- end
- end
-
- context 'when ordering by ARRAY_POSITION', :aggregate_failuers do
- let(:array_position) { Arel::Nodes::NamedFunction.new('ARRAY_POSITION', [Arel.sql("ARRAY[1,0]::smallint[]"), Project.arel_table[:auto_cancel_pending_pipelines]]) }
- let(:relation) { Project.order(array_position.asc) }
-
- it 'assigns the right attribute name, named function, and direction' do
- expect(order_list.count).to eq 1
- expect(order_list.first.attribute_name).to eq 'array_position'
- expect(order_list.first.named_function).to be_kind_of(Arel::Nodes::NamedFunction)
- expect(order_list.first.sort_direction).to eq :asc
- end
- end
- end
-
- describe '#validate_ordering' do
- let(:order_list) { described_class.build_order_list(relation) }
-
- context 'when number of ordering fields is 0' do
- let(:relation) { Project.all }
-
- it 'raises an error' do
- expect { described_class.validate_ordering(relation, order_list) }
- .to raise_error(ArgumentError, 'A minimum of 1 ordering field is required')
- end
- end
-
- context 'when number of ordering fields is over 2' do
- let(:relation) { Project.order(last_repository_check_at: :desc).order(updated_at: :desc).order(:id) }
-
- it 'raises an error' do
- expect { described_class.validate_ordering(relation, order_list) }
- .to raise_error(ArgumentError, 'A maximum of 2 ordering fields are allowed')
- end
- end
-
- context 'when the second (or first) column is nullable' do
- let(:relation) { Project.order(last_repository_check_at: :desc).order(updated_at: :desc) }
-
- it 'raises an error' do
- expect { described_class.validate_ordering(relation, order_list) }
- .to raise_error(ArgumentError, "Column `updated_at` must not allow NULL")
- end
- end
-
- context 'for last ordering field' do
- let(:relation) { Project.order(namespace_id: :desc) }
-
- it 'raises error if primary key is not last field' do
- expect { described_class.validate_ordering(relation, order_list) }
- .to raise_error(ArgumentError, "Last ordering field must be the primary key, `#{relation.primary_key}`")
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb
deleted file mode 100644
index 31c02fd43e8..00000000000
--- a/spec/lib/gitlab/graphql/pagination/keyset/query_builder_spec.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Graphql::Pagination::Keyset::QueryBuilder do
- context 'when number of ordering fields is 0' do
- it 'raises an error' do
- expect { described_class.new(Issue.arel_table, [], {}, :after) }
- .to raise_error(ArgumentError, 'No ordering scopes have been supplied')
- end
- end
-
- describe '#conditions' do
- let(:relation) { Issue.order(relative_position: :desc).order(:id) }
- let(:order_list) { Gitlab::Graphql::Pagination::Keyset::OrderInfo.build_order_list(relation) }
- let(:arel_table) { Issue.arel_table }
- let(:builder) { described_class.new(arel_table, order_list, decoded_cursor, before_or_after) }
- let(:before_or_after) { :after }
-
- context 'when only a single ordering' do
- let(:relation) { Issue.order(id: :desc) }
-
- context 'when the value is nil' do
- let(:decoded_cursor) { { 'id' => nil } }
-
- it 'raises an error' do
- expect { builder.conditions }
- .to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'Before/after cursor invalid: `nil` was provided as only sortable value')
- end
- end
-
- context 'when value is not nil' do
- let(:decoded_cursor) { { 'id' => 100 } }
- let(:conditions) { builder.conditions }
-
- context 'when :after' do
- it 'generates the correct condition' do
- expect(conditions.strip).to eq '("issues"."id" < 100)'
- end
- end
-
- context 'when :before' do
- let(:before_or_after) { :before }
-
- it 'generates the correct condition' do
- expect(conditions.strip).to eq '("issues"."id" > 100)'
- end
- end
- end
- end
-
- context 'when two orderings' do
- let(:decoded_cursor) { { 'relative_position' => 1500, 'id' => 100 } }
-
- context 'when no values are nil' do
- context 'when :after' do
- it 'generates the correct condition' do
- conditions = builder.conditions
-
- expect(conditions).to include '"issues"."relative_position" < 1500'
- expect(conditions).to include '"issues"."id" > 100'
- expect(conditions).to include 'OR ("issues"."relative_position" IS NULL)'
- end
- end
-
- context 'when :before' do
- let(:before_or_after) { :before }
-
- it 'generates the correct condition' do
- conditions = builder.conditions
-
- expect(conditions).to include '("issues"."relative_position" > 1500)'
- expect(conditions).to include '"issues"."id" < 100'
- expect(conditions).to include '"issues"."relative_position" = 1500'
- end
- end
- end
-
- context 'when first value is nil' do
- let(:decoded_cursor) { { 'relative_position' => nil, 'id' => 100 } }
-
- context 'when :after' do
- it 'generates the correct condition' do
- conditions = builder.conditions
-
- expect(conditions).to include '"issues"."relative_position" IS NULL'
- expect(conditions).to include '"issues"."id" > 100'
- end
- end
-
- context 'when :before' do
- let(:before_or_after) { :before }
-
- it 'generates the correct condition' do
- conditions = builder.conditions
-
- expect(conditions).to include '"issues"."relative_position" IS NULL'
- expect(conditions).to include '"issues"."id" < 100'
- expect(conditions).to include 'OR ("issues"."relative_position" IS NOT NULL)'
- end
- end
- end
- end
-
- context 'when sorting using LOWER' do
- let(:relation) { Project.order(Arel::Table.new(:projects)['name'].lower.asc).order(:id) }
- let(:arel_table) { Project.arel_table }
- let(:decoded_cursor) { { 'name' => 'Test', 'id' => 100 } }
-
- context 'when no values are nil' do
- context 'when :after' do
- it 'generates the correct condition' do
- conditions = builder.conditions
-
- expect(conditions).to include '(LOWER("projects"."name") > \'test\')'
- expect(conditions).to include '"projects"."id" > 100'
- expect(conditions).to include 'OR (LOWER("projects"."name") IS NULL)'
- end
- end
-
- context 'when :before' do
- let(:before_or_after) { :before }
-
- it 'generates the correct condition' do
- conditions = builder.conditions
-
- expect(conditions).to include '(LOWER("projects"."name") < \'test\')'
- expect(conditions).to include '"projects"."id" < 100'
- expect(conditions).to include 'LOWER("projects"."name") = \'test\''
- end
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/graphql/type_name_deprecations_spec.rb b/spec/lib/gitlab/graphql/type_name_deprecations_spec.rb
new file mode 100644
index 00000000000..0505e709a3b
--- /dev/null
+++ b/spec/lib/gitlab/graphql/type_name_deprecations_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative '../../../support/helpers/type_name_deprecation_helpers'
+
+RSpec.describe Gitlab::Graphql::TypeNameDeprecations do
+ include TypeNameDeprecationHelpers
+
+ let(:deprecation_1) do
+ described_class::NameDeprecation.new(old_name: 'Foo::Model', new_name: 'Bar', milestone: '9.0')
+ end
+
+ let(:deprecation_2) do
+ described_class::NameDeprecation.new(old_name: 'Baz', new_name: 'Qux::Model', milestone: '10.0')
+ end
+
+ before do
+ stub_type_name_deprecations(deprecation_1, deprecation_2)
+ end
+
+ describe '.deprecated?' do
+ it 'returns a boolean to signal if model name has a deprecation', :aggregate_failures do
+ expect(described_class.deprecated?('Foo::Model')).to eq(true)
+ expect(described_class.deprecated?('Qux::Model')).to eq(false)
+ end
+ end
+
+ describe '.deprecation_for' do
+ it 'returns the deprecation for the model if it exists', :aggregate_failures do
+ expect(described_class.deprecation_for('Foo::Model')).to eq(deprecation_1)
+ expect(described_class.deprecation_for('Qux::Model')).to be_nil
+ end
+ end
+
+ describe '.deprecation_by' do
+ it 'returns the deprecation by the model if it exists', :aggregate_failures do
+ expect(described_class.deprecation_by('Foo::Model')).to be_nil
+ expect(described_class.deprecation_by('Qux::Model')).to eq(deprecation_2)
+ end
+ end
+
+ describe '.apply_to_graphql_name' do
+ it 'returns the corresponding graphql_name of the GID for the new model', :aggregate_failures do
+ expect(described_class.apply_to_graphql_name('Foo::Model')).to eq('Bar')
+ expect(described_class.apply_to_graphql_name('Baz')).to eq('Qux::Model')
+ end
+
+ it 'returns the same value if there is no deprecation' do
+ expect(described_class.apply_to_graphql_name('Project')).to eq('Project')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb
index 79cec2d8705..c3c696ceedc 100644
--- a/spec/lib/gitlab/graphs/commits_spec.rb
+++ b/spec/lib/gitlab/graphs/commits_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Graphs::Commits do
let!(:project) { create(:project, :public) }
let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) }
- let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago)}
+ let!(:commit1_yesterday) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: 1.day.ago) }
let!(:commit2) { create(:commit, git_commit: RepoHelpers.another_sample_commit, project: project, committed_date: Time.now) }
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 537e59d91c3..d7ae6ed06a4 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::Highlight do
context 'diff highlighting' do
let(:file_name) { 'test.diff' }
- let(:content) { "+aaa\n+bbb\n- ccc\n ddd\n"}
+ let(:content) { "+aaa\n+bbb\n- ccc\n ddd\n" }
let(:expected) do
%q(<span id="LC1" class="line" lang="diff"><span class="gi">+aaa</span></span>
<span id="LC2" class="line" lang="diff"><span class="gi">+bbb</span></span>
diff --git a/spec/lib/gitlab/hook_data/group_builder_spec.rb b/spec/lib/gitlab/hook_data/group_builder_spec.rb
index d7347ff99d4..4e6152390a4 100644
--- a/spec/lib/gitlab/hook_data/group_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/group_builder_spec.rb
@@ -38,6 +38,7 @@ RSpec.describe Gitlab::HookData::GroupBuilder do
let(:event) { :create }
it { expect(event_name).to eq('group_create') }
+
it_behaves_like 'includes the required attributes'
it_behaves_like 'does not include old path attributes'
end
@@ -46,6 +47,7 @@ RSpec.describe Gitlab::HookData::GroupBuilder do
let(:event) { :destroy }
it { expect(event_name).to eq('group_destroy') }
+
it_behaves_like 'includes the required attributes'
it_behaves_like 'does not include old path attributes'
end
@@ -54,6 +56,7 @@ RSpec.describe Gitlab::HookData::GroupBuilder do
let(:event) { :rename }
it { expect(event_name).to eq('group_rename') }
+
it_behaves_like 'includes the required attributes'
it 'includes old path details' do
diff --git a/spec/lib/gitlab/hook_data/group_member_builder_spec.rb b/spec/lib/gitlab/hook_data/group_member_builder_spec.rb
index 78c62fd23c7..35ce31ab897 100644
--- a/spec/lib/gitlab/hook_data/group_member_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/group_member_builder_spec.rb
@@ -39,6 +39,7 @@ RSpec.describe Gitlab::HookData::GroupMemberBuilder do
let(:event) { :create }
it { expect(event_name).to eq('user_add_to_group') }
+
it_behaves_like 'includes the required attributes'
end
@@ -46,6 +47,7 @@ RSpec.describe Gitlab::HookData::GroupMemberBuilder do
let(:event) { :update }
it { expect(event_name).to eq('user_update_for_group') }
+
it_behaves_like 'includes the required attributes'
end
@@ -53,6 +55,7 @@ RSpec.describe Gitlab::HookData::GroupMemberBuilder do
let(:event) { :destroy }
it { expect(event_name).to eq('user_remove_from_group') }
+
it_behaves_like 'includes the required attributes'
end
end
diff --git a/spec/lib/gitlab/hook_data/key_builder_spec.rb b/spec/lib/gitlab/hook_data/key_builder_spec.rb
index 86f33df115f..2c87c9a10e6 100644
--- a/spec/lib/gitlab/hook_data/key_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/key_builder_spec.rb
@@ -36,6 +36,7 @@ RSpec.describe Gitlab::HookData::KeyBuilder do
it { expect(event_name).to eq('key_create') }
it { expect(data[:username]).to eq(key.user.username) }
+
it_behaves_like 'includes the required attributes'
end
@@ -44,6 +45,7 @@ RSpec.describe Gitlab::HookData::KeyBuilder do
it { expect(event_name).to eq('key_destroy') }
it { expect(data[:username]).to eq(key.user.username) }
+
it_behaves_like 'includes the required attributes'
end
end
@@ -58,6 +60,7 @@ RSpec.describe Gitlab::HookData::KeyBuilder do
let(:event) { :create }
it { expect(event_name).to eq('key_create') }
+
it_behaves_like 'includes the required attributes'
end
@@ -65,6 +68,7 @@ RSpec.describe Gitlab::HookData::KeyBuilder do
let(:event) { :destroy }
it { expect(event_name).to eq('key_destroy') }
+
it_behaves_like 'includes the required attributes'
end
end
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 25b84a67ab2..cb8fef60ab2 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -29,6 +29,7 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
merge_user_id
merge_when_pipeline_succeeds
milestone_id
+ reviewer_ids
source_branch
source_project_id
state_id
@@ -72,6 +73,7 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do
human_time_estimate
assignee_ids
assignee_id
+ reviewer_ids
labels
state
blocking_discussions_resolved
diff --git a/spec/lib/gitlab/hook_data/project_builder_spec.rb b/spec/lib/gitlab/hook_data/project_builder_spec.rb
index e86ac66b1ad..729712510ea 100644
--- a/spec/lib/gitlab/hook_data/project_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/project_builder_spec.rb
@@ -52,6 +52,7 @@ RSpec.describe Gitlab::HookData::ProjectBuilder do
let(:event) { :create }
it { expect(event_name).to eq('project_create') }
+
it_behaves_like 'includes the required attributes'
it_behaves_like 'does not include `old_path_with_namespace` attribute'
end
@@ -60,6 +61,7 @@ RSpec.describe Gitlab::HookData::ProjectBuilder do
let(:event) { :destroy }
it { expect(event_name).to eq('project_destroy') }
+
it_behaves_like 'includes the required attributes'
it_behaves_like 'does not include `old_path_with_namespace` attribute'
end
@@ -68,6 +70,7 @@ RSpec.describe Gitlab::HookData::ProjectBuilder do
let(:event) { :rename }
it { expect(event_name).to eq('project_rename') }
+
it_behaves_like 'includes the required attributes'
it_behaves_like 'includes `old_path_with_namespace` attribute'
end
@@ -76,6 +79,7 @@ RSpec.describe Gitlab::HookData::ProjectBuilder do
let(:event) { :transfer }
it { expect(event_name).to eq('project_transfer') }
+
it_behaves_like 'includes the required attributes'
it_behaves_like 'includes `old_path_with_namespace` attribute'
end
diff --git a/spec/lib/gitlab/hook_data/project_member_builder_spec.rb b/spec/lib/gitlab/hook_data/project_member_builder_spec.rb
index 3fb84223581..76446adf7b7 100644
--- a/spec/lib/gitlab/hook_data/project_member_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/project_member_builder_spec.rb
@@ -37,6 +37,7 @@ RSpec.describe Gitlab::HookData::ProjectMemberBuilder do
let(:event) { :create }
it { expect(event_name).to eq('user_add_to_team') }
+
it_behaves_like 'includes the required attributes'
end
@@ -44,6 +45,7 @@ RSpec.describe Gitlab::HookData::ProjectMemberBuilder do
let(:event) { :update }
it { expect(event_name).to eq('user_update_for_team') }
+
it_behaves_like 'includes the required attributes'
end
@@ -51,6 +53,7 @@ RSpec.describe Gitlab::HookData::ProjectMemberBuilder do
let(:event) { :destroy }
it { expect(event_name).to eq('user_remove_from_team') }
+
it_behaves_like 'includes the required attributes'
end
end
diff --git a/spec/lib/gitlab/hook_data/subgroup_builder_spec.rb b/spec/lib/gitlab/hook_data/subgroup_builder_spec.rb
index 89e5dffd7b4..b25320af891 100644
--- a/spec/lib/gitlab/hook_data/subgroup_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/subgroup_builder_spec.rb
@@ -38,6 +38,7 @@ RSpec.describe Gitlab::HookData::SubgroupBuilder do
let(:event) { :create }
it { expect(event_name).to eq('subgroup_create') }
+
it_behaves_like 'includes the required attributes'
end
@@ -45,6 +46,7 @@ RSpec.describe Gitlab::HookData::SubgroupBuilder do
let(:event) { :destroy }
it { expect(event_name).to eq('subgroup_destroy') }
+
it_behaves_like 'includes the required attributes'
end
end
diff --git a/spec/lib/gitlab/hook_data/user_builder_spec.rb b/spec/lib/gitlab/hook_data/user_builder_spec.rb
index f971089850b..ae844308fb1 100644
--- a/spec/lib/gitlab/hook_data/user_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/user_builder_spec.rb
@@ -44,6 +44,7 @@ RSpec.describe Gitlab::HookData::UserBuilder 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'
@@ -53,6 +54,7 @@ RSpec.describe Gitlab::HookData::UserBuilder 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'
@@ -62,6 +64,7 @@ RSpec.describe Gitlab::HookData::UserBuilder 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'
@@ -76,6 +79,7 @@ RSpec.describe Gitlab::HookData::UserBuilder 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'
diff --git a/spec/lib/gitlab/http_io_spec.rb b/spec/lib/gitlab/http_io_spec.rb
index 5ba0cb5e686..1376b726df3 100644
--- a/spec/lib/gitlab/http_io_spec.rb
+++ b/spec/lib/gitlab/http_io_spec.rb
@@ -262,7 +262,7 @@ RSpec.describe Gitlab::HttpIO do
end
it 'reads a trace' do
- expect { subject }.to raise_error(Gitlab::HttpIO::FailedToGetChunkError)
+ expect { subject }.to raise_error(Gitlab::HttpIO::FailedToGetChunkError, 'Unexpected response code: 500')
end
end
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
index 451fd6c6f46..42cf9c54798 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
@@ -9,12 +9,21 @@ RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
allow_next_instance_of(ProjectExportWorker) do |job|
allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
end
+
+ stub_feature_flags(import_export_web_upload_stream: false)
+ stub_uploads_object_storage(FileUploader, enabled: false)
end
let(:example_url) { 'http://www.example.com' }
let(:strategy) { subject.new(url: example_url, http_method: 'post') }
- let!(:project) { create(:project, :with_export) }
- let!(:user) { build(:user) }
+ let(:user) { build(:user) }
+ let(:project) { import_export_upload.project }
+ let(:import_export_upload) do
+ create(
+ :import_export_upload,
+ export_file: fixture_file_upload('spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz')
+ )
+ end
subject { described_class }
@@ -36,20 +45,42 @@ RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
describe '#execute' do
context 'when upload succeeds' do
before do
- allow(strategy).to receive(:send_file)
- allow(strategy).to receive(:handle_response_error)
+ stub_full_request(example_url, method: :post).to_return(status: 200)
end
- it 'does not remove the exported project file after the upload' do
+ it 'does not remove the exported project file after the upload', :aggregate_failures do
expect(project).not_to receive(:remove_exports)
- strategy.execute(user, project)
+ expect { strategy.execute(user, project) }.not_to change(project, :export_status)
+
+ expect(project.export_status).to eq(:finished)
end
- it 'has finished export status' do
- strategy.execute(user, project)
+ it 'logs when upload starts and finishes' do
+ export_size = import_export_upload.export_file.size
+
+ expect_next_instance_of(Gitlab::Export::Logger) do |logger|
+ expect(logger).to receive(:info).ordered.with(
+ {
+ message: "Started uploading project",
+ project_id: project.id,
+ project_name: project.name,
+ export_size: export_size
+ }
+ )
+
+ expect(logger).to receive(:info).ordered.with(
+ {
+ message: "Finished uploading project",
+ project_id: project.id,
+ project_name: project.name,
+ export_size: export_size,
+ upload_duration: anything
+ }
+ )
+ end
- expect(project.export_status).to eq(:finished)
+ strategy.execute(user, project)
end
end
@@ -64,5 +95,124 @@ RSpec.describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
expect(errors.first).to eq "Error uploading the project. Code 404: Page not found"
end
end
+
+ context 'when object store is disabled' do
+ it 'reads file from disk and uploads to external url' do
+ stub_request(:post, example_url).to_return(status: 200)
+ expect(Gitlab::ImportExport::RemoteStreamUpload).not_to receive(:new)
+ expect(Gitlab::HttpIO).not_to receive(:new)
+
+ strategy.execute(user, project)
+
+ expect(a_request(:post, example_url)).to have_been_made
+ end
+ end
+
+ context 'when object store is enabled' do
+ before do
+ object_store_url = 'http://object-storage/project.tar.gz'
+ stub_uploads_object_storage(FileUploader)
+ stub_request(:get, object_store_url)
+ stub_request(:post, example_url)
+ allow(import_export_upload.export_file).to receive(:url).and_return(object_store_url)
+ allow(import_export_upload.export_file).to receive(:file_storage?).and_return(false)
+ end
+
+ it 'reads file using Gitlab::HttpIO and uploads to external url' do
+ expect_next_instance_of(Gitlab::HttpIO) do |http_io|
+ expect(http_io).to receive(:read).and_call_original
+ end
+ expect(Gitlab::ImportExport::RemoteStreamUpload).not_to receive(:new)
+
+ strategy.execute(user, project)
+
+ expect(a_request(:post, example_url)).to have_been_made
+ end
+ end
+
+ context 'when `import_export_web_upload_stream` feature is enabled' do
+ before do
+ stub_feature_flags(import_export_web_upload_stream: true)
+ end
+
+ context 'when remote object store is disabled' do
+ it 'reads file from disk and uploads to external url' do
+ stub_request(:post, example_url).to_return(status: 200)
+ expect(Gitlab::ImportExport::RemoteStreamUpload).not_to receive(:new)
+ expect(Gitlab::HttpIO).not_to receive(:new)
+
+ strategy.execute(user, project)
+
+ expect(a_request(:post, example_url)).to have_been_made
+ end
+ end
+
+ context 'when object store is enabled' do
+ let(:object_store_url) { 'http://object-storage/project.tar.gz' }
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+
+ allow(import_export_upload.export_file).to receive(:url).and_return(object_store_url)
+ allow(import_export_upload.export_file).to receive(:file_storage?).and_return(false)
+ end
+
+ it 'uploads file as a remote stream' do
+ arguments = {
+ download_url: object_store_url,
+ upload_url: example_url,
+ options: {
+ upload_method: :post,
+ upload_content_type: 'application/gzip'
+ }
+ }
+
+ expect_next_instance_of(Gitlab::ImportExport::RemoteStreamUpload, arguments) do |remote_stream_upload|
+ expect(remote_stream_upload).to receive(:execute)
+ end
+ expect(Gitlab::HttpIO).not_to receive(:new)
+
+ strategy.execute(user, project)
+ end
+
+ context 'when upload as remote stream raises an exception' do
+ before do
+ allow_next_instance_of(Gitlab::ImportExport::RemoteStreamUpload) do |remote_stream_upload|
+ allow(remote_stream_upload).to receive(:execute).and_raise(
+ Gitlab::ImportExport::RemoteStreamUpload::StreamError.new('Exception error message', 'Response body')
+ )
+ end
+ end
+
+ it 'logs the exception and stores the error message' do
+ expect_next_instance_of(Gitlab::Export::Logger) do |logger|
+ expect(logger).to receive(:error).ordered.with(
+ {
+ project_id: project.id,
+ project_name: project.name,
+ message: 'Exception error message',
+ response_body: 'Response body'
+ }
+ )
+
+ expect(logger).to receive(:error).ordered.with(
+ {
+ project_id: project.id,
+ project_name: project.name,
+ message: 'After export strategy failed',
+ 'exception.class' => 'Gitlab::ImportExport::RemoteStreamUpload::StreamError',
+ 'exception.message' => 'Exception error message',
+ 'exception.backtrace' => anything
+ }
+ )
+ end
+
+ strategy.execute(user, project)
+
+ expect(project.import_export_shared.errors.first).to eq('Exception error message')
+ end
+ 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 8c1e60e78b0..9aec3271913 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -140,6 +140,12 @@ project_members:
- project
- member_task
- member_namespace
+- member_role
+member_roles:
+- members
+- namespace
+- base_access_level
+- download_code
merge_requests:
- status_check_responses
- subscriptions
@@ -591,6 +597,7 @@ project:
- alert_management_alerts
- repository_storage_moves
- freeze_periods
+- pumble_integration
- webex_teams_integration
- build_report_results
- vulnerability_statistic
@@ -621,6 +628,7 @@ project:
- security_trainings
- vulnerability_reads
- build_artifacts_size_refresh
+- project_callouts
award_emoji:
- awardable
- user
@@ -646,6 +654,11 @@ search_data:
merge_request_assignees:
- merge_request
- assignee
+- updated_state_by
+merge_request_reviewers:
+- merge_request
+- reviewer
+- updated_state_by
lfs_file_locks:
- user
project_badges:
@@ -805,3 +818,6 @@ bulk_import_export:
- group
service_desk_setting:
- file_template_project
+approvals:
+ - user
+ - merge_request
diff --git a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
index b8999f608b1..4ef8f4b5d76 100644
--- a/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
@@ -139,6 +139,30 @@ RSpec.describe Gitlab::ImportExport::Base::RelationFactory do
expect(subject.value).to be_nil
end
end
+
+ context 'with duplicate assignees' do
+ let(:relation_sym) { :issues }
+ let(:relation_hash) do
+ { "title" => "title", "state" => "opened" }.merge(issue_assignees)
+ end
+
+ context 'when duplicate assignees are present' do
+ let(:issue_assignees) do
+ {
+ "issue_assignees" => [
+ IssueAssignee.new(user_id: 1),
+ IssueAssignee.new(user_id: 2),
+ IssueAssignee.new(user_id: 1),
+ { user_id: 3 }
+ ]
+ }
+ end
+
+ it 'removes duplicate assignees' do
+ expect(subject.issue_assignees.map(&:user_id)).to contain_exactly(1, 2)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb b/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb
index 7c84b9604a6..9f1b15aa049 100644
--- a/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb
@@ -58,8 +58,8 @@ RSpec.describe Gitlab::ImportExport::Base::RelationObjectSaver do
end
context 'when subrelation collection count is small' do
- let(:notes) { build_list(:note, 2, project: project, importing: true) }
- let(:relation_object) { build(:issue, project: project, notes: notes) }
+ let(:note) { build(:note, project: project, importing: true) }
+ let(:relation_object) { build(:issue, project: project, notes: [note]) }
let(:relation_definition) { { 'notes' => {} } }
it 'saves subrelation as part of the relation object itself' do
@@ -68,7 +68,7 @@ RSpec.describe Gitlab::ImportExport::Base::RelationObjectSaver do
saver.execute
issue = project.issues.last
- expect(issue.notes.count).to eq(2)
+ expect(issue.notes.count).to eq(1)
end
end
diff --git a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
index dea584e5019..9af72cc0dea 100644
--- a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
+++ b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
@@ -51,10 +51,11 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
shared_examples 'logs raised exception and terminates validator process group' do
let(:std) { double(:std, close: nil, value: nil) }
let(:wait_thr) { double }
+ let(:wait_threads) { [wait_thr, wait_thr] }
before do
allow(Process).to receive(:getpgid).and_return(2)
- allow(Open3).to receive(:popen3).and_return([std, std, std, wait_thr])
+ allow(Open3).to receive(:pipeline_r).and_return([std, wait_threads])
allow(wait_thr).to receive(:[]).with(:pid).and_return(1)
allow(wait_thr).to receive(:value).and_raise(exception)
end
@@ -67,7 +68,7 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
import_upload_archive_size: File.size(filepath),
message: error_message
)
- expect(Process).to receive(:kill).with(-1, 2)
+ expect(Process).to receive(:kill).with(-1, 2).twice
expect(subject.valid?).to eq(false)
end
end
diff --git a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
index 9b01005c2e9..89ae869ae86 100644
--- a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
@@ -204,19 +204,5 @@ RSpec.describe Gitlab::ImportExport::Group::TreeRestorer do
end
end
- context 'when import_relation_object_persistence feature flag is enabled' do
- before do
- stub_feature_flags(import_relation_object_persistence: true)
- end
-
- include_examples 'group restoration'
- end
-
- context 'when import_relation_object_persistence feature flag is disabled' do
- before do
- stub_feature_flags(import_relation_object_persistence: false)
- end
-
- include_examples 'group restoration'
- end
+ include_examples 'group restoration'
end
diff --git a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
index 90966cb4915..51c0008b2b4 100644
--- a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
+++ b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
@@ -88,8 +88,8 @@ RSpec.describe 'Test coverage of the Project Import' do
def relations_from_json(json_file)
json = Gitlab::Json.parse(IO.read(json_file))
- [].tap {|res| gather_relations({ project: json }, res, [])}
- .map {|relation_names| relation_names.join('.')}
+ [].tap { |res| gather_relations({ project: json }, res, []) }
+ .map { |relation_names| relation_names.join('.') }
end
def gather_relations(item, res, path)
@@ -103,7 +103,7 @@ RSpec.describe 'Test coverage of the Project Import' do
end
end
when Array
- item.each {|i| gather_relations(i, res, path)}
+ item.each { |i| gather_relations(i, res, path) }
end
end
diff --git a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
index 9be95591ae9..452d63d548e 100644
--- a/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/ndjson_writer_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe Gitlab::ImportExport::Json::NdjsonWriter do
file_path = File.join(path, exportable_path, "#{relation}.ndjson")
subject.write_relation(exportable_path, relation, values[0])
- expect {subject.write_relation(exportable_path, relation, values[1])}.to raise_exception("The #{file_path} already exist")
+ expect { subject.write_relation(exportable_path, relation, values[1]) }.to raise_exception("The #{file_path} already exist")
end
end
end
diff --git a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
index 3f73a730744..3088129a732 100644
--- a/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/json/streaming_serializer_spec.rb
@@ -27,6 +27,7 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do
end
let(:exportable_path) { 'project' }
+ let(:logger) { Gitlab::Export::Logger.build }
let(:json_writer) { instance_double('Gitlab::ImportExport::Json::LegacyWriter') }
let(:hash) { { name: exportable.name, description: exportable.description }.stringify_keys }
let(:include) { [] }
@@ -42,7 +43,7 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do
end
subject do
- described_class.new(exportable, relations_schema, json_writer, exportable_path: exportable_path)
+ described_class.new(exportable, relations_schema, json_writer, exportable_path: exportable_path, logger: logger)
end
describe '#execute' do
@@ -73,6 +74,21 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do
subject.execute
end
+ it 'logs the relation name and the number of records to export' do
+ allow(json_writer).to receive(:write_relation_array)
+ allow(logger).to receive(:info)
+
+ subject.execute
+
+ expect(logger).to have_received(:info).with(
+ importer: 'Import/Export',
+ message: "Exporting issues relation. Number of records to export: 16",
+ project_id: exportable.id,
+ project_name: exportable.name,
+ project_path: exportable.full_path
+ )
+ end
+
context 'default relation ordering' do
it 'orders exported issues by primary key(:id)' do
expected_issues = exportable.issues.reorder(:id).map(&:to_json)
@@ -138,6 +154,21 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do
subject.execute
end
+
+ it 'logs the relation name' do
+ allow(json_writer).to receive(:write_relation)
+ allow(logger).to receive(:info)
+
+ subject.execute
+
+ expect(logger).to have_received(:info).with(
+ importer: 'Import/Export',
+ message: 'Exporting group relation',
+ project_id: exportable.id,
+ project_name: exportable.name,
+ project_path: exportable.full_path
+ )
+ end
end
context 'with array relation' do
@@ -155,6 +186,21 @@ RSpec.describe Gitlab::ImportExport::Json::StreamingSerializer do
subject.execute
end
+
+ it 'logs the relation name and the number of records to export' do
+ allow(json_writer).to receive(:write_relation_array)
+ allow(logger).to receive(:info)
+
+ subject.execute
+
+ expect(logger).to have_received(:info).with(
+ importer: 'Import/Export',
+ message: 'Exporting project_members relation. Number of records to export: 1',
+ project_id: exportable.id,
+ project_name: exportable.name,
+ project_path: exportable.full_path
+ )
+ end
end
describe 'load balancing' do
diff --git a/spec/lib/gitlab/import_export/log_util_spec.rb b/spec/lib/gitlab/import_export/log_util_spec.rb
new file mode 100644
index 00000000000..2b1a4b7bb61
--- /dev/null
+++ b/spec/lib/gitlab/import_export/log_util_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ImportExport::LogUtil do
+ describe '.exportable_to_log_payload' do
+ subject { described_class.exportable_to_log_payload(exportable) }
+
+ context 'when exportable is a group' do
+ let(:exportable) { build_stubbed(:group) }
+
+ it 'returns hash with group keys' do
+ expect(subject).to be_a(Hash)
+ expect(subject.keys).to eq(%i[group_id group_name group_path])
+ end
+ end
+
+ context 'when exportable is a project' do
+ let(:exportable) { build_stubbed(:project) }
+
+ it 'returns hash with project keys' do
+ expect(subject).to be_a(Hash)
+ expect(subject.keys).to eq(%i[project_id project_name project_path])
+ end
+ end
+
+ context 'when exportable is a new record' do
+ let(:exportable) { Project.new }
+
+ it 'returns empty hash' do
+ expect(subject).to eq({})
+ end
+ end
+
+ context 'when exportable is an unexpected type' do
+ let(:exportable) { build_stubbed(:issue) }
+
+ it 'returns empty hash' do
+ expect(subject).to eq({})
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/project/relation_saver_spec.rb b/spec/lib/gitlab/import_export/project/relation_saver_spec.rb
new file mode 100644
index 00000000000..dec51b3afd1
--- /dev/null
+++ b/spec/lib/gitlab/import_export/project/relation_saver_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ImportExport::Project::RelationSaver do
+ include ImportExport::CommonUtil
+
+ subject(:relation_saver) { described_class.new(project: project, shared: shared, relation: relation) }
+
+ let_it_be(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
+ let_it_be(:project) { setup_project }
+
+ let(:relation) { Projects::ImportExport::RelationExport::ROOT_RELATION }
+ let(:shared) do
+ shared = project.import_export_shared
+ allow(shared).to receive(:export_path).and_return(export_path)
+ shared
+ end
+
+ after do
+ FileUtils.rm_rf(export_path)
+ end
+
+ describe '#save' do
+ context 'when relation is the root node' do
+ let(:relation) { Projects::ImportExport::RelationExport::ROOT_RELATION }
+
+ it 'serializes the root node as a json file in the export path' do
+ relation_saver.save # rubocop:disable Rails/SaveBang
+
+ json = read_json(File.join(shared.export_path, 'project.json'))
+ expect(json).to include({ 'description' => 'Project description' })
+ end
+
+ it 'serializes only allowed attributes' do
+ relation_saver.save # rubocop:disable Rails/SaveBang
+
+ json = read_json(File.join(shared.export_path, 'project.json'))
+ expect(json).to include({ 'description' => 'Project description' })
+ expect(json.keys).not_to include('name')
+ end
+
+ it 'successfuly serializes without errors' do
+ result = relation_saver.save # rubocop:disable Rails/SaveBang
+
+ expect(result).to eq(true)
+ expect(shared.errors).to be_empty
+ end
+ end
+
+ context 'when relation is a child node' do
+ let(:relation) { 'labels' }
+
+ it 'serializes the child node as a ndjson file in the export path inside the project folder' do
+ relation_saver.save # rubocop:disable Rails/SaveBang
+
+ ndjson = read_ndjson(File.join(shared.export_path, 'project', "#{relation}.ndjson"))
+ expect(ndjson.first).to include({ 'title' => 'Label 1' })
+ expect(ndjson.second).to include({ 'title' => 'Label 2' })
+ end
+
+ it 'serializes only allowed attributes' do
+ relation_saver.save # rubocop:disable Rails/SaveBang
+
+ ndjson = read_ndjson(File.join(shared.export_path, 'project', "#{relation}.ndjson"))
+ expect(ndjson.first.keys).not_to include('description_html')
+ end
+
+ it 'successfuly serializes without errors' do
+ result = relation_saver.save # rubocop:disable Rails/SaveBang
+
+ expect(result).to eq(true)
+ expect(shared.errors).to be_empty
+ end
+ end
+
+ context 'when relation name is not supported' do
+ let(:relation) { 'unknown' }
+
+ it 'returns false and register the error' do
+ result = relation_saver.save # rubocop:disable Rails/SaveBang
+
+ expect(result).to eq(false)
+ expect(shared.errors).to be_present
+ end
+ end
+
+ context 'when an exception occurs during serialization' do
+ it 'returns false and register the exception error message' do
+ allow_next_instance_of(Gitlab::ImportExport::Json::StreamingSerializer) do |serializer|
+ allow(serializer).to receive(:serialize_root).and_raise('Error!')
+ end
+
+ result = relation_saver.save # rubocop:disable Rails/SaveBang
+
+ expect(result).to eq(false)
+ expect(shared.errors).to include('Error!')
+ end
+ end
+ end
+
+ def setup_project
+ project = create(:project,
+ description: 'Project description'
+ )
+
+ create(:label, project: project, title: 'Label 1')
+ create(:label, project: project, title: 'Label 2')
+
+ project
+ end
+
+ def read_json(path)
+ Gitlab::Json.parse(IO.read(path))
+ end
+
+ def read_ndjson(path)
+ relations = []
+ File.foreach(path) do |line|
+ json = Gitlab::Json.parse(line)
+ relations << json
+ end
+ relations
+ end
+end
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 157cd408da9..47d7555c8f4 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -254,6 +254,16 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
end
end
+ it 'has multiple merge request assignees' do
+ expect(MergeRequest.find_by(title: 'MR1').assignees).to contain_exactly(@user, *@existing_members)
+ expect(MergeRequest.find_by(title: 'MR2').assignees).to be_empty
+ end
+
+ it 'has multiple merge request reviewers' do
+ expect(MergeRequest.find_by(title: 'MR1').reviewers).to contain_exactly(@user, *@existing_members)
+ expect(MergeRequest.find_by(title: 'MR2').reviewers).to be_empty
+ end
+
it 'has labels associated to label links, associated to issues' do
expect(Label.first.label_links.first.target).not_to be_nil
end
@@ -262,6 +272,11 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
expect(ProjectLabel.count).to eq(3)
end
+ it 'has merge request approvals' do
+ expect(MergeRequest.find_by(title: 'MR1').approvals.pluck(:user_id)).to contain_exactly(@user.id, *@existing_members.map(&:id))
+ expect(MergeRequest.find_by(title: 'MR2').approvals).to be_empty
+ end
+
it 'has no group labels' do
expect(GroupLabel.count).to eq(0)
end
@@ -589,7 +604,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
it 'issue system note metadata restored successfully' do
note_content = 'created merge request !1 to address this issue'
- note = project.issues.first.notes.find { |n| n.note.match(/#{note_content}/)}
+ note = project.issues.first.notes.find { |n| n.note.match(/#{note_content}/) }
expect(note.noteable_type).to eq('Issue')
expect(note.system).to eq(true)
@@ -1085,35 +1100,13 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
end
end
- context 'when import_relation_object_persistence feature flag is enabled' do
- before do
- stub_feature_flags(import_relation_object_persistence: true)
- end
-
- context 'enable ndjson import' do
- it_behaves_like 'project tree restorer work properly', :legacy_reader, true
+ context 'enable ndjson import' do
+ it_behaves_like 'project tree restorer work properly', :legacy_reader, true
- it_behaves_like 'project tree restorer work properly', :ndjson_reader, true
- end
-
- context 'disable ndjson import' do
- it_behaves_like 'project tree restorer work properly', :legacy_reader, false
- end
+ it_behaves_like 'project tree restorer work properly', :ndjson_reader, true
end
- context 'when import_relation_object_persistence feature flag is disabled' do
- before do
- stub_feature_flags(import_relation_object_persistence: false)
- end
-
- context 'enable ndjson import' do
- it_behaves_like 'project tree restorer work properly', :legacy_reader, true
-
- it_behaves_like 'project tree restorer work properly', :ndjson_reader, true
- end
-
- context 'disable ndjson import' do
- it_behaves_like 'project tree restorer work properly', :legacy_reader, false
- end
+ context 'disable ndjson import' do
+ it_behaves_like 'project tree restorer work properly', :legacy_reader, false
end
end
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 ba781ae78b7..15108d28bf2 100644
--- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -68,6 +68,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
it 'has merge request\'s milestones' do
expect(subject.first['milestone']).not_to be_empty
end
+
it 'has merge request\'s source branch SHA' do
expect(subject.first['source_branch_sha']).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0')
end
@@ -100,9 +101,30 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
expect(subject.first['notes'].first['author']).not_to be_empty
end
+ it 'has merge request approvals' do
+ approval = subject.first['approvals'].first
+
+ expect(approval).not_to be_nil
+ expect(approval['user_id']).to eq(user.id)
+ end
+
it 'has merge request resource label events' do
expect(subject.first['resource_label_events']).not_to be_empty
end
+
+ it 'has merge request assignees' do
+ reviewer = subject.first['merge_request_assignees'].first
+
+ expect(reviewer).not_to be_nil
+ expect(reviewer['user_id']).to eq(user.id)
+ end
+
+ it 'has merge request reviewers' do
+ reviewer = subject.first['merge_request_reviewers'].first
+
+ expect(reviewer).not_to be_nil
+ expect(reviewer['user_id']).to eq(user.id)
+ end
end
context 'with snippets' do
@@ -404,7 +426,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
context 'when streaming has to retry', :aggregate_failures do
let(:shared) { double('shared', export_path: exportable_path) }
- let(:logger) { Gitlab::Import::Logger.build }
+ let(:logger) { Gitlab::Export::Logger.build }
let(:serializer) { double('serializer') }
let(:error_class) { Net::OpenTimeout }
let(:info_params) do
@@ -468,7 +490,8 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
create(:label_link, label: group_label, target: issue)
create(:label_priority, label: group_label, priority: 1)
milestone = create(:milestone, project: project)
- merge_request = create(:merge_request, source_project: project, milestone: milestone)
+ merge_request = create(:merge_request, source_project: project, milestone: milestone, assignees: [user], reviewers: [user])
+ create(:approval, merge_request: merge_request, user: user)
ci_build = create(:ci_build, project: project, when: nil)
ci_build.pipeline.update!(project: project)
diff --git a/spec/lib/gitlab/import_export/remote_stream_upload_spec.rb b/spec/lib/gitlab/import_export/remote_stream_upload_spec.rb
new file mode 100644
index 00000000000..b1bc6b7eeaf
--- /dev/null
+++ b/spec/lib/gitlab/import_export/remote_stream_upload_spec.rb
@@ -0,0 +1,232 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::ImportExport::RemoteStreamUpload do
+ include StubRequests
+
+ subject do
+ described_class.new(
+ download_url: download_url,
+ upload_url: upload_url,
+ options: {
+ upload_method: upload_method,
+ upload_content_type: upload_content_type
+ }
+ )
+ end
+
+ let(:download_url) { 'http://object-storage/file.txt' }
+ let(:upload_url) { 'http://example.com/file.txt' }
+ let(:upload_method) { :post }
+ let(:upload_content_type) { 'text/plain' }
+
+ describe '#execute' do
+ context 'when download request and upload request return 200' do
+ it 'uploads the downloaded content' do
+ stub_request(:get, download_url).to_return(status: 200, body: 'ABC', headers: { 'Content-Length' => 3 })
+ stub_request(:post, upload_url)
+
+ subject.execute
+
+ expect(
+ a_request(:post, upload_url).with(
+ body: 'ABC', headers: { 'Content-Length' => 3, 'Content-Type' => 'text/plain' }
+ )
+ ).to have_been_made
+ end
+ end
+
+ context 'when upload method is put' do
+ let(:upload_method) { :put }
+
+ it 'uploads using the put method' do
+ stub_request(:get, download_url).to_return(status: 200, body: 'ABC', headers: { 'Content-Length' => 3 })
+ stub_request(:put, upload_url)
+
+ subject.execute
+
+ expect(
+ a_request(:put, upload_url).with(
+ body: 'ABC', headers: { 'Content-Length' => 3, 'Content-Type' => 'text/plain' }
+ )
+ ).to have_been_made
+ end
+ end
+
+ context 'when download request does not return 200' do
+ it do
+ stub_request(:get, download_url).to_return(status: 404)
+
+ expect { subject.execute }.to raise_error(
+ Gitlab::ImportExport::RemoteStreamUpload::StreamError,
+ "Invalid response code while downloading file. Code: 404"
+ )
+ end
+ end
+
+ context 'when upload request does not returns 200' do
+ it do
+ stub_request(:get, download_url).to_return(status: 200, body: 'ABC', headers: { 'Content-Length' => 3 })
+ stub_request(:post, upload_url).to_return(status: 403)
+
+ expect { subject.execute }.to raise_error(
+ Gitlab::ImportExport::RemoteStreamUpload::StreamError,
+ "Invalid response code while uploading file. Code: 403"
+ )
+ end
+ end
+
+ context 'when download URL is a local address' do
+ let(:download_url) { 'http://127.0.0.1/file.txt' }
+
+ before do
+ stub_request(:get, download_url)
+ stub_request(:post, upload_url)
+ end
+
+ it 'raises error' do
+ expect { subject.execute }.to raise_error(
+ Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://127.0.0.1/file.txt' is blocked: Requests to localhost are not allowed"
+ )
+ end
+
+ context 'when local requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it 'raises does not error' do
+ expect { subject.execute }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when download URL is a local network' do
+ let(:download_url) { 'http://172.16.0.0/file.txt' }
+
+ before do
+ stub_request(:get, download_url)
+ stub_request(:post, upload_url)
+ end
+
+ it 'raises error' do
+ expect { subject.execute }.to raise_error(
+ Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://172.16.0.0/file.txt' is blocked: Requests to the local network are not allowed"
+ )
+ end
+
+ context 'when local network requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it 'raises does not error' do
+ expect { subject.execute }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when upload URL is a local address' do
+ let(:upload_url) { 'http://127.0.0.1/file.txt' }
+
+ before do
+ stub_request(:get, download_url)
+ stub_request(:post, upload_url)
+ end
+
+ it 'raises error' do
+ stub_request(:get, download_url)
+
+ expect { subject.execute }.to raise_error(
+ Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://127.0.0.1/file.txt' is blocked: Requests to localhost are not allowed"
+ )
+ end
+
+ context 'when local requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it 'raises does not error' do
+ expect { subject.execute }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when upload URL it is a request to local network' do
+ let(:upload_url) { 'http://172.16.0.0/file.txt' }
+
+ before do
+ stub_request(:get, download_url)
+ stub_request(:post, upload_url)
+ end
+
+ it 'raises error' do
+ expect { subject.execute }.to raise_error(
+ Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://172.16.0.0/file.txt' is blocked: Requests to the local network are not allowed"
+ )
+ end
+
+ context 'when local network requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it 'raises does not error' do
+ expect { subject.execute }.not_to raise_error
+ end
+ end
+ end
+
+ context 'when upload URL resolves to a local address' do
+ let(:upload_url) { 'http://example.com/file.txt' }
+
+ it 'raises error' do
+ stub_request(:get, download_url)
+ stub_full_request(upload_url, ip_address: '127.0.0.1', method: upload_method)
+
+ expect { subject.execute }.to raise_error(
+ Gitlab::HTTP::BlockedUrlError,
+ "URL 'http://example.com/file.txt' is blocked: Requests to localhost are not allowed"
+ )
+ end
+ end
+ end
+
+ describe Gitlab::ImportExport::RemoteStreamUpload::ChunkStream do
+ describe 'StringIO#copy_stream compatibility' do
+ it 'copies all chunks' do
+ chunks = %w[ABC EFD].to_enum
+ chunk_stream = described_class.new(chunks)
+ new_stream = StringIO.new
+
+ IO.copy_stream(chunk_stream, new_stream)
+ new_stream.rewind
+
+ expect(new_stream.read).to eq('ABCEFD')
+ end
+
+ context 'with chunks smaller and bigger than buffer size' do
+ before do
+ stub_const('Gitlab::ImportExport::RemoteStreamUpload::ChunkStream::DEFAULT_BUFFER_SIZE', 4)
+ end
+
+ it 'copies all chunks' do
+ chunks = %w[A BC DEF GHIJ KLMNOPQ RSTUVWXYZ].to_enum
+ chunk_stream = described_class.new(chunks)
+ new_stream = StringIO.new
+
+ IO.copy_stream(chunk_stream, new_stream)
+ new_stream.rewind
+
+ expect(new_stream.read).to eq('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index bd60bb53d49..6cfc24a8996 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -521,7 +521,6 @@ Project:
- star_count
- ci_id
- shared_runners_enabled
-- build_coverage_regex
- build_allow_git_fetchs
- build_timeout
- pending_delete
@@ -584,6 +583,9 @@ ProjectFeature:
- security_and_compliance_access_level
- container_registry_access_level
- package_registry_access_level
+- environments_access_level
+- feature_flags_access_level
+- releases_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:
@@ -741,6 +743,14 @@ MergeRequestAssignee:
- id
- user_id
- merge_request_id
+- created_at
+- state
+MergeRequestReviewer:
+- id
+- user_id
+- merge_request_id
+- created_at
+- state
ProjectMetricsSetting:
- project_id
- external_dashboard_url
@@ -903,3 +913,7 @@ MergeRequest::CleanupSchedule:
- completed_at
- created_at
- updated_at
+Approval:
+ - user_id
+ - created_at
+ - updated_at
diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb
index 1945156ca59..408ed3a2176 100644
--- a/spec/lib/gitlab/import_export/shared_spec.rb
+++ b/spec/lib/gitlab/import_export/shared_spec.rb
@@ -68,12 +68,18 @@ RSpec.describe Gitlab::ImportExport::Shared do
expect(subject.errors).to eq(['Error importing into [FILTERED] Permission denied @ unlink_internal - [FILTERED]'])
end
- it 'updates the import JID' do
+ it 'tracks exception' do
import_state = create(:import_state, project: project, jid: 'jid-test')
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
- .with(error, hash_including(import_jid: import_state.jid))
+ .with(error, hash_including(
+ importer: 'Import/Export',
+ project_id: project.id,
+ project_name: project.name,
+ project_path: project.full_path,
+ import_jid: import_state.jid
+ ))
subject.error(error)
end
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index 9e69e04b17c..14c62edb786 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::ImportExport::VersionChecker do
end
context 'newer version' do
- let(:version) { '900.0'}
+ let(:version) { '900.0' }
it 'returns false if export version is newer' do
expect(described_class.check!(shared: shared)).to be false
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index 79d626386d4..4fa9079144d 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -195,6 +195,28 @@ RSpec.describe Gitlab::InstrumentationHelper do
expect(payload[:uploaded_file_size_bytes]).to eq(uploaded_file.size)
end
end
+
+ context 'when an api call to the search api is made' do
+ before do
+ Gitlab::Instrumentation::GlobalSearchApi.set_information(
+ type: 'basic',
+ level: 'global',
+ scope: 'issues',
+ search_duration_s: 0.1
+ )
+ end
+
+ it 'adds search data' do
+ subject
+
+ expect(payload).to include({
+ 'meta.search.type' => 'basic',
+ 'meta.search.level' => 'global',
+ 'meta.search.scope' => 'issues',
+ global_search_duration_s: 0.1
+ })
+ end
+ end
end
describe 'duration calculations' do
diff --git a/spec/lib/gitlab/jira/dvcs_spec.rb b/spec/lib/gitlab/jira/dvcs_spec.rb
index 09e777b38ea..76d81343875 100644
--- a/spec/lib/gitlab/jira/dvcs_spec.rb
+++ b/spec/lib/gitlab/jira/dvcs_spec.rb
@@ -24,8 +24,8 @@ RSpec.describe Gitlab::Jira::Dvcs do
end
describe '.encode_project_name' do
- let(:group) { create(:group)}
- let(:project) { create(:project, group: group)}
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
context 'root group' do
it 'returns project path' do
@@ -34,7 +34,7 @@ RSpec.describe Gitlab::Jira::Dvcs do
end
context 'nested group' do
- let(:group) { create(:group, :nested)}
+ let(:group) { create(:group, :nested) }
it 'returns encoded project full path' do
expect(described_class.encode_project_name(project)).to eq(described_class.encode_slash(project.full_path))
diff --git a/spec/lib/gitlab/jira_import/issues_importer_spec.rb b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
index 1bc052ee0b6..a2a482dde7c 100644
--- a/spec/lib/gitlab/jira_import/issues_importer_spec.rb
+++ b/spec/lib/gitlab/jira_import/issues_importer_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
context 'with results returned' do
jira_issue = Struct.new(:id)
- let_it_be(:jira_issues) { [jira_issue.new(1), jira_issue.new(2)] }
+ let_it_be(:jira_issues) { [jira_issue.new(1), jira_issue.new(2), jira_issue.new(3)] }
def mock_issue_serializer(count, raise_exception_on_even_mocks: false)
serializer = instance_double(Gitlab::JiraImport::IssueSerializer, execute: { key: 'data' })
@@ -125,6 +125,47 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
expect(Gitlab::JiraImport.get_issues_next_start_at(project.id)).to eq(2)
end
end
+
+ context 'when number of issues is above the threshold' do
+ before do
+ stub_const("#{described_class.name}::JIRA_IMPORT_THRESHOLD", 2)
+ stub_const("#{described_class.name}::JIRA_IMPORT_PAUSE_LIMIT", 1)
+ allow(Gitlab::ErrorTracking).to receive(:track_exception)
+ allow_next_instance_of(Gitlab::JobWaiter) do |job_waiter|
+ allow(job_waiter).to receive(:wait).with(5).and_return(job_waiter.wait(0.1))
+ end
+ end
+
+ it 'schedules 2 import jobs with two pause points' do
+ expect(subject).to receive(:fetch_issues).with(0).and_return([jira_issues[0], jira_issues[1], jira_issues[2]])
+ expect(Gitlab::JiraImport::ImportIssueWorker).to receive(:perform_async).exactly(3).times
+ expect(Gitlab::JiraImport::ImportIssueWorker)
+ .to receive(:queue_size)
+ .exactly(6).times
+ .and_return(1, 2, 3, 2, 1, 0)
+
+ mock_issue_serializer(3)
+
+ expect(subject.execute).to have_received(:wait).with(5).twice
+ end
+
+ it 'tracks the exception if the queue size does not reduce' do
+ expect(subject).to receive(:fetch_issues).with(0).and_return([jira_issues[0]])
+ expect(Gitlab::JiraImport::ImportIssueWorker).not_to receive(:perform_async)
+ expect(Gitlab::JiraImport::ImportIssueWorker)
+ .to receive(:queue_size)
+ .exactly(11).times
+ .and_return(3)
+
+ mock_issue_serializer(1)
+
+ expect(subject.execute).to have_received(:wait).with(5).exactly(10).times
+ expect(Gitlab::ErrorTracking)
+ .to have_received(:track_exception)
+ .with(described_class::RetriesExceededError, { project_id: project.id })
+ .once
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/kubernetes/rollout_status_spec.rb b/spec/lib/gitlab/kubernetes/rollout_status_spec.rb
index 8ed9fdd799c..21d345f0739 100644
--- a/spec/lib/gitlab/kubernetes/rollout_status_spec.rb
+++ b/spec/lib/gitlab/kubernetes/rollout_status_spec.rb
@@ -213,7 +213,7 @@ RSpec.describe Gitlab::Kubernetes::RolloutStatus do
let(:specs) { specs_half_finished }
- it { is_expected.to be_falsy}
+ it { is_expected.to be_falsy }
end
end
diff --git a/spec/lib/gitlab/mail_room/mail_room_spec.rb b/spec/lib/gitlab/mail_room/mail_room_spec.rb
index 06a25be757e..0c2c9b89005 100644
--- a/spec/lib/gitlab/mail_room/mail_room_spec.rb
+++ b/spec/lib/gitlab/mail_room/mail_room_spec.rb
@@ -246,7 +246,7 @@ RSpec.describe Gitlab::MailRoom do
redis_url: "localhost",
redis_db: 99,
namespace: "resque:gitlab",
- queue: "email_receiver",
+ queue: "default",
worker: "EmailReceiverWorker",
sentinels: [{ host: "localhost", port: 1234 }]
}
@@ -259,7 +259,7 @@ RSpec.describe Gitlab::MailRoom do
redis_url: "localhost",
redis_db: 99,
namespace: "resque:gitlab",
- queue: "service_desk_email_receiver",
+ queue: "default",
worker: "ServiceDeskEmailReceiverWorker",
sentinels: [{ host: "localhost", port: 1234 }]
}
diff --git a/spec/lib/gitlab/memory/jemalloc_spec.rb b/spec/lib/gitlab/memory/jemalloc_spec.rb
index 8847516b52c..482ac6e5802 100644
--- a/spec/lib/gitlab/memory/jemalloc_spec.rb
+++ b/spec/lib/gitlab/memory/jemalloc_spec.rb
@@ -28,11 +28,12 @@ RSpec.describe Gitlab::Memory::Jemalloc do
describe '.dump_stats' do
it 'writes stats JSON file' do
- described_class.dump_stats(path: outdir, format: format)
+ file_path = described_class.dump_stats(path: outdir, format: format)
file = Dir.entries(outdir).find { |e| e.match(/jemalloc_stats\.#{$$}\.\d+\.json$/) }
expect(file).not_to be_nil
- expect(File.read(File.join(outdir, file))).to eq(output)
+ expect(file_path).to eq(File.join(outdir, file))
+ expect(File.read(file_path)).to eq(output)
end
end
end
@@ -52,12 +53,22 @@ RSpec.describe Gitlab::Memory::Jemalloc do
end
describe '.dump_stats' do
- it 'writes stats text file' do
- described_class.dump_stats(path: outdir, format: format)
+ shared_examples 'writes stats text file' do |filename_label, filename_pattern|
+ it do
+ described_class.dump_stats(path: outdir, format: format, filename_label: filename_label)
+
+ file = Dir.entries(outdir).find { |e| e.match(filename_pattern) }
+ expect(file).not_to be_nil
+ expect(File.read(File.join(outdir, file))).to eq(output)
+ end
+ end
- file = Dir.entries(outdir).find { |e| e.match(/jemalloc_stats\.#{$$}\.\d+\.txt$/) }
- expect(file).not_to be_nil
- expect(File.read(File.join(outdir, file))).to eq(output)
+ context 'when custom filename label is passed' do
+ include_examples 'writes stats text file', 'puma_0', /jemalloc_stats\.#{$$}\.puma_0\.\d+\.txt$/
+ end
+
+ context 'when custom filename label is not passed' do
+ include_examples 'writes stats text file', nil, /jemalloc_stats\.#{$$}\.\d+\.txt$/
end
end
end
diff --git a/spec/lib/gitlab/memory/reports/jemalloc_stats_spec.rb b/spec/lib/gitlab/memory/reports/jemalloc_stats_spec.rb
new file mode 100644
index 00000000000..53fae48776b
--- /dev/null
+++ b/spec/lib/gitlab/memory/reports/jemalloc_stats_spec.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Memory::Reports::JemallocStats do
+ let(:reports_dir) { '/empty-dir' }
+ let(:jemalloc_stats) { described_class.new(reports_path: reports_dir) }
+
+ describe '.run' do
+ context 'when :report_jemalloc_stats ops FF is enabled' do
+ let(:worker_id) { 'puma_1' }
+ let(:report_name) { 'report.json' }
+ let(:report_path) { File.join(reports_dir, report_name) }
+
+ before do
+ allow(Prometheus::PidProvider).to receive(:worker_id).and_return(worker_id)
+ end
+
+ it 'invokes Jemalloc.dump_stats and returns file path' do
+ expect(Gitlab::Memory::Jemalloc)
+ .to receive(:dump_stats).with(path: reports_dir, filename_label: worker_id).and_return(report_path)
+
+ expect(jemalloc_stats.run).to eq(report_path)
+ end
+
+ describe 'reports cleanup' do
+ let_it_be(:outdir) { Dir.mktmpdir }
+
+ let(:jemalloc_stats) { described_class.new(reports_path: outdir) }
+
+ before do
+ stub_env('GITLAB_DIAGNOSTIC_REPORTS_JEMALLOC_MAX_REPORTS_STORED', 3)
+ allow(Gitlab::Memory::Jemalloc).to receive(:dump_stats)
+ end
+
+ after do
+ FileUtils.rm_f(outdir)
+ end
+
+ context 'when number of reports exceeds `max_reports_stored`' do
+ let_it_be(:reports) do
+ now = Time.current
+
+ (1..5).map do |i|
+ Tempfile.new("jemalloc_stats.#{i}.worker_#{i}.#{Time.current.to_i}.json", outdir).tap do |f|
+ FileUtils.touch(f, mtime: (now + i.second).to_i)
+ end
+ end
+ end
+
+ after do
+ reports.each do |f|
+ f.close
+ f.unlink
+ rescue Errno::ENOENT
+ # Some of the files are already unlinked by the code we test; Ignore
+ end
+ end
+
+ it 'keeps only `max_reports_stored` total newest files' do
+ expect { jemalloc_stats.run }
+ .to change { Dir.entries(outdir).count { |e| e.match(/jemalloc_stats.*/) } }
+ .from(5).to(3)
+
+ # Keeps only the newest reports
+ expect(reports.last(3).all? { |r| File.exist?(r) }).to be true
+ end
+ end
+
+ context 'when number of reports does not exceed `max_reports_stored`' do
+ let_it_be(:reports) do
+ now = Time.current
+
+ (1..3).map do |i|
+ Tempfile.new("jemalloc_stats.#{i}.worker_#{i}.#{Time.current.to_i}.json", outdir).tap do |f|
+ FileUtils.touch(f, mtime: (now + i.second).to_i)
+ end
+ end
+ end
+
+ after do
+ reports.each do |f|
+ f.close
+ f.unlink
+ end
+ end
+
+ it 'does not remove any reports' do
+ expect { jemalloc_stats.run }
+ .not_to change { Dir.entries(outdir).count { |e| e.match(/jemalloc_stats.*/) } }
+ end
+ end
+ end
+ end
+
+ context 'when :report_jemalloc_stats ops FF is disabled' do
+ before do
+ stub_feature_flags(report_jemalloc_stats: false)
+ end
+
+ it 'does not run the report and returns nil' do
+ expect(Gitlab::Memory::Jemalloc).not_to receive(:dump_stats)
+
+ expect(jemalloc_stats.run).to be_nil
+ end
+ end
+ end
+
+ describe '.active?' do
+ subject(:active) { jemalloc_stats.active? }
+
+ context 'when :report_jemalloc_stats ops FF is enabled' do
+ it { is_expected.to be true }
+ end
+
+ context 'when :report_jemalloc_stats ops FF is disabled' do
+ before do
+ stub_feature_flags(report_jemalloc_stats: false)
+ end
+
+ it { is_expected.to be false }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/memory/reports_daemon_spec.rb b/spec/lib/gitlab/memory/reports_daemon_spec.rb
new file mode 100644
index 00000000000..c9562470971
--- /dev/null
+++ b/spec/lib/gitlab/memory/reports_daemon_spec.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Memory::ReportsDaemon do
+ let(:daemon) { described_class.new }
+
+ describe '#run_thread' do
+ let(:report_duration_counter) { instance_double(::Prometheus::Client::Counter) }
+ let(:file_size) { 1_000_000 }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter).and_return(report_duration_counter)
+ allow(report_duration_counter).to receive(:increment)
+
+ # make sleep no-op
+ allow(daemon).to receive(:sleep) {}
+
+ # let alive return 3 times: true, true, false
+ allow(daemon).to receive(:alive).and_return(true, true, false)
+
+ allow(File).to receive(:size).with(/#{daemon.reports_path}.*\.json/).and_return(file_size)
+ end
+
+ it 'runs reports' do
+ expect(daemon.send(:reports)).to all(receive(:run).twice.and_call_original)
+
+ daemon.send(:run_thread)
+ end
+
+ it 'logs report execution' do
+ expect(::Prometheus::PidProvider).to receive(:worker_id).at_least(:once).and_return('worker_1')
+
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ hash_including(
+ :duration_s,
+ :cpu_s,
+ perf_report_size_bytes: file_size,
+ message: 'finished',
+ pid: Process.pid,
+ worker_id: 'worker_1',
+ perf_report: 'jemalloc_stats'
+ )).twice
+
+ daemon.send(:run_thread)
+ end
+
+ context 'when the report object returns invalid file path' do
+ before do
+ allow(File).to receive(:size).with(/#{daemon.reports_path}.*\.json/).and_raise(Errno::ENOENT)
+ end
+
+ it 'logs `0` as `perf_report_size_bytes`' do
+ expect(Gitlab::AppLogger).to receive(:info).with(hash_including(perf_report_size_bytes: 0)).twice
+
+ daemon.send(:run_thread)
+ end
+ end
+
+ it 'sets real time duration gauge' do
+ expect(report_duration_counter).to receive(:increment).with({ report: 'jemalloc_stats' }, an_instance_of(Float))
+
+ daemon.send(:run_thread)
+ end
+
+ it 'allows configure and run multiple reports' do
+ # rubocop: disable RSpec/VerifiedDoubles
+ # We test how ReportsDaemon could be extended in the future
+ # We configure it with new reports classes which are not yet defined so we cannot make this an instance_double.
+ active_report_1 = double("Active Report 1", active?: true)
+ active_report_2 = double("Active Report 2", active?: true)
+ inactive_report = double("Inactive Report", active?: false)
+ # rubocop: enable RSpec/VerifiedDoubles
+
+ allow(daemon).to receive(:reports).and_return([active_report_1, inactive_report, active_report_2])
+
+ expect(active_report_1).to receive(:run).and_return('/tmp/report_1.json').twice
+ expect(active_report_2).to receive(:run).and_return('/tmp/report_2.json').twice
+ expect(inactive_report).not_to receive(:run)
+
+ daemon.send(:run_thread)
+ end
+
+ context 'sleep timers logic' do
+ it 'wakes up every (fixed interval + defined delta), sleeps between reports each cycle' do
+ stub_env('GITLAB_DIAGNOSTIC_REPORTS_SLEEP_MAX_DELTA_S', 1) # rand(1) == 0, so we will have fixed sleep interval
+ daemon = described_class.new
+ allow(daemon).to receive(:alive).and_return(true, true, false)
+
+ expect(daemon).to receive(:sleep).with(described_class::DEFAULT_SLEEP_S).ordered
+ expect(daemon).to receive(:sleep).with(described_class::DEFAULT_SLEEP_BETWEEN_REPORTS_S).ordered
+ expect(daemon).to receive(:sleep).with(described_class::DEFAULT_SLEEP_S).ordered
+ expect(daemon).to receive(:sleep).with(described_class::DEFAULT_SLEEP_BETWEEN_REPORTS_S).ordered
+
+ daemon.send(:run_thread)
+ end
+ end
+ end
+
+ describe '#stop_working' do
+ it 'changes :alive to false' do
+ expect { daemon.send(:stop_working) }.to change { daemon.send(:alive) }.from(true).to(false)
+ end
+ end
+
+ context 'timer intervals settings' do
+ context 'when no settings are set in the environment' do
+ it 'uses defaults' do
+ daemon = described_class.new
+
+ expect(daemon.sleep_s).to eq(described_class::DEFAULT_SLEEP_S)
+ expect(daemon.sleep_max_delta_s).to eq(described_class::DEFAULT_SLEEP_MAX_DELTA_S)
+ expect(daemon.sleep_between_reports_s).to eq(described_class::DEFAULT_SLEEP_BETWEEN_REPORTS_S)
+ expect(daemon.reports_path).to eq(described_class::DEFAULT_REPORTS_PATH)
+ end
+ end
+
+ context 'when settings are passed through the environment' do
+ before do
+ stub_env('GITLAB_DIAGNOSTIC_REPORTS_SLEEP_S', 100)
+ stub_env('GITLAB_DIAGNOSTIC_REPORTS_SLEEP_MAX_DELTA_S', 50)
+ stub_env('GITLAB_DIAGNOSTIC_REPORTS_SLEEP_BETWEEN_REPORTS_S', 2)
+ stub_env('GITLAB_DIAGNOSTIC_REPORTS_PATH', '/empty-dir')
+ end
+
+ it 'uses provided values' do
+ daemon = described_class.new
+
+ expect(daemon.sleep_s).to eq(100)
+ expect(daemon.sleep_max_delta_s).to eq(50)
+ expect(daemon.sleep_between_reports_s).to eq(2)
+ expect(daemon.reports_path).to eq('/empty-dir')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/memory/watchdog_spec.rb b/spec/lib/gitlab/memory/watchdog_spec.rb
index 8b82078bcb9..010f6884df3 100644
--- a/spec/lib/gitlab/memory/watchdog_spec.rb
+++ b/spec/lib/gitlab/memory/watchdog_spec.rb
@@ -14,32 +14,57 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
let(:sleep_time) { 0.1 }
let(:max_heap_fragmentation) { 0.2 }
+ # Tests should set this to control the number of loop iterations in `call`.
+ let(:watchdog_iterations) { 1 }
+
subject(:watchdog) do
described_class.new(handler: handler, logger: logger, sleep_time_seconds: sleep_time,
- max_strikes: max_strikes, max_heap_fragmentation: max_heap_fragmentation)
+ max_strikes: max_strikes, max_heap_fragmentation: max_heap_fragmentation).tap do |instance|
+ # We need to defuse `sleep` and stop the internal loop after N iterations.
+ iterations = 0
+ expect(instance).to receive(:sleep) do
+ instance.stop if (iterations += 1) >= watchdog_iterations
+ end.at_most(watchdog_iterations)
+ end
+ end
+
+ def stub_prometheus_metrics
+ allow(Gitlab::Metrics).to receive(:gauge)
+ .with(:gitlab_memwd_heap_frag_limit, anything)
+ .and_return(heap_frag_limit_gauge)
+ allow(Gitlab::Metrics).to receive(:counter)
+ .with(:gitlab_memwd_heap_frag_violations_total, anything, anything)
+ .and_return(heap_frag_violations_counter)
+ allow(Gitlab::Metrics).to receive(:counter)
+ .with(:gitlab_memwd_heap_frag_violations_handled_total, anything, anything)
+ .and_return(heap_frag_violations_handled_counter)
+
+ allow(heap_frag_limit_gauge).to receive(:set)
+ allow(heap_frag_violations_counter).to receive(:increment)
+ allow(heap_frag_violations_handled_counter).to receive(:increment)
end
before do
+ stub_prometheus_metrics
+
allow(handler).to receive(:on_high_heap_fragmentation).and_return(true)
allow(logger).to receive(:warn)
allow(logger).to receive(:info)
allow(Gitlab::Metrics::Memory).to receive(:gc_heap_fragmentation).and_return(fragmentation)
- end
- after do
- watchdog.stop
+ allow(::Prometheus::PidProvider).to receive(:worker_id).and_return('worker_1')
end
- context 'when starting up' do
+ context 'when created' do
let(:fragmentation) { 0 }
let(:max_strikes) { 0 }
it 'sets the heap fragmentation limit gauge' do
- allow(Gitlab::Metrics).to receive(:gauge).and_return(heap_frag_limit_gauge)
-
expect(heap_frag_limit_gauge).to receive(:set).with({}, max_heap_fragmentation)
+
+ watchdog
end
context 'when no settings are set in the environment' do
@@ -76,77 +101,54 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
it 'does not signal the handler' do
expect(handler).not_to receive(:on_high_heap_fragmentation)
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
end
end
context 'when process exceeds heap fragmentation threshold permanently' do
let(:fragmentation) { max_heap_fragmentation + 0.1 }
-
- before do
- allow(Gitlab::Metrics).to receive(:counter)
- .with(:gitlab_memwd_heap_frag_violations_total, anything, anything)
- .and_return(heap_frag_violations_counter)
- allow(Gitlab::Metrics).to receive(:counter)
- .with(:gitlab_memwd_heap_frag_violations_handled_total, anything, anything)
- .and_return(heap_frag_violations_handled_counter)
- allow(heap_frag_violations_counter).to receive(:increment)
- allow(heap_frag_violations_handled_counter).to receive(:increment)
- end
+ let(:max_strikes) { 3 }
context 'when process has not exceeded allowed number of strikes' do
- let(:max_strikes) { 10 }
+ let(:watchdog_iterations) { max_strikes }
it 'does not signal the handler' do
expect(handler).not_to receive(:on_high_heap_fragmentation)
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
end
it 'does not log any events' do
expect(logger).not_to receive(:warn)
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
end
it 'increments the violations counter' do
- expect(heap_frag_violations_counter).to receive(:increment)
-
- watchdog.start
+ expect(heap_frag_violations_counter).to receive(:increment).exactly(watchdog_iterations)
- sleep sleep_time * 3
+ watchdog.call
end
it 'does not increment violations handled counter' do
expect(heap_frag_violations_handled_counter).not_to receive(:increment)
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
end
end
context 'when process exceeds the allowed number of strikes' do
- let(:max_strikes) { 1 }
+ let(:watchdog_iterations) { max_strikes + 1 }
it 'signals the handler and resets strike counter' do
expect(handler).to receive(:on_high_heap_fragmentation).and_return(true)
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
expect(watchdog.strikes).to eq(0)
end
it 'logs the event' do
- expect(::Prometheus::PidProvider).to receive(:worker_id).at_least(:once).and_return('worker_1')
expect(Gitlab::Metrics::System).to receive(:memory_usage_rss).at_least(:once).and_return(1024)
expect(logger).to receive(:warn).with({
message: 'heap fragmentation limit exceeded',
@@ -161,18 +163,14 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
memwd_rss_bytes: 1024
})
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
end
it 'increments both the violations and violations handled counters' do
- expect(heap_frag_violations_counter).to receive(:increment)
+ expect(heap_frag_violations_counter).to receive(:increment).exactly(watchdog_iterations)
expect(heap_frag_violations_handled_counter).to receive(:increment)
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
end
context 'when enforce_memory_watchdog ops toggle is off' do
@@ -186,35 +184,31 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
receive(:on_high_heap_fragmentation).with(fragmentation).and_return(true)
)
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
end
end
- end
-
- context 'when handler result is true' do
- let(:max_strikes) { 1 }
- it 'considers the event handled and stops itself' do
- expect(handler).to receive(:on_high_heap_fragmentation).once.and_return(true)
+ context 'when handler result is true' do
+ it 'considers the event handled and stops itself' do
+ expect(handler).to receive(:on_high_heap_fragmentation).once.and_return(true)
+ expect(logger).to receive(:info).with(hash_including(message: 'stopped'))
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
+ end
end
- end
-
- context 'when handler result is false' do
- let(:max_strikes) { 1 }
- it 'keeps running' do
- # Return true the third time to terminate the daemon.
- expect(handler).to receive(:on_high_heap_fragmentation).and_return(false, false, true)
+ context 'when handler result is false' do
+ let(:max_strikes) { 0 } # to make sure the handler fires each iteration
+ let(:watchdog_iterations) { 3 }
- watchdog.start
+ it 'keeps running' do
+ expect(heap_frag_violations_counter).to receive(:increment).exactly(watchdog_iterations)
+ expect(heap_frag_violations_handled_counter).to receive(:increment).exactly(watchdog_iterations)
+ # Return true the third time to terminate the daemon.
+ expect(handler).to receive(:on_high_heap_fragmentation).and_return(false, false, true)
- sleep sleep_time * 4
+ watchdog.call
+ end
end
end
end
@@ -222,6 +216,7 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
context 'when process exceeds heap fragmentation threshold temporarily' do
let(:fragmentation) { max_heap_fragmentation }
let(:max_strikes) { 1 }
+ let(:watchdog_iterations) { 4 }
before do
allow(Gitlab::Metrics::Memory).to receive(:gc_heap_fragmentation).and_return(
@@ -235,9 +230,7 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
it 'does not signal the handler' do
expect(handler).not_to receive(:on_high_heap_fragmentation)
- watchdog.start
-
- sleep sleep_time * 4
+ watchdog.call
end
end
@@ -252,9 +245,7 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures, :prometheus do
it 'does not monitor heap fragmentation' do
expect(Gitlab::Metrics::Memory).not_to receive(:gc_heap_fragmentation)
- watchdog.start
-
- sleep sleep_time * 3
+ watchdog.call
end
end
end
diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb
index 83bee84df99..2e48070cb4f 100644
--- a/spec/lib/gitlab/metrics/background_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/background_transaction_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe Gitlab::Metrics::BackgroundTransaction do
end
it 'removes the transaction from the current thread upon completion' do
- transaction.run { }
+ transaction.run {}
expect(Thread.current[described_class::THREAD_KEY]).to be_nil
end
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 06ce58a9e84..d6590efcf4f 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
end
it 'removes the transaction from the current thread upon completion' do
- transaction.run { }
+ transaction.run {}
expect(Thread.current[described_class::THREAD_KEY]).to be_nil
expect(described_class.current).to be_nil
diff --git a/spec/lib/gitlab/middleware/compressed_json_spec.rb b/spec/lib/gitlab/middleware/compressed_json_spec.rb
index a07cd49c572..6d49ab58d5d 100644
--- a/spec/lib/gitlab/middleware/compressed_json_spec.rb
+++ b/spec/lib/gitlab/middleware/compressed_json_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Gitlab::Middleware::CompressedJson do
describe '#call' do
context 'with collector route' do
- let(:path) { '/api/v4/error_tracking/collector/1/store'}
+ let(:path) { '/api/v4/error_tracking/collector/1/store' }
it_behaves_like 'decompress middleware'
@@ -45,7 +45,7 @@ RSpec.describe Gitlab::Middleware::CompressedJson do
end
context 'with collector route under relative url' do
- let(:path) { '/gitlab/api/v4/error_tracking/collector/1/store'}
+ let(:path) { '/gitlab/api/v4/error_tracking/collector/1/store' }
before do
stub_config_setting(relative_url_root: '/gitlab')
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::Middleware::CompressedJson do
let(:body_limit) { Gitlab::Middleware::CompressedJson::MAXIMUM_BODY_SIZE }
let(:decompressed_input) { 'a' * (body_limit + 100) }
let(:input) { ActiveSupport::Gzip.compress(decompressed_input) }
- let(:path) { '/api/v4/error_tracking/collector/1/envelope'}
+ let(:path) { '/api/v4/error_tracking/collector/1/envelope' }
it 'reads only limited size' do
expect(middleware.call(env))
diff --git a/spec/lib/gitlab/middleware/sidekiq_web_static_spec.rb b/spec/lib/gitlab/middleware/sidekiq_web_static_spec.rb
index e6815a46a56..91c030a0f45 100644
--- a/spec/lib/gitlab/middleware/sidekiq_web_static_spec.rb
+++ b/spec/lib/gitlab/middleware/sidekiq_web_static_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Middleware::SidekiqWebStatic do
end
context 'with an /admin/sidekiq route' do
- let(:path) { '/admin/sidekiq/javascripts/application.js'}
+ let(:path) { '/admin/sidekiq/javascripts/application.js' }
it 'deletes the HTTP_X_SENDFILE_TYPE header' do
expect(app).to receive(:call)
diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb
index bc4d95738c7..92e424978ff 100644
--- a/spec/lib/gitlab/octokit/middleware_spec.rb
+++ b/spec/lib/gitlab/octokit/middleware_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe Gitlab::Octokit::Middleware do
it_behaves_like 'Public URL'
end
- context 'when the URL is a localhost adresss' do
+ context 'when the URL is a localhost address' do
let(:env) { { url: 'http://127.0.0.1' } }
context 'when localhost requests are not allowed' do
diff --git a/spec/lib/gitlab/otp_key_rotator_spec.rb b/spec/lib/gitlab/otp_key_rotator_spec.rb
index e328b190db4..e3b9f006b19 100644
--- a/spec/lib/gitlab/otp_key_rotator_spec.rb
+++ b/spec/lib/gitlab/otp_key_rotator_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe Gitlab::OtpKeyRotator do
it 'stores the calculated values in a spreadsheet' do
rotation
- expect(data).to match_array(users.map {|u| build_row(u) })
+ expect(data).to match_array(users.map { |u| build_row(u) })
end
context 'new key is too short' do
diff --git a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb
index dcb8138bdde..0bafd436bd0 100644
--- a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb
+++ b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb
@@ -126,5 +126,19 @@ RSpec.describe Gitlab::Pagination::GitalyKeysetPager do
end
end
end
+
+ context 'with "none" pagination option' do
+ let(:expected_result) { double(:result) }
+ let(:query) { { pagination: 'none' } }
+
+ it 'uses offset pagination' do
+ expect(finder).to receive(:execute).with(gitaly_pagination: false).and_return(expected_result)
+ expect(Kaminari).not_to receive(:paginate_array)
+ expect(Gitlab::Pagination::OffsetPagination).not_to receive(:new)
+
+ actual_result = pager.paginate(finder)
+ expect(actual_result).to eq(expected_result)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/pagination/keyset_spec.rb b/spec/lib/gitlab/pagination/keyset_spec.rb
index 81dc40b35d5..8885e684d8a 100644
--- a/spec/lib/gitlab/pagination/keyset_spec.rb
+++ b/spec/lib/gitlab/pagination/keyset_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::Pagination::Keyset do
describe '.available?' do
subject { described_class }
- let(:request_context) { double("request context", page: page)}
+ let(:request_context) { double("request context", page: page) }
let(:page) { double("page", order_by: order_by) }
shared_examples_for 'keyset pagination is available' do
diff --git a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
index c368b349a3c..a444e7fdf47 100644
--- a/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/conduit/response_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::PhabricatorImport::Conduit::Response do
- let(:response) { described_class.new(Gitlab::Json.parse(fixture_file('phabricator_responses/maniphest.search.json')))}
+ let(:response) { described_class.new(Gitlab::Json.parse(fixture_file('phabricator_responses/maniphest.search.json'))) }
let(:error_response) { described_class.new(Gitlab::Json.parse(fixture_file('phabricator_responses/auth_failed.json'))) }
describe '.parse!' do
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 89ddde4a01d..9083c5625d4 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -104,7 +104,7 @@ RSpec.describe Gitlab::PrometheusClient do
end
describe 'failure to reach a provided prometheus url' do
- let(:prometheus_url) {"https://prometheus.invalid.example.com/api/v1/query?query=1"}
+ let(:prometheus_url) { "https://prometheus.invalid.example.com/api/v1/query?query=1" }
shared_examples 'exceptions are raised' do
Gitlab::HTTP::HTTP_ERRORS.each do |error|
diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
index c040a70e403..e2f289041ce 100644
--- a/spec/lib/gitlab/quick_actions/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -7,10 +7,10 @@ RSpec.describe Gitlab::QuickActions::Extractor do
Class.new do
include Gitlab::QuickActions::Dsl
- command(:reopen, :open) { }
- command(:assign) { }
- command(:labels) { }
- command(:power) { }
+ command(:reopen, :open) {}
+ command(:assign) {}
+ command(:labels) {}
+ command(:power) {}
command(:noop_command)
substitution(:substitution) { 'foo' }
substitution :shrug do |comment|
diff --git a/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb b/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb
index bd167ee2e3e..8151519ddec 100644
--- a/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb
+++ b/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::RackAttack::InstrumentedCacheStore do
let(:store) { ::ActiveSupport::Cache::NullStore.new }
- subject { described_class.new(upstream_store: store)}
+ subject { described_class.new(upstream_store: store) }
where(:operation, :params, :test_proc) do
:fetch | [:key] | ->(s) { s.fetch(:key) }
diff --git a/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb b/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb
index aa604dfab71..1b6fa584e3e 100644
--- a/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb
+++ b/spec/lib/gitlab/rack_attack/user_allowlist_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::RackAttack::UserAllowlist do
using RSpec::Parameterized::TableSyntax
- subject { described_class.new(input)}
+ subject { described_class.new(input) }
where(:input, :elements) do
nil | []
diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb
index 31141ac1139..1f0ebbe107f 100644
--- a/spec/lib/gitlab/redis/cache_spec.rb
+++ b/spec/lib/gitlab/redis/cache_spec.rb
@@ -15,4 +15,16 @@ RSpec.describe Gitlab::Redis::Cache do
expect(subject.send(:raw_config_hash)).to eq(url: 'redis://localhost:6380' )
end
end
+
+ describe '.active_support_config' do
+ it 'has a default ttl of 2 weeks' do
+ expect(described_class.active_support_config[:expires_in]).to eq(2.weeks)
+ end
+
+ it 'allows configuring the TTL through an env variable' do
+ stub_env('GITLAB_RAILS_CACHE_DEFAULT_TTL_SECONDS' => '86400')
+
+ expect(described_class.active_support_config[:expires_in]).to eq(1.day)
+ end
+ end
end
diff --git a/spec/lib/gitlab/redis/hll_spec.rb b/spec/lib/gitlab/redis/hll_spec.rb
index e452e5b2f52..9cd339239bb 100644
--- a/spec/lib/gitlab/redis/hll_spec.rb
+++ b/spec/lib/gitlab/redis/hll_spec.rb
@@ -64,10 +64,10 @@ RSpec.describe Gitlab::Redis::HLL, :clean_gitlab_redis_shared_state do
let(:event_2020_33) { '2020-33-{expand_vulnerabilities}' }
let(:event_2020_34) { '2020-34-{expand_vulnerabilities}' }
- let(:entity1) { 'user_id_1'}
- let(:entity2) { 'user_id_2'}
- let(:entity3) { 'user_id_3'}
- let(:entity4) { 'user_id_4'}
+ let(:entity1) { 'user_id_1' }
+ let(:entity2) { 'user_id_2' }
+ let(:entity3) { 'user_id_3' }
+ let(:entity4) { 'user_id_4' }
before do
track_event(event_2020_32, entity1)
diff --git a/spec/lib/gitlab/redis/multi_store_spec.rb b/spec/lib/gitlab/redis/multi_store_spec.rb
index 50ebf43a05e..ef8549548d7 100644
--- a/spec/lib/gitlab/redis/multi_store_spec.rb
+++ b/spec/lib/gitlab/redis/multi_store_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe Gitlab::Redis::MultiStore do
let_it_be(:primary_store) { create_redis_store(redis_store_class.params, db: primary_db, serializer: nil) }
let_it_be(:secondary_store) { create_redis_store(redis_store_class.params, db: secondary_db, serializer: nil) }
let_it_be(:instance_name) { 'TestStore' }
- let_it_be(:multi_store) { described_class.new(primary_store, secondary_store, instance_name)}
+ let_it_be(:multi_store) { described_class.new(primary_store, secondary_store, instance_name) }
subject { multi_store.send(name, *args) }
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::Redis::MultiStore do
end
context 'when primary_store is nil' do
- let(:multi_store) { described_class.new(nil, secondary_store, instance_name)}
+ let(:multi_store) { described_class.new(nil, secondary_store, instance_name) }
it 'fails with exception' do
expect { multi_store }.to raise_error(ArgumentError, /primary_store is required/)
@@ -46,7 +46,7 @@ RSpec.describe Gitlab::Redis::MultiStore do
end
context 'when secondary_store is nil' do
- let(:multi_store) { described_class.new(primary_store, nil, instance_name)}
+ let(:multi_store) { described_class.new(primary_store, nil, instance_name) }
it 'fails with exception' do
expect { multi_store }.to raise_error(ArgumentError, /secondary_store is required/)
@@ -55,7 +55,7 @@ RSpec.describe Gitlab::Redis::MultiStore do
context 'when instance_name is nil' do
let(:instance_name) { nil }
- let(:multi_store) { described_class.new(primary_store, secondary_store, instance_name)}
+ let(:multi_store) { described_class.new(primary_store, secondary_store, instance_name) }
it 'fails with exception' do
expect { multi_store }.to raise_error(ArgumentError, /instance_name is required/)
@@ -111,8 +111,8 @@ RSpec.describe Gitlab::Redis::MultiStore do
context 'with READ redis commands' do
let_it_be(:key1) { "redis:{1}:key_a" }
let_it_be(:key2) { "redis:{1}:key_b" }
- let_it_be(:value1) { "redis_value1"}
- let_it_be(:value2) { "redis_value2"}
+ let_it_be(:value1) { "redis_value1" }
+ let_it_be(:value2) { "redis_value2" }
let_it_be(:skey) { "redis:set:key" }
let_it_be(:keys) { [key1, key2] }
let_it_be(:values) { [value1, value2] }
@@ -330,7 +330,7 @@ RSpec.describe Gitlab::Redis::MultiStore do
context 'with both primary and secondary store using same redis instance' do
let(:primary_store) { create_redis_store(redis_store_class.params, db: primary_db, serializer: nil) }
let(:secondary_store) { create_redis_store(redis_store_class.params, db: primary_db, serializer: nil) }
- let(:multi_store) { described_class.new(primary_store, secondary_store, instance_name)}
+ let(:multi_store) { described_class.new(primary_store, secondary_store, instance_name) }
it_behaves_like 'secondary store'
end
@@ -356,8 +356,8 @@ RSpec.describe Gitlab::Redis::MultiStore do
context 'with WRITE redis commands' do
let_it_be(:key1) { "redis:{1}:key_a" }
let_it_be(:key2) { "redis:{1}:key_b" }
- let_it_be(:value1) { "redis_value1"}
- let_it_be(:value2) { "redis_value2"}
+ let_it_be(:value1) { "redis_value1" }
+ let_it_be(:value2) { "redis_value2" }
let_it_be(:key1_value1) { [key1, value1] }
let_it_be(:key1_value2) { [key1, value2] }
let_it_be(:ttl) { 10 }
@@ -395,7 +395,7 @@ RSpec.describe Gitlab::Redis::MultiStore do
with_them do
describe "#{name}" do
- let(:expected_args) {args || no_args }
+ let(:expected_args) { args || no_args }
before do
allow(primary_store).to receive(name).and_call_original
@@ -496,8 +496,8 @@ RSpec.describe Gitlab::Redis::MultiStore do
RSpec.shared_examples_for 'pipelined command' do |name|
let_it_be(:key1) { "redis:{1}:key_a" }
- let_it_be(:value1) { "redis_value1"}
- let_it_be(:value2) { "redis_value2"}
+ let_it_be(:value1) { "redis_value1" }
+ let_it_be(:value2) { "redis_value2" }
let_it_be(:expected_value) { value1 }
let_it_be(:verification_name) { :get }
let_it_be(:verification_args) { key1 }
diff --git a/spec/lib/gitlab/reference_counter_spec.rb b/spec/lib/gitlab/reference_counter_spec.rb
index 83e4006c69b..05294fb84e7 100644
--- a/spec/lib/gitlab/reference_counter_spec.rb
+++ b/spec/lib/gitlab/reference_counter_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe Gitlab::ReferenceCounter, :clean_gitlab_redis_shared_state do
it 'resets reference count down to zero' do
3.times { reference_counter.increase }
- expect { reference_counter.reset! }.to change { reference_counter.value}.from(3).to(0)
+ expect { reference_counter.reset! }.to change { reference_counter.value }.from(3).to(0)
end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index a3afbed18e2..d8f182d903d 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -270,7 +270,7 @@ RSpec.describe Gitlab::Regex do
context 'conan recipe components' do
shared_examples 'accepting valid recipe components values' do
- let(:fifty_one_characters) { 'f_a' * 17}
+ let(:fifty_one_characters) { 'f_a' * 17 }
it { is_expected.to match('foobar') }
it { is_expected.to match('foo_bar') }
@@ -374,12 +374,12 @@ RSpec.describe Gitlab::Regex do
end
end
- it { is_expected.to match('0')}
+ it { is_expected.to match('0') }
it { is_expected.to match('1') }
it { is_expected.to match('03') }
it { is_expected.to match('2.0') }
it { is_expected.to match('01.2') }
- it { is_expected.to match('10.2.3-beta')}
+ it { is_expected.to match('10.2.3-beta') }
it { is_expected.to match('1.2-SNAPSHOT') }
it { is_expected.to match('20') }
it { is_expected.to match('20.3') }
@@ -454,7 +454,7 @@ RSpec.describe Gitlab::Regex do
it { is_expected.to match('0.1') }
it { is_expected.to match('2.0') }
- it { is_expected.to match('1.2.0')}
+ it { is_expected.to match('1.2.0') }
it { is_expected.to match('0100!0.0') }
it { is_expected.to match('00!1.2') }
it { is_expected.to match('1.0a') }
diff --git a/spec/lib/gitlab/search/abuse_detection_spec.rb b/spec/lib/gitlab/search/abuse_detection_spec.rb
index a18d28456cd..2a8d74a62ab 100644
--- a/spec/lib/gitlab/search/abuse_detection_spec.rb
+++ b/spec/lib/gitlab/search/abuse_detection_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Search::AbuseDetection do
subject { described_class.new(params) }
- let(:params) {{ query_string: 'foobar' }}
+ let(:params) { { query_string: 'foobar' } }
describe 'abusive scopes validation' do
it 'allows only approved scopes' do
diff --git a/spec/lib/gitlab/search_context/builder_spec.rb b/spec/lib/gitlab/search_context/builder_spec.rb
index a09115f3f21..78799b67a69 100644
--- a/spec/lib/gitlab/search_context/builder_spec.rb
+++ b/spec/lib/gitlab/search_context/builder_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::SearchContext::Builder, type: :controller do
- controller(ApplicationController) { }
+ controller(ApplicationController) {}
subject(:builder) { described_class.new(controller.view_context) }
diff --git a/spec/lib/gitlab/seeder_spec.rb b/spec/lib/gitlab/seeder_spec.rb
index a94ae2bca7a..0ad80323085 100644
--- a/spec/lib/gitlab/seeder_spec.rb
+++ b/spec/lib/gitlab/seeder_spec.rb
@@ -77,4 +77,44 @@ RSpec.describe Gitlab::Seeder do
end
end
end
+
+ describe ::Gitlab::Seeder::Ci::DailyBuildGroupReportResult do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :repository, group: group) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:build) { create(:ci_build, :success, pipeline: pipeline) }
+
+ subject(:build_report) do
+ described_class.new(project)
+ end
+
+ describe '#seed' do
+ it 'creates daily build results for the project' do
+ expect { build_report.seed }.to change {
+ Ci::DailyBuildGroupReportResult.count
+ }.by(Gitlab::Seeder::Ci::DailyBuildGroupReportResult::COUNT_OF_DAYS)
+ end
+
+ it 'matches project data with last report' do
+ build_report.seed
+
+ report = project.daily_build_group_report_results.last
+ reports_count = project.daily_build_group_report_results.count
+
+ expect(build.group_name).to eq(report.group_name)
+ expect(pipeline.source_ref_path).to eq(report.ref_path)
+ expect(pipeline.default_branch?).to eq(report.default_branch)
+ expect(reports_count).to eq(Gitlab::Seeder::Ci::DailyBuildGroupReportResult::COUNT_OF_DAYS)
+ end
+
+ it 'does not raise error on RecordNotUnique' do
+ build_report.seed
+ build_report.seed
+
+ reports_count = project.daily_build_group_report_results.count
+
+ expect(reports_count).to eq(Gitlab::Seeder::Ci::DailyBuildGroupReportResult::COUNT_OF_DAYS)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/session_spec.rb b/spec/lib/gitlab/session_spec.rb
index de680e8425e..67ad59f956d 100644
--- a/spec/lib/gitlab/session_spec.rb
+++ b/spec/lib/gitlab/session_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Gitlab::Session do
end
it 'restores current store after' do
- described_class.with_session(two: 2) { }
+ described_class.with_session(two: 2) {}
expect(described_class.current).to eq nil
end
diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb
index 4a1a9beb21a..c62302d8bba 100644
--- a/spec/lib/gitlab/sidekiq_config_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config_spec.rb
@@ -194,7 +194,7 @@ RSpec.describe Gitlab::SidekiqConfig do
queues = described_class.routing_queues
expect(queues).to match_array(%w[
- default mailers high_urgency gitaly email_receiver service_desk_email_receiver
+ default mailers high_urgency gitaly
])
expect(queues).not_to include('not_exist')
end
diff --git a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
index 01b7270d761..635f572daef 100644
--- a/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb
@@ -106,7 +106,7 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
end
describe '#stop_working' do
- subject { memory_killer.send(:stop_working)}
+ subject { memory_killer.send(:stop_working) }
it 'changes enable? to false' do
expect { subject }.to change { memory_killer.send(:enabled?) }
@@ -355,6 +355,7 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
let(:reason) { 'rss out of range reason description' }
let(:queue) { 'default' }
let(:running_jobs) { [{ jid: jid, worker_class: 'DummyWorker' }] }
+ let(:metrics) { memory_killer.instance_variable_get(:@metrics) }
let(:worker) do
Class.new do
def self.name
@@ -390,6 +391,9 @@ RSpec.describe Gitlab::SidekiqDaemon::MemoryKiller do
reason: reason,
running_jobs: running_jobs)
+ expect(metrics[:sidekiq_memory_killer_running_jobs]).to receive(:increment)
+ .with({ worker_class: "DummyWorker", deadline_exceeded: true })
+
Gitlab::SidekiqDaemon::Monitor.instance.within_job(DummyWorker, jid, queue) do
subject
end
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 9c0cbe21e6b..e3d9549a3c0 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
- call_subject(job, 'test_queue') { }
+ call_subject(job, 'test_queue') {}
end
end
@@ -40,7 +40,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
- call_subject(wrapped_job, 'test_queue') { }
+ call_subject(wrapped_job, 'test_queue') {}
end
end
@@ -175,7 +175,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
- call_subject(job, 'test_queue') { }
+ call_subject(job, 'test_queue') {}
end
end
@@ -188,7 +188,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
- call_subject(job.except("created_at", "enqueued_at"), 'test_queue') { }
+ call_subject(job.except("created_at", "enqueued_at"), 'test_queue') {}
end
end
end
@@ -204,7 +204,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
- call_subject(job, 'test_queue') { }
+ call_subject(job, 'test_queue') {}
end
end
end
@@ -233,7 +233,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
- call_subject(job, 'test_queue') { }
+ call_subject(job, 'test_queue') {}
end
end
end
@@ -266,7 +266,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger 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') { }
+ call_subject(job, 'test_queue') {}
end
end
end
@@ -330,7 +330,7 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
Gitlab::SafeRequestStore.clear!
- call_subject(job.dup, 'test_queue') { }
+ call_subject(job.dup, 'test_queue') {}
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
index 85cddfa7bf1..d61c9765753 100644
--- a/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
@@ -41,7 +41,9 @@ RSpec.describe Gitlab::SidekiqMiddleware::Monitor do
::Sidekiq::DeadSet.new.clear
expect do
- subject rescue Sidekiq::JobRetry::Skip
+ subject
+ rescue Sidekiq::JobRetry::Skip
+ nil
end.to change { ::Sidekiq::DeadSet.new.size }.by(1)
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 117b37ffda3..d6d24ea3a24 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -109,6 +109,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do
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)
+ expect(sidekiq_mem_total_bytes).to receive(:set).with(labels_with_job_status, mem_total_bytes)
subject.call(worker, job, :test) { nil }
end
diff --git a/spec/lib/gitlab/slash_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb
index 5167523ff58..5af234ff88e 100644
--- a/spec/lib/gitlab/slash_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb
@@ -165,7 +165,7 @@ RSpec.describe Gitlab::SlashCommands::Deploy do
context 'with ReDoS attempts' do
def duration_for(&block)
start = Time.zone.now
- yield if block_given?
+ yield if block
Time.zone.now - start
end
diff --git a/spec/lib/gitlab/spamcheck/client_spec.rb b/spec/lib/gitlab/spamcheck/client_spec.rb
index a6e7665569c..956ed2a976f 100644
--- a/spec/lib/gitlab/spamcheck/client_spec.rb
+++ b/spec/lib/gitlab/spamcheck/client_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Gitlab::Spamcheck::Client do
let(:stub) { double(:spamcheck_stub, check_for_spam_issue: response) }
context 'is tls ' do
- let(:endpoint) { 'tls://spamcheck.example.com'}
+ let(:endpoint) { 'tls://spamcheck.example.com' }
it 'uses secure connection' do
expect(Spamcheck::SpamcheckService::Stub).to receive(:new).with(endpoint.sub(%r{^tls://}, ''),
@@ -97,7 +97,7 @@ RSpec.describe Gitlab::Spamcheck::Client do
context: cxt)
expect(issue_pb.title).to eq issue.title
expect(issue_pb.description).to eq issue.description
- expect(issue_pb.user_in_project). to be false
+ expect(issue_pb.user_in_project).to be false
expect(issue_pb.project.project_id).to eq issue.project_id
expect(issue_pb.created_at).to eq timestamp_to_protobuf_timestamp(issue.created_at)
expect(issue_pb.updated_at).to eq timestamp_to_protobuf_timestamp(issue.updated_at)
@@ -118,7 +118,7 @@ RSpec.describe Gitlab::Spamcheck::Client do
end
context 'when user has multiple email addresses' do
- let(:secondary_email) {create(:email, :confirmed, user: user)}
+ let(:secondary_email) { create(:email, :confirmed, user: user) }
before do
user.emails << secondary_email
diff --git a/spec/lib/gitlab/ssh/commit_spec.rb b/spec/lib/gitlab/ssh/commit_spec.rb
new file mode 100644
index 00000000000..cc977a80f95
--- /dev/null
+++ b/spec/lib/gitlab/ssh/commit_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ssh::Commit do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:signed_by_key) { create(:key) }
+
+ let(:commit) { create(:commit, project: project) }
+ let(:signature_text) { 'signature_text' }
+ let(:signed_text) { 'signed_text' }
+ let(:signature_data) { [signature_text, signed_text] }
+ let(:verifier) { instance_double('Gitlab::Ssh::Signature') }
+ let(:verification_status) { :verified }
+
+ subject(:signature) { described_class.new(commit).signature }
+
+ before do
+ allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
+ .with(Gitlab::Git::Repository, commit.sha)
+ .and_return(signature_data)
+
+ allow(verifier).to receive(:verification_status).and_return(verification_status)
+ allow(verifier).to receive(:signed_by_key).and_return(signed_by_key)
+
+ allow(Gitlab::Ssh::Signature).to receive(:new)
+ .with(signature_text, signed_text, commit.committer_email)
+ .and_return(verifier)
+ end
+
+ describe '#signature' do
+ it 'returns the cached signature on multiple calls' do
+ ssh_commit = described_class.new(commit)
+
+ expect(ssh_commit).to receive(:create_cached_signature!).and_call_original
+ ssh_commit.signature
+
+ expect(ssh_commit).not_to receive(:create_cached_signature!)
+ ssh_commit.signature
+ end
+
+ context 'when all expected data is present' do
+ it 'calls signature verifier and uses returned attributes' do
+ expect(signature).to have_attributes(
+ commit_sha: commit.sha,
+ project: project,
+ key_id: signed_by_key.id,
+ verification_status: 'verified'
+ )
+ end
+ end
+
+ context 'when signed_by_key is nil' do
+ let_it_be(:signed_by_key) { nil }
+
+ let(:verification_status) { :unknown_key }
+
+ it 'creates signature without a key_id' do
+ expect(signature).to have_attributes(
+ commit_sha: commit.sha,
+ project: project,
+ key_id: nil,
+ verification_status: 'unknown_key'
+ )
+ end
+ end
+ end
+
+ describe '#update_signature!' do
+ it 'updates verification status' do
+ allow(verifier).to receive(:verification_status).and_return(:unverified)
+ signature
+
+ stored_signature = CommitSignatures::SshSignature.find_by_commit_sha(commit.sha)
+
+ allow(verifier).to receive(:verification_status).and_return(:verified)
+
+ expect { described_class.new(commit).update_signature!(stored_signature) }.to(
+ change { signature.reload.verification_status }.from('unverified').to('verified')
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/suggestions/file_suggestion_spec.rb b/spec/lib/gitlab/suggestions/file_suggestion_spec.rb
index 1d25bf6edbd..5971f4ebbce 100644
--- a/spec/lib/gitlab/suggestions/file_suggestion_spec.rb
+++ b/spec/lib/gitlab/suggestions/file_suggestion_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Gitlab::Suggestions::FileSuggestion do
let_it_be(:user) { create(:user) }
- let_it_be(:file_path) { 'files/ruby/popen.rb'}
+ let_it_be(:file_path) { 'files/ruby/popen.rb' }
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb b/spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb
index 2554a15d97e..48092a33da3 100644
--- a/spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb
+++ b/spec/lib/gitlab/tracking/destinations/snowplow_micro_spec.rb
@@ -48,40 +48,8 @@ RSpec.describe Gitlab::Tracking::Destinations::SnowplowMicro do
allow(Gitlab.config).to receive(:snowplow_micro).and_raise(Settingslogic::MissingSetting)
end
- context 'when SNOWPLOW_MICRO_URI has scheme and port' do
- before do
- stub_env('SNOWPLOW_MICRO_URI', 'http://gdk.test:9091')
- end
-
- it 'returns hostname URI part' do
- expect(subject.hostname).to eq('gdk.test:9091')
- end
- end
-
- context 'when SNOWPLOW_MICRO_URI is without protocol' do
- before do
- stub_env('SNOWPLOW_MICRO_URI', 'gdk.test:9091')
- end
-
- it 'returns hostname URI part' do
- expect(subject.hostname).to eq('gdk.test:9091')
- end
- end
-
- context 'when SNOWPLOW_MICRO_URI is hostname only' do
- before do
- stub_env('SNOWPLOW_MICRO_URI', 'uriwithoutport')
- end
-
- it 'returns hostname URI with default HTTP port' do
- expect(subject.hostname).to eq('uriwithoutport:80')
- end
- end
-
- context 'when SNOWPLOW_MICRO_URI is not set' do
- it 'returns localhost hostname' do
- expect(subject.hostname).to eq('localhost:9090')
- end
+ it 'returns localhost hostname' do
+ expect(subject.hostname).to eq('localhost:9090')
end
end
end
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index dd62c832f6f..028c985f3b3 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -90,15 +90,6 @@ RSpec.describe Gitlab::Tracking do
it_behaves_like 'delegates to SnowplowMicro destination with proper options'
end
-
- context "enabled with env variable" do
- before do
- allow(Gitlab.config).to receive(:snowplow_micro).and_raise(Settingslogic::MissingSetting)
- stub_env('SNOWPLOW_MICRO_ENABLE', '1')
- end
-
- it_behaves_like 'delegates to SnowplowMicro destination with proper options'
- end
end
it 'when feature flag is disabled' do
@@ -149,7 +140,6 @@ RSpec.describe Gitlab::Tracking do
context 'when destination is Snowplow' do
before do
- stub_env('SNOWPLOW_MICRO_ENABLE', '0')
allow(Rails.env).to receive(:development?).and_return(true)
end
@@ -158,7 +148,6 @@ RSpec.describe Gitlab::Tracking do
context 'when destination is SnowplowMicro' do
before do
- stub_env('SNOWPLOW_MICRO_ENABLE', '1')
allow(Rails.env).to receive(:development?).and_return(true)
end
@@ -181,7 +170,7 @@ RSpec.describe Gitlab::Tracking do
let_it_be(:definition_action) { 'definition_action' }
let_it_be(:definition_category) { 'definition_category' }
let_it_be(:label_description) { 'definition label description' }
- let_it_be(:test_definition) {{ 'category': definition_category, 'action': definition_action }}
+ let_it_be(:test_definition) { { 'category': definition_category, 'action': definition_action } }
before do
allow_next_instance_of(described_class) do |instance|
@@ -212,4 +201,28 @@ RSpec.describe Gitlab::Tracking do
project: project, user: user, namespace: namespace, extra_key_1: 'extra value 1')
end
end
+
+ describe 'snowplow_micro_enabled?' do
+ before do
+ allow(Rails.env).to receive(:development?).and_return(true)
+ end
+
+ it 'returns true when snowplow_micro is enabled' do
+ stub_config(snowplow_micro: { enabled: true })
+
+ expect(described_class).to be_snowplow_micro_enabled
+ end
+
+ it 'returns false when snowplow_micro is disabled' do
+ stub_config(snowplow_micro: { enabled: false })
+
+ expect(described_class).not_to be_snowplow_micro_enabled
+ end
+
+ it 'returns false when snowplow_micro is not configured' do
+ allow(Gitlab.config).to receive(:snowplow_micro).and_raise(Settingslogic::MissingSetting)
+
+ expect(described_class).not_to be_snowplow_micro_enabled
+ end
+ end
end
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb
index 8e7bd7b84e6..f73155642d6 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/database_metric_spec.rb
@@ -160,6 +160,38 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::DatabaseMetric do
end
end
end
+
+ context 'with custom timestamp column' do
+ subject do
+ described_class.tap do |metric_class|
+ metric_class.relation { Issue }
+ metric_class.operation :count
+ metric_class.timestamp_column :last_edited_at
+ end.new(time_frame: '28d')
+ end
+
+ it 'calculates a correct result' do
+ create(:issue, last_edited_at: 5.days.ago)
+
+ expect(subject.value).to eq(1)
+ end
+ end
+
+ context 'with default timestamp column' do
+ subject do
+ described_class.tap do |metric_class|
+ metric_class.relation { Issue }
+ metric_class.operation :count
+ end.new(time_frame: '28d')
+ end
+
+ it 'calculates a correct result' do
+ create(:issue, last_edited_at: 5.days.ago)
+ create(:issue, created_at: 5.days.ago)
+
+ expect(subject.value).to eq(1)
+ end
+ end
end
context 'with unimplemented operation method used' do
diff --git a/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb b/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb
index 9ee8bc6b568..f9cd6e88e0a 100644
--- a/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/name_suggestion_spec.rb
@@ -66,7 +66,7 @@ RSpec.describe Gitlab::Usage::Metrics::NameSuggestion do
let(:key_path) { 'counts.jira_imports_total_imported_issues_count' }
let(:operation) { :sum }
let(:relation) { JiraImportState.finished }
- let(:column) { :imported_issues_count}
+ let(:column) { :imported_issues_count }
let(:name_suggestion) { /sum_imported_issues_count_from_<adjective describing\: '\(jira_imports\.status = \d+\)'>_jira_imports/ }
end
end
@@ -77,7 +77,7 @@ RSpec.describe Gitlab::Usage::Metrics::NameSuggestion do
let(:key_path) { 'counts.ci_pipeline_duration' }
let(:operation) { :average }
let(:relation) { Ci::Pipeline }
- let(:column) { :duration}
+ let(:column) { :duration }
let(:name_suggestion) { /average_duration_from_ci_pipelines/ }
end
end
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 167dba9b57d..7e8b15d23db 100644
--- a/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/names_suggestions/generator_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe Gitlab::Usage::Metrics::NamesSuggestions::Generator do
end
describe '#add_metric' do
- let(:metric) {'CountIssuesMetric' }
+ let(:metric) { 'CountIssuesMetric' }
it 'computes the suggested name for given metric' do
expect(described_class.add_metric(metric)).to eq('count_issues')
diff --git a/spec/lib/gitlab/usage/service_ping_report_spec.rb b/spec/lib/gitlab/usage/service_ping_report_spec.rb
index 1e8f9db4dea..7a37a31b195 100644
--- a/spec/lib/gitlab/usage/service_ping_report_spec.rb
+++ b/spec/lib/gitlab/usage/service_ping_report_spec.rb
@@ -111,8 +111,12 @@ RSpec.describe Gitlab::Usage::ServicePingReport, :use_clean_rails_memory_store_c
# Because test cases are run inside a transaction, if any query raise and error all queries that follows
# it are automatically canceled by PostgreSQL, to avoid that problem, and to provide exhaustive information
# about every metric, queries are wrapped explicitly in sub transactions.
- ApplicationRecord.transaction do
- ApplicationRecord.connection.execute(query)&.first&.values&.first
+ table = PgQuery.parse(query).tables.first
+ gitlab_schema = Gitlab::Database::GitlabSchema.tables_to_schema[table]
+ base_model = gitlab_schema == :gitlab_main ? ApplicationRecord : Ci::ApplicationRecord
+
+ base_model.transaction do
+ base_model.connection.execute(query)&.first&.values&.first
end
rescue ActiveRecord::StatementInvalid => e
e.message
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 54d49b432f4..e0b334cb5af 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
@@ -77,32 +77,18 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
describe '.unique_events_data' do
- context 'with use_redis_hll_instrumentation_classes feature enabled' do
- it 'does not include instrumented categories' do
- stub_feature_flags(use_redis_hll_instrumentation_classes: true)
-
- expect(described_class.unique_events_data.keys)
- .not_to include(*described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
- end
- end
-
- context 'with use_redis_hll_instrumentation_classes feature disabled' do
- it 'includes instrumented categories' do
- stub_feature_flags(use_redis_hll_instrumentation_classes: false)
-
- expect(described_class.unique_events_data.keys)
- .to include(*described_class::CATEGORIES_COLLECTED_FROM_METRICS_DEFINITIONS)
- end
+ it 'does not include instrumented categories' do
+ expect(described_class.unique_events_data.keys)
+ .not_to include(*described_class.categories_collected_from_metrics_definitions)
end
end
end
describe '.categories' do
- it 'gets all unique category names' do
- expect(described_class.categories).to contain_exactly(
+ it 'gets CE unique category names' do
+ expect(described_class.categories).to include(
'deploy_token_packages',
'user_packages',
- 'compliance',
'ecosystem',
'analytics',
'ide_edit',
@@ -130,7 +116,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'work_items',
'ci_users',
'error_tracking',
- 'manage'
+ 'manage',
+ 'kubernetes_agent'
)
end
end
@@ -483,7 +470,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.weekly_redis_keys' do
using RSpec::Parameterized::TableSyntax
- let(:weekly_event) { 'g_compliance_dashboard' }
+ let(:weekly_event) { 'i_search_total' }
let(:redis_event) { described_class.send(:event_for, weekly_event) }
subject(:weekly_redis_keys) { described_class.send(:weekly_redis_keys, events: [redis_event], start_date: DateTime.parse(start_date), end_date: DateTime.parse(end_date)) }
@@ -493,13 +480,13 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'2020-12-21' | '2020-12-20' | []
'2020-12-21' | '2020-11-21' | []
'2021-01-01' | '2020-12-28' | []
- '2020-12-21' | '2020-12-28' | ['g_{compliance}_dashboard-2020-52']
- '2020-12-21' | '2021-01-01' | ['g_{compliance}_dashboard-2020-52']
- '2020-12-27' | '2021-01-01' | ['g_{compliance}_dashboard-2020-52']
- '2020-12-26' | '2021-01-04' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53']
- '2020-12-26' | '2021-01-11' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53', 'g_{compliance}_dashboard-2021-01']
- '2020-12-26' | '2021-01-17' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53', 'g_{compliance}_dashboard-2021-01']
- '2020-12-26' | '2021-01-18' | ['g_{compliance}_dashboard-2020-52', 'g_{compliance}_dashboard-2020-53', 'g_{compliance}_dashboard-2021-01', 'g_{compliance}_dashboard-2021-02']
+ '2020-12-21' | '2020-12-28' | ['i_{search}_total-2020-52']
+ '2020-12-21' | '2021-01-01' | ['i_{search}_total-2020-52']
+ '2020-12-27' | '2021-01-01' | ['i_{search}_total-2020-52']
+ '2020-12-26' | '2021-01-04' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53']
+ '2020-12-26' | '2021-01-11' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53', 'i_{search}_total-2021-01']
+ '2020-12-26' | '2021-01-17' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53', 'i_{search}_total-2021-01']
+ '2020-12-26' | '2021-01-18' | ['i_{search}_total-2020-52', 'i_{search}_total-2020-53', 'i_{search}_total-2021-01', 'i_{search}_total-2021-02']
end
with_them do
diff --git a/spec/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter_spec.rb
index 60c4424d2ae..b778f532a11 100644
--- a/spec/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter_spec.rb
@@ -43,18 +43,18 @@ RSpec.describe Gitlab::UsageDataCounters::IpynbDiffActivityCounter, :clean_gitla
let(:for_commit) { true }
it_behaves_like 'an action that tracks events' do
- let(:action) {described_class::NOTE_CREATED_IN_IPYNB_DIFF_ACTION}
- let(:per_user_action) {described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_ACTION}
+ let(:action) { described_class::NOTE_CREATED_IN_IPYNB_DIFF_ACTION }
+ let(:per_user_action) { described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_ACTION }
end
it_behaves_like 'an action that tracks events' do
- let(:action) {described_class::NOTE_CREATED_IN_IPYNB_DIFF_COMMIT_ACTION}
- let(:per_user_action) {described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_COMMIT_ACTION}
+ let(:action) { described_class::NOTE_CREATED_IN_IPYNB_DIFF_COMMIT_ACTION }
+ let(:per_user_action) { described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_COMMIT_ACTION }
end
it_behaves_like 'an action that does not track events' do
- let(:action) {described_class::NOTE_CREATED_IN_IPYNB_DIFF_MR_ACTION}
- let(:per_user_action) {described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_MR_ACTION}
+ let(:action) { described_class::NOTE_CREATED_IN_IPYNB_DIFF_MR_ACTION }
+ let(:per_user_action) { described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_MR_ACTION }
end
end
@@ -62,35 +62,35 @@ RSpec.describe Gitlab::UsageDataCounters::IpynbDiffActivityCounter, :clean_gitla
let(:for_mr) { true }
it_behaves_like 'an action that tracks events' do
- let(:action) {described_class::NOTE_CREATED_IN_IPYNB_DIFF_MR_ACTION}
- let(:per_user_action) {described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_MR_ACTION}
+ let(:action) { described_class::NOTE_CREATED_IN_IPYNB_DIFF_MR_ACTION }
+ let(:per_user_action) { described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_MR_ACTION }
end
it_behaves_like 'an action that tracks events' do
- let(:action) {described_class::NOTE_CREATED_IN_IPYNB_DIFF_ACTION}
- let(:per_user_action) {described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_ACTION}
+ let(:action) { described_class::NOTE_CREATED_IN_IPYNB_DIFF_ACTION }
+ let(:per_user_action) { described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_ACTION }
end
it_behaves_like 'an action that does not track events' do
- let(:action) {described_class::NOTE_CREATED_IN_IPYNB_DIFF_COMMIT_ACTION}
- let(:per_user_action) {described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_COMMIT_ACTION}
+ let(:action) { described_class::NOTE_CREATED_IN_IPYNB_DIFF_COMMIT_ACTION }
+ let(:per_user_action) { described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_COMMIT_ACTION }
end
end
context 'note is for neither MR nor Commit' do
it_behaves_like 'an action that does not track events' do
- let(:action) {described_class::NOTE_CREATED_IN_IPYNB_DIFF_ACTION}
- let(:per_user_action) {described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_ACTION}
+ let(:action) { described_class::NOTE_CREATED_IN_IPYNB_DIFF_ACTION }
+ let(:per_user_action) { described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_ACTION }
end
it_behaves_like 'an action that does not track events' do
- let(:action) {described_class::NOTE_CREATED_IN_IPYNB_DIFF_MR_ACTION}
- let(:per_user_action) {described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_MR_ACTION}
+ let(:action) { described_class::NOTE_CREATED_IN_IPYNB_DIFF_MR_ACTION }
+ let(:per_user_action) { described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_MR_ACTION }
end
it_behaves_like 'an action that does not track events' do
- let(:action) {described_class::NOTE_CREATED_IN_IPYNB_DIFF_COMMIT_ACTION}
- let(:per_user_action) {described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_COMMIT_ACTION}
+ let(:action) { described_class::NOTE_CREATED_IN_IPYNB_DIFF_COMMIT_ACTION }
+ let(:per_user_action) { described_class::USER_CREATED_NOTE_IN_IPYNB_DIFF_COMMIT_ACTION }
end
end
end
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 1b73e5269d7..84a6f338282 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
@@ -6,7 +6,12 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
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_it_be(:project) { build(:project) }
+ let_it_be(:category) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_CATEGORY }
+ let_it_be(:event_action) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_ACTION }
+ let_it_be(:event_label) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_LABEL }
+ let(:event_property) { action }
let(:time) { Time.zone.now }
context 'for Issue title edit actions' do
@@ -120,8 +125,8 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue cloned actions' do
- it_behaves_like 'a daily tracked issuable event' do
- let(:action) { described_class::ISSUE_CLONED }
+ it_behaves_like 'daily tracked issuable snowplow and service ping events with project' do
+ let_it_be(:action) { described_class::ISSUE_CLONED }
def track_action(params)
described_class.track_issue_cloned_action(**params)
@@ -239,8 +244,8 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
end
- context 'for Issue comment added actions' do
- it_behaves_like 'a daily tracked issuable event' do
+ context 'for Issue comment added actions', :snowplow do
+ it_behaves_like 'daily tracked issuable snowplow and service ping events with project' do
let(:action) { described_class::ISSUE_COMMENT_ADDED }
def track_action(params)
@@ -249,8 +254,8 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
end
- context 'for Issue comment edited actions' do
- it_behaves_like 'a daily tracked issuable event' do
+ context 'for Issue comment edited actions', :snowplow do
+ it_behaves_like 'daily tracked issuable snowplow and service ping events with project' do
let(:action) { described_class::ISSUE_COMMENT_EDITED }
def track_action(params)
@@ -259,8 +264,8 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
end
- context 'for Issue comment removed actions' do
- it_behaves_like 'a daily tracked issuable event' do
+ context 'for Issue comment removed actions', :snowplow do
+ it_behaves_like 'daily tracked issuable snowplow and service ping events with project' do
let(:action) { described_class::ISSUE_COMMENT_REMOVED }
def track_action(params)
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter_spec.rb
new file mode 100644
index 00000000000..e073fac504a
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_widget_extension_counter_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::MergeRequestWidgetExtensionCounter do
+ it_behaves_like 'a redis usage counter', 'Widget Extension', :test_summary_count_expand
+
+ it_behaves_like 'a redis usage counter with totals', :i_code_review_merge_request_widget, test_summary_count_expand: 5
+end
diff --git a/spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb
index 0264236f087..0bcdbe82a7a 100644
--- a/spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/work_item_activity_unique_counter_spec.rb
@@ -20,4 +20,12 @@ RSpec.describe Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter, :clean_
it_behaves_like 'work item unique counter'
end
+
+ describe '.track_work_item_date_changed_action' do
+ subject(:track_event) { described_class.track_work_item_date_changed_action(author: user) }
+
+ let(:event_name) { described_class::WORK_ITEM_DATE_CHANGED }
+
+ it_behaves_like 'work item unique counter'
+ end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 6eb00053b17..692b6483149 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -1203,12 +1203,14 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
describe 'redis_hll_counters' do
subject { described_class.redis_hll_counters }
- let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
+ let(:migrated_categories) do
+ ::Gitlab::UsageDataCounters::HLLRedisCounter.categories_collected_from_metrics_definitions
+ end
+ let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories - migrated_categories }
let(:ignored_metrics) { ["i_package_composer_deploy_token_weekly"] }
it 'has all known_events' do
- stub_feature_flags(use_redis_hll_instrumentation_classes: false)
expect(subject).to have_key(:redis_hll_counters)
expect(subject[:redis_hll_counters].keys).to match_array(categories)
diff --git a/spec/lib/gitlab/utils/batch_loader_spec.rb b/spec/lib/gitlab/utils/batch_loader_spec.rb
new file mode 100644
index 00000000000..c1f6d6df07a
--- /dev/null
+++ b/spec/lib/gitlab/utils/batch_loader_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'batch-loader'
+
+RSpec.describe Gitlab::Utils::BatchLoader do
+ let(:stubbed_loader) do
+ double( # rubocop:disable RSpec/VerifiedDoubles
+ 'Loader',
+ load_lazy_method: [],
+ load_lazy_method_same_batch_key: [],
+ load_lazy_method_other_batch_key: []
+ )
+ end
+
+ let(:test_module) do
+ Module.new do
+ def self.lazy_method(id)
+ BatchLoader.for(id).batch(key: :my_batch_name) do |ids, loader|
+ stubbed_loader.load_lazy_method(ids)
+
+ ids.each { |id| loader.call(id, id) }
+ end
+ end
+
+ def self.lazy_method_same_batch_key(id)
+ BatchLoader.for(id).batch(key: :my_batch_name) do |ids, loader|
+ stubbed_loader.load_lazy_method_same_batch_key(ids)
+
+ ids.each { |id| loader.call(id, id) }
+ end
+ end
+
+ def self.lazy_method_other_batch_key(id)
+ BatchLoader.for(id).batch(key: :other_batch_name) do |ids, loader|
+ stubbed_loader.load_lazy_method_other_batch_key(ids)
+
+ ids.each { |id| loader.call(id, id) }
+ end
+ end
+ end
+ end
+
+ before do
+ BatchLoader::Executor.clear_current
+ allow(test_module).to receive(:stubbed_loader).and_return(stubbed_loader)
+ end
+
+ describe '.clear_key' do
+ it 'clears batched items which match the specified batch key' do
+ test_module.lazy_method(1)
+ test_module.lazy_method_same_batch_key(2)
+ test_module.lazy_method_other_batch_key(3)
+
+ described_class.clear_key(:my_batch_name)
+
+ test_module.lazy_method(4).to_i
+ test_module.lazy_method_same_batch_key(5).to_i
+ test_module.lazy_method_other_batch_key(6).to_i
+
+ expect(stubbed_loader).to have_received(:load_lazy_method).with([4])
+ expect(stubbed_loader).to have_received(:load_lazy_method_same_batch_key).with([5])
+ expect(stubbed_loader).to have_received(:load_lazy_method_other_batch_key).with([3, 6])
+ end
+
+ it 'clears loaded values which match the specified batch key' do
+ test_module.lazy_method(1).to_i
+ test_module.lazy_method_same_batch_key(2).to_i
+ test_module.lazy_method_other_batch_key(3).to_i
+
+ described_class.clear_key(:my_batch_name)
+
+ test_module.lazy_method(1).to_i
+ test_module.lazy_method_same_batch_key(2).to_i
+ test_module.lazy_method_other_batch_key(3).to_i
+
+ expect(stubbed_loader).to have_received(:load_lazy_method).with([1]).twice
+ expect(stubbed_loader).to have_received(:load_lazy_method_same_batch_key).with([2]).twice
+ expect(stubbed_loader).to have_received(:load_lazy_method_other_batch_key).with([3])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils/link_header_parser_spec.rb b/spec/lib/gitlab/utils/link_header_parser_spec.rb
new file mode 100644
index 00000000000..e15ef930271
--- /dev/null
+++ b/spec/lib/gitlab/utils/link_header_parser_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Utils::LinkHeaderParser do
+ let(:parser) { described_class.new(header) }
+
+ describe '#parse' do
+ subject { parser.parse }
+
+ context 'with a valid header' do
+ let(:header) { generate_header(next: 'http://sandbox.org/next') }
+ let(:expected) { { next: { uri: URI('http://sandbox.org/next') } } }
+
+ it { is_expected.to eq(expected) }
+
+ context 'with multiple links' do
+ let(:header) { generate_header(next: 'http://sandbox.org/next', previous: 'http://sandbox.org/previous') }
+ let(:expected) do
+ {
+ next: { uri: URI('http://sandbox.org/next') },
+ previous: { uri: URI('http://sandbox.org/previous') }
+ }
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+
+ context 'with an incomplete uri' do
+ let(:header) { '<http://sandbox.org/next; rel="next"' }
+
+ it { is_expected.to eq({}) }
+ end
+
+ context 'with no rel' do
+ let(:header) { '<http://sandbox.org/next>; direction="next"' }
+
+ it { is_expected.to eq({}) }
+ end
+
+ context 'with multiple rel elements' do
+ # check https://datatracker.ietf.org/doc/html/rfc5988#section-5.3:
+ # occurrences after the first MUST be ignored by parsers
+ let(:header) { '<http://sandbox.org/next>; rel="next"; rel="dummy"' }
+
+ it { is_expected.to eq(expected) }
+ end
+
+ context 'when the url is too long' do
+ let(:header) { "<http://sandbox.org/#{'a' * 500}>; rel=\"next\"" }
+
+ it { is_expected.to eq({}) }
+ end
+ end
+
+ context 'with nil header' do
+ let(:header) { nil }
+
+ it { is_expected.to eq({}) }
+ end
+
+ context 'with empty header' do
+ let(:header) { '' }
+
+ it { is_expected.to eq({}) }
+ end
+
+ def generate_header(links)
+ stringified_links = links.map do |rel, url|
+ "<#{url}>; rel=\"#{rel}\""
+ end
+ stringified_links.join(', ')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
index 514051b1cc0..3ab592dfc62 100644
--- a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
+++ b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
@@ -68,7 +68,7 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
describe "#safe_protocol?" do
let(:doc) { HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>") }
let(:node) { doc.children.first }
- let(:uri) { Addressable::URI.parse(node['href'])}
+ let(:uri) { Addressable::URI.parse(node['href']) }
it "returns false" do
expect(object.safe_protocol?(scheme)).to be_falsy
diff --git a/spec/lib/gitlab/utils/strong_memoize_spec.rb b/spec/lib/gitlab/utils/strong_memoize_spec.rb
index 5350e090e2b..cb03797b3d9 100644
--- a/spec/lib/gitlab/utils/strong_memoize_spec.rb
+++ b/spec/lib/gitlab/utils/strong_memoize_spec.rb
@@ -1,10 +1,27 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
+require 'rspec-benchmark'
+
+RSpec.configure do |config|
+ config.include RSpec::Benchmark::Matchers
+end
RSpec.describe Gitlab::Utils::StrongMemoize do
let(:klass) do
- struct = Struct.new(:value) do
+ strong_memoize_class = described_class
+
+ Struct.new(:value) do
+ include strong_memoize_class
+
+ def self.method_added_list
+ @method_added_list ||= []
+ end
+
+ def self.method_added(name)
+ method_added_list << name
+ end
+
def method_name
strong_memoize(:method_name) do
trace << value
@@ -12,21 +29,56 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
end
end
+ def method_name_attr
+ trace << value
+ value
+ end
+ strong_memoize_attr :method_name_attr
+
+ strong_memoize_attr :different_method_name_attr, :different_member_name_attr
+ def different_method_name_attr
+ trace << value
+ value
+ end
+
+ strong_memoize_attr :enabled?
+ def enabled?
+ true
+ end
+
def trace
@trace ||= []
end
- end
- struct.include(described_class)
- struct
+ protected
+
+ def private_method
+ end
+ private :private_method
+ strong_memoize_attr :private_method
+
+ public
+
+ def protected_method
+ end
+ protected :protected_method
+ strong_memoize_attr :protected_method
+
+ private
+
+ def public_method
+ end
+ public :public_method
+ strong_memoize_attr :public_method
+ end
end
subject(:object) { klass.new(value) }
shared_examples 'caching the value' do
it 'only calls the block once' do
- value0 = object.method_name
- value1 = object.method_name
+ value0 = object.send(method_name)
+ value1 = object.send(method_name)
expect(value0).to eq(value)
expect(value1).to eq(value)
@@ -34,8 +86,8 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
end
it 'returns and defines the instance variable for the exact value' do
- returned_value = object.method_name
- memoized_value = object.instance_variable_get(:@method_name)
+ returned_value = object.send(method_name)
+ memoized_value = object.instance_variable_get(:"@#{member_name}")
expect(returned_value).to eql(value)
expect(memoized_value).to eql(value)
@@ -46,12 +98,19 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
[nil, false, true, 'value', 0, [0]].each do |value|
context "with value #{value}" do
let(:value) { value }
+ let(:method_name) { :method_name }
+ let(:member_name) { :method_name }
it_behaves_like 'caching the value'
- it 'raises exception for invalid key' do
+ it 'raises exception for invalid type as key' do
expect { object.strong_memoize(10) { 20 } }.to raise_error /Invalid type of '10'/
end
+
+ it 'raises exception for invalid characters in key' do
+ expect { object.strong_memoize(:enabled?) { 20 } }
+ .to raise_error /is not allowed as an instance variable name/
+ end
end
end
@@ -109,4 +168,64 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
expect(object.instance_variable_defined?(:@method_name)).to be(false)
end
end
+
+ describe '.strong_memoize_attr' do
+ [nil, false, true, 'value', 0, [0]].each do |value|
+ let(:value) { value }
+
+ context "memoized after method definition with value #{value}" do
+ let(:method_name) { :method_name_attr }
+ let(:member_name) { :method_name_attr }
+
+ it_behaves_like 'caching the value'
+
+ it 'calls the existing .method_added' do
+ expect(klass.method_added_list).to include(:method_name_attr)
+ end
+ end
+
+ context "memoized before method definition with different member name and value #{value}" do
+ let(:method_name) { :different_method_name_attr }
+ let(:member_name) { :different_member_name_attr }
+
+ it_behaves_like 'caching the value'
+
+ it 'calls the existing .method_added' do
+ expect(klass.method_added_list).to include(:different_method_name_attr)
+ end
+ end
+
+ context 'with valid method name' do
+ let(:method_name) { :enabled? }
+
+ context 'with invalid member name' do
+ let(:member_name) { :enabled? }
+
+ it 'is invalid' do
+ expect { object.send(method_name) { value } }.to raise_error /is not allowed as an instance variable name/
+ end
+ end
+ end
+ end
+
+ describe 'method visibility' do
+ it 'sets private visibility' do
+ expect(klass.private_instance_methods).to include(:private_method)
+ expect(klass.protected_instance_methods).not_to include(:private_method)
+ expect(klass.public_instance_methods).not_to include(:private_method)
+ end
+
+ it 'sets protected visibility' do
+ expect(klass.private_instance_methods).not_to include(:protected_method)
+ expect(klass.protected_instance_methods).to include(:protected_method)
+ expect(klass.public_instance_methods).not_to include(:protected_method)
+ end
+
+ it 'sets public visibility' do
+ expect(klass.private_instance_methods).not_to include(:public_method)
+ expect(klass.protected_instance_methods).not_to include(:public_method)
+ expect(klass.public_instance_methods).to include(:public_method)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb
index 25ba5a3e09e..13d046b0816 100644
--- a/spec/lib/gitlab/utils/usage_data_spec.rb
+++ b/spec/lib/gitlab/utils/usage_data_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe Gitlab::Utils::UsageData do
end
describe '#add_metric' do
- let(:metric) { 'UuidMetric'}
+ let(:metric) { 'UuidMetric' }
it 'computes the metric value for given metric' do
expect(described_class.add_metric(metric)).to eq(Gitlab::CurrentSettings.uuid)
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 0648d276a6b..ad1a65ffae8 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -115,7 +115,7 @@ RSpec.describe Gitlab::Utils do
end
it 'raises error for a non-string' do
- expect {check_allowed_absolute_path_and_path_traversal!(nil, allowed_paths)}.to raise_error(StandardError)
+ expect { check_allowed_absolute_path_and_path_traversal!(nil, allowed_paths) }.to raise_error(StandardError)
end
it 'raises an exception if an absolute path is not allowed' do
@@ -128,7 +128,7 @@ RSpec.describe Gitlab::Utils do
end
describe '.allowlisted?' do
- let(:allowed_paths) { ['/home/foo', '/foo/bar', '/etc/passwd']}
+ let(:allowed_paths) { ['/home/foo', '/foo/bar', '/etc/passwd'] }
it 'returns true if path is allowed' do
expect(allowlisted?('/foo/bar', allowed_paths)).to be(true)
diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb
index 3e5154d5029..f9aa196ffde 100644
--- a/spec/lib/gitlab/verify/uploads_spec.rb
+++ b/spec/lib/gitlab/verify/uploads_spec.rb
@@ -90,7 +90,7 @@ RSpec.describe Gitlab::Verify::Uploads do
end
def perform_task
- described_class.new(batch_size: 100).run_batches { }
+ described_class.new(batch_size: 100).run_batches {}
end
end
end
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index 6ed094f11c8..078f952afad 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -79,11 +79,12 @@ RSpec.describe Gitlab::VersionInfo do
describe '.unknown' do
it { expect(@unknown).not_to be @v0_0_1 }
it { expect(@unknown).not_to be described_class.new }
- it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
- it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
+ it { expect { @unknown > @v0_0_1 }.to raise_error(ArgumentError) }
+ it { expect { @unknown < @v0_0_1 }.to raise_error(ArgumentError) }
end
describe '.parse' do
+ it { expect(described_class.parse(described_class.new(1, 0, 0))).to eq(@v1_0_0) }
it { expect(described_class.parse("1.0.0")).to eq(@v1_0_0) }
it { expect(described_class.parse("1.0.0.1")).to eq(@v1_0_0) }
it { expect(described_class.parse("1.0.0-ee")).to eq(@v1_0_0) }
@@ -133,6 +134,20 @@ RSpec.describe Gitlab::VersionInfo do
it { expect(@unknown.to_s).to eq("Unknown") }
end
+ describe '.to_json' do
+ let(:correct_version) do
+ "{\"major\":1,\"minor\":0,\"patch\":1}"
+ end
+
+ let(:unknown_version) do
+ "{\"major\":0,\"minor\":0,\"patch\":0}"
+ end
+
+ it { expect(@v1_0_1.to_json).to eq(correct_version) }
+ it { expect(@v1_0_1_rc2.to_json).to eq(correct_version) }
+ it { expect(@unknown.to_json).to eq(unknown_version) }
+ end
+
describe '.hash' do
it { expect(described_class.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
it { expect(described_class.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index ba49c00245e..aeca7b09a88 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -10,6 +10,25 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
let(:gcp_project_id) { String('gcp_proj_id') }
let(:operation) { true }
let(:database_instance) { Google::Apis::SqladminV1beta4::DatabaseInstance.new(state: 'RUNNABLE') }
+ let(:instance_name) { 'mock-instance-name' }
+ let(:root_password) { 'mock-root-password' }
+ let(:database_version) { 'mock-database-version' }
+ let(:region) { 'mock-region' }
+ let(:tier) { 'mock-tier' }
+
+ let(:database_list) do
+ Google::Apis::SqladminV1beta4::ListDatabasesResponse.new(items: [
+ Google::Apis::SqladminV1beta4::Database.new(name: 'db_01', instance: database_instance),
+ Google::Apis::SqladminV1beta4::Database.new(name: 'db_02', instance: database_instance)
+ ])
+ end
+
+ let(:user_list) do
+ Google::Apis::SqladminV1beta4::ListUsersResponse.new(items: [
+ Google::Apis::SqladminV1beta4::User.new(name: 'user_01', instance: database_instance),
+ Google::Apis::SqladminV1beta4::User.new(name: 'user_02', instance: database_instance)
+ ])
+ end
describe '.session_key_for_redirect_uri' do
let(:state) { 'random_string' }
@@ -217,7 +236,11 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
describe '#list_projects' do
subject { client.list_projects }
- let(:list_of_projects) { [{}, {}, {}] }
+ let(:gcp_project_01) { Google::Apis::CloudresourcemanagerV1::Project.new(project_id: '01') }
+ let(:gcp_project_02) { Google::Apis::CloudresourcemanagerV1::Project.new(project_id: '02') }
+ let(:gcp_project_03) { Google::Apis::CloudresourcemanagerV1::Project.new(project_id: '03') }
+ let(:list_of_projects) { [gcp_project_03, gcp_project_01, gcp_project_02] }
+
let(:next_page_token) { nil }
let(:operation) { double('projects': list_of_projects, 'next_page_token': next_page_token) }
@@ -225,7 +248,8 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
expect_any_instance_of(Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService)
.to receive(:list_projects)
.and_return(operation)
- is_expected.to eq(list_of_projects)
+
+ is_expected.to contain_exactly(gcp_project_01, gcp_project_02, gcp_project_03)
end
end
@@ -337,6 +361,42 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
end
end
+ describe '#enable_cloud_sql_admin' do
+ subject { client.enable_cloud_sql_admin(gcp_project_id) }
+
+ it 'calls Google Api ServiceUsageService' do
+ expect_any_instance_of(Google::Apis::ServiceusageV1::ServiceUsageService)
+ .to receive(:enable_service)
+ .with("projects/#{gcp_project_id}/services/sqladmin.googleapis.com")
+ .and_return(operation)
+ is_expected.to eq(operation)
+ end
+ end
+
+ describe '#enable_compute' do
+ subject { client.enable_compute(gcp_project_id) }
+
+ it 'calls Google Api ServiceUsageService' do
+ expect_any_instance_of(Google::Apis::ServiceusageV1::ServiceUsageService)
+ .to receive(:enable_service)
+ .with("projects/#{gcp_project_id}/services/compute.googleapis.com")
+ .and_return(operation)
+ is_expected.to eq(operation)
+ end
+ end
+
+ describe '#enable_service_networking' do
+ subject { client.enable_service_networking(gcp_project_id) }
+
+ it 'calls Google Api ServiceUsageService' do
+ expect_any_instance_of(Google::Apis::ServiceusageV1::ServiceUsageService)
+ .to receive(:enable_service)
+ .with("projects/#{gcp_project_id}/services/servicenetworking.googleapis.com")
+ .and_return(operation)
+ is_expected.to eq(operation)
+ end
+ end
+
describe '#revoke_authorizations' do
subject { client.revoke_authorizations }
@@ -388,4 +448,57 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
is_expected.to eq(database_instance)
end
end
+
+ describe '#list_cloudsql_databases' do
+ subject { client.list_cloudsql_databases(:gcp_project_id, :instance_name) }
+
+ it 'calls Google Api SQLAdminService#list_databases' do
+ expect_any_instance_of(Google::Apis::SqladminV1beta4::SQLAdminService)
+ .to receive(:list_databases)
+ .with(any_args)
+ .and_return(database_list)
+ is_expected.to eq(database_list)
+ end
+ end
+
+ describe '#list_cloudsql_users' do
+ subject { client.list_cloudsql_users(:gcp_project_id, :instance_name) }
+
+ it 'calls Google Api SQLAdminService#list_users' do
+ expect_any_instance_of(Google::Apis::SqladminV1beta4::SQLAdminService)
+ .to receive(:list_users)
+ .with(any_args)
+ .and_return(user_list)
+ is_expected.to eq(user_list)
+ end
+ end
+
+ describe '#create_cloudsql_instance' do
+ subject do
+ client.create_cloudsql_instance(
+ gcp_project_id,
+ instance_name,
+ root_password,
+ database_version,
+ region,
+ tier
+ )
+ end
+
+ it 'calls Google Api SQLAdminService#insert_instance' do
+ expect_any_instance_of(Google::Apis::SqladminV1beta4::SQLAdminService)
+ .to receive(:insert_instance)
+ .with(gcp_project_id,
+ having_attributes(
+ class: ::Google::Apis::SqladminV1beta4::DatabaseInstance,
+ name: instance_name,
+ root_password: root_password,
+ database_version: database_version,
+ region: region,
+ settings: instance_of(Google::Apis::SqladminV1beta4::Settings)
+ ))
+ .and_return(operation)
+ is_expected.to eq(operation)
+ end
+ end
end
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
index 6d2026752d6..b77345d8b7a 100644
--- a/spec/lib/json_web_token/rsa_token_spec.rb
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -30,8 +30,9 @@ RSpec.describe JSONWebToken::RSAToken do
subject { JWT.decode(rsa_encoded, rsa_key, true, { algorithm: 'RS256' }) }
- it { expect {subject}.not_to raise_error }
+ it { expect { subject }.not_to raise_error }
it { expect(subject.first).to include('key' => 'value') }
+
it do
expect(subject.second).to eq(
"typ" => "JWT",
@@ -45,7 +46,7 @@ RSpec.describe JSONWebToken::RSAToken do
subject { JWT.decode(rsa_encoded, new_key, true, { algorithm: 'RS256' }) }
- it { expect {subject}.to raise_error(JWT::DecodeError) }
+ it { expect { subject }.to raise_error(JWT::DecodeError) }
end
end
end
diff --git a/spec/lib/marginalia_spec.rb b/spec/lib/marginalia_spec.rb
index 693b7bd45c9..59add4e8347 100644
--- a/spec/lib/marginalia_spec.rb
+++ b/spec/lib/marginalia_spec.rb
@@ -11,10 +11,15 @@ RSpec.describe 'Marginalia spec' do
render body: nil
end
+ def first_ci_pipeline
+ Ci::Pipeline.first
+ render body: nil
+ end
+
private
[:auth_user, :current_user, :set_experimentation_subject_id_cookie, :signed_in?].each do |method|
- define_method(method) { }
+ define_method(method) {}
end
end
@@ -36,7 +41,7 @@ RSpec.describe 'Marginalia spec' do
describe 'For rails web requests' do
let(:correlation_id) { SecureRandom.uuid }
- let(:recorded) { ActiveRecord::QueryRecorder.new { make_request(correlation_id) } }
+ let(:recorded) { ActiveRecord::QueryRecorder.new { make_request(correlation_id, :first_user) } }
let(:component_map) do
{
@@ -54,10 +59,11 @@ RSpec.describe 'Marginalia spec' do
end
context 'when using CI database' do
+ let(:recorded) { ActiveRecord::QueryRecorder.new { make_request(correlation_id, :first_ci_pipeline) } }
let(:component_map) do
{
"application" => "test",
- "endpoint_id" => "MarginaliaTestController#first_user",
+ "endpoint_id" => "MarginaliaTestController#first_ci_pipeline",
"correlation_id" => correlation_id,
"db_config_name" => 'ci'
}
@@ -65,8 +71,6 @@ RSpec.describe 'Marginalia spec' do
before do
skip_if_multiple_databases_not_setup
-
- allow(User).to receive(:connection) { Ci::ApplicationRecord.connection }
end
it 'generates a query that includes the component and value' do
@@ -140,11 +144,11 @@ RSpec.describe 'Marginalia spec' do
end
end
- def make_request(correlation_id)
+ def make_request(correlation_id, action_name)
request_env = Rack::MockRequest.env_for('/')
::Labkit::Correlation::CorrelationId.use_id(correlation_id) do
- MarginaliaTestController.action(:first_user).call(request_env)
+ MarginaliaTestController.action(action_name).call(request_env)
end
end
end
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index 2158076e4b5..d208ef93224 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Mattermost::Session, type: :request do
describe '#with session' do
let(:location) { 'http://location.tld' }
- let(:cookie_header) {'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;'}
+ let(:cookie_header) { 'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;' }
let!(:stub) do
stub_full_request("#{mattermost_url}/oauth/gitlab/login")
.to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 302)
diff --git a/spec/lib/microsoft_teams/notifier_spec.rb b/spec/lib/microsoft_teams/notifier_spec.rb
index 3b7892334dd..905b118d934 100644
--- a/spec/lib/microsoft_teams/notifier_spec.rb
+++ b/spec/lib/microsoft_teams/notifier_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe MicrosoftTeams::Notifier do
subject { described_class.new(webhook_url) }
- let(:webhook_url) { 'https://example.gitlab.com/'}
+ let(:webhook_url) { 'https://example.gitlab.com/' }
let(:header) { { 'Content-Type' => 'application/json' } }
let(:options) do
{
diff --git a/spec/lib/release_highlights/validator/entry_spec.rb b/spec/lib/release_highlights/validator/entry_spec.rb
index 5f7ccbf4310..b8b745ac8cd 100644
--- a/spec/lib/release_highlights/validator/entry_spec.rb
+++ b/spec/lib/release_highlights/validator/entry_spec.rb
@@ -25,18 +25,18 @@ RSpec.describe ReleaseHighlights::Validator::Entry do
it 'returns line numbers in errors' do
subject.valid?
- expect(entry.errors[:packages].first).to match('(line 6)')
+ expect(entry.errors[:available_in].first).to match('(line 6)')
end
end
context 'with a blank entry' do
- it 'validate presence of title, body and stage' do
+ it 'validate presence of name, description and stage' do
subject.valid?
- expect(subject.errors[:title]).not_to be_empty
- expect(subject.errors[:body]).not_to be_empty
+ expect(subject.errors[:name]).not_to be_empty
+ expect(subject.errors[:description]).not_to be_empty
expect(subject.errors[:stage]).not_to be_empty
- expect(subject.errors[:packages]).not_to be_empty
+ expect(subject.errors[:available_in]).not_to be_empty
end
it 'validates boolean value of "self-managed" and "gitlab-com"' do
@@ -52,11 +52,11 @@ RSpec.describe ReleaseHighlights::Validator::Entry do
it 'validates URI of "url" and "image_url"' do
stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
allow(entry).to receive(:value_for).with(:image_url).and_return('https://foobar.x/images/ci/gitlab-ci-cd-logo_2x.png')
- allow(entry).to receive(:value_for).with(:url).and_return('')
+ allow(entry).to receive(:value_for).with(:documentation_link).and_return('')
subject.valid?
- expect(subject.errors[:url]).to include(/must be a valid URL/)
+ expect(subject.errors[:documentation_link]).to include(/must be a valid URL/)
expect(subject.errors[:image_url]).to include(/is blocked: Host cannot be resolved or invalid/)
end
@@ -76,12 +76,12 @@ RSpec.describe ReleaseHighlights::Validator::Entry do
expect(subject.errors[:published_at]).to include(/must be valid Date/)
end
- it 'validates packages are included in list' do
- allow(entry).to receive(:value_for).with(:packages).and_return(['ALL'])
+ it 'validates available_in are included in list' do
+ allow(entry).to receive(:value_for).with(:available_in).and_return(['ALL'])
subject.valid?
- expect(subject.errors[:packages].first).to include("must be one of", "Free", "Premium", "Ultimate")
+ expect(subject.errors[:available_in].first).to include("must be one of", "Free", "Premium", "Ultimate")
end
end
end
diff --git a/spec/lib/release_highlights/validator_spec.rb b/spec/lib/release_highlights/validator_spec.rb
index f30754b4167..dd1b3aa4803 100644
--- a/spec/lib/release_highlights/validator_spec.rb
+++ b/spec/lib/release_highlights/validator_spec.rb
@@ -70,7 +70,7 @@ RSpec.describe ReleaseHighlights::Validator do
---------------------------------------------------------
Validation failed for spec/fixtures/whats_new/invalid.yml
---------------------------------------------------------
- * Packages must be one of ["Free", "Premium", "Ultimate"] (line 6)
+ * Available in must be one of ["Free", "Premium", "Ultimate"] (line 6)
MESSAGE
end
diff --git a/spec/lib/security/report_schema_version_matcher_spec.rb b/spec/lib/security/report_schema_version_matcher_spec.rb
new file mode 100644
index 00000000000..9c40f0bc6fa
--- /dev/null
+++ b/spec/lib/security/report_schema_version_matcher_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Security::ReportSchemaVersionMatcher do
+ let(:vendored_versions) { %w[14.0.0 14.0.1 14.0.2 14.1.0] }
+ let(:version_finder) do
+ described_class.new(
+ report_declared_version: report_version,
+ supported_versions: vendored_versions
+ )
+ end
+
+ describe '#call' do
+ subject { version_finder.call }
+
+ context 'when minor version matches' do
+ context 'and report schema patch version does not match any vendored schema versions' do
+ context 'and report version is 14.1.1' do
+ let(:report_version) { '14.1.1' }
+
+ it 'returns 14.1.0' do
+ expect(subject).to eq('14.1.0')
+ end
+ end
+
+ context 'and report version is 14.0.32' do
+ let(:report_version) { '14.0.32' }
+
+ it 'returns 14.0.2' do
+ expect(subject).to eq('14.0.2')
+ end
+ end
+ end
+ end
+
+ context 'when report minor version does not match' do
+ let(:report_version) { '14.2.1' }
+
+ it 'does not return a version' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/sidebars/groups/menus/group_information_menu_spec.rb b/spec/lib/sidebars/groups/menus/group_information_menu_spec.rb
index 5f67ee11970..1b27db53b6f 100644
--- a/spec/lib/sidebars/groups/menus/group_information_menu_spec.rb
+++ b/spec/lib/sidebars/groups/menus/group_information_menu_spec.rb
@@ -18,13 +18,13 @@ RSpec.describe Sidebars::Groups::Menus::GroupInformationMenu do
subject { described_class.new(context).title }
context 'when group is a root group' do
- specify { is_expected.to eq 'Group information'}
+ specify { is_expected.to eq 'Group information' }
end
context 'when group is a child group' do
let(:group) { build(:group, parent: root_group) }
- specify { is_expected.to eq 'Subgroup information'}
+ specify { is_expected.to eq 'Subgroup information' }
end
end
@@ -32,13 +32,13 @@ RSpec.describe Sidebars::Groups::Menus::GroupInformationMenu do
subject { described_class.new(context).sprite_icon }
context 'when group is a root group' do
- specify { is_expected.to eq 'group'}
+ specify { is_expected.to eq 'group' }
end
context 'when group is a child group' do
let(:group) { build(:group, parent: root_group) }
- specify { is_expected.to eq 'subgroup'}
+ specify { is_expected.to eq 'subgroup' }
end
end
diff --git a/spec/lib/sidebars/menu_spec.rb b/spec/lib/sidebars/menu_spec.rb
index bdd9f22d5a0..53a889c2db8 100644
--- a/spec/lib/sidebars/menu_spec.rb
+++ b/spec/lib/sidebars/menu_spec.rb
@@ -219,7 +219,7 @@ RSpec.describe Sidebars::Menu do
end
describe '#link' do
- let(:foo_path) { '/foo_path'}
+ let(:foo_path) { '/foo_path' }
let(:foo_menu) do
::Sidebars::MenuItem.new(
diff --git a/spec/lib/tasks/gitlab/metrics_exporter_task_spec.rb b/spec/lib/tasks/gitlab/metrics_exporter_task_spec.rb
index dfb3c511470..4e17e91f019 100644
--- a/spec/lib/tasks/gitlab/metrics_exporter_task_spec.rb
+++ b/spec/lib/tasks/gitlab/metrics_exporter_task_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe 'gitlab:metrics_exporter:install' do
let(:expected_clone_params) do
{
repo: 'https://gitlab.com/gitlab-org/gitlab-metrics-exporter.git',
- version: 'main',
+ version: an_instance_of(String),
target_dir: 'path/to/exporter'
}
end
diff --git a/spec/lib/unnested_in_filters/rewriter_spec.rb b/spec/lib/unnested_in_filters/rewriter_spec.rb
index e2ccbd92504..a808aec7728 100644
--- a/spec/lib/unnested_in_filters/rewriter_spec.rb
+++ b/spec/lib/unnested_in_filters/rewriter_spec.rb
@@ -88,6 +88,35 @@ RSpec.describe UnnestedInFilters::Rewriter do
expect(issued_query.gsub(/\s/, '')).to start_with(expected_query.gsub(/\s/, ''))
end
+ context 'when the relation has a subquery' do
+ let(:relation) { User.where(state: User.select(:state), user_type: %i(support_bot alert_bot)).limit(1) }
+
+ let(:expected_query) do
+ <<~SQL
+ SELECT
+ "users".*
+ FROM
+ unnest(ARRAY(SELECT "users"."state" FROM "users")::character varying[]) AS "states"("state"),
+ unnest('{1,2}'::smallint[]) AS "user_types"("user_type"),
+ LATERAL (
+ SELECT
+ "users".*
+ FROM
+ "users"
+ WHERE
+ (users."state" = "states"."state") AND
+ (users."user_type" = "user_types"."user_type")
+ LIMIT 1
+ ) AS users
+ LIMIT 1
+ SQL
+ end
+
+ it 'changes the query' do
+ expect(issued_query.gsub(/\s/, '')).to start_with(expected_query.gsub(/\s/, ''))
+ end
+ end
+
context 'when there is an order' do
let(:relation) { User.where(state: %w(active blocked banned)).order(order).limit(2) }
let(:expected_query) do