summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab')
-rw-r--r--spec/lib/gitlab/access/branch_protection_spec.rb17
-rw-r--r--spec/lib/gitlab/alerting/alert_spec.rb11
-rw-r--r--spec/lib/gitlab/auth/current_user_mode_spec.rb404
-rw-r--r--spec/lib/gitlab/auth/key_status_checker_spec.rb68
-rw-r--r--spec/lib/gitlab/auth/ldap/access_spec.rb10
-rw-r--r--spec/lib/gitlab/auth/ldap/adapter_spec.rb10
-rw-r--r--spec/lib/gitlab/auth/ldap/auth_hash_spec.rb4
-rw-r--r--spec/lib/gitlab/auth/ldap/authentication_spec.rb8
-rw-r--r--spec/lib/gitlab/auth/ldap/config_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/dn_spec.rb50
-rw-r--r--spec/lib/gitlab/auth/ldap/person_spec.rb4
-rw-r--r--spec/lib/gitlab/auth/ldap/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb36
-rw-r--r--spec/lib/gitlab/auth/saml/user_spec.rb16
-rw-r--r--spec/lib/gitlab/auth_spec.rb13
-rw-r--r--spec/lib/gitlab/authorized_keys_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb154
-rw-r--r--spec/lib/gitlab/background_migration/digest_column_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_columns_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb13
-rw-r--r--spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb113
-rw-r--r--spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/reset_merge_status_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb109
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb9
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb13
-rw-r--r--spec/lib/gitlab/cache/import/caching_spec.rb (renamed from spec/lib/gitlab/github_import/caching_spec.rb)2
-rw-r--r--spec/lib/gitlab/checks/branch_check_spec.rb20
-rw-r--r--spec/lib/gitlab/checks/diff_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/lfs_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_created_spec.rb29
-rw-r--r--spec/lib/gitlab/checks/project_moved_spec.rb103
-rw-r--r--spec/lib/gitlab/checks/push_check_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/push_file_count_check_spec.rb53
-rw-r--r--spec/lib/gitlab/checks/snippet_check_spec.rb43
-rw-r--r--spec/lib/gitlab/checks/tag_check_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/artifact_file_reader_spec.rb100
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/bridge_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb42
-rw-r--r--spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb42
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb381
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/config/external/file/artifact_spec.rb167
-rw-r--r--spec/lib/gitlab/ci/config/external/file/local_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/config/external/file/project_spec.rb24
-rw-r--r--spec/lib/gitlab/ci/config/external/file/template_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb110
-rw-r--r--spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb176
-rw-r--r--spec/lib/gitlab/ci/parsers/test/junit_spec.rb69
-rw-r--r--spec/lib/gitlab/ci/parsers_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb88
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb43
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb59
-rw-r--r--spec/lib/gitlab/ci/reports/coverage_reports_spec.rb66
-rw-r--r--spec/lib/gitlab/ci/reports/test_case_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/status/composite_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/templates_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/trace/chunked_io_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb102
-rw-r--r--spec/lib/gitlab/config/entry/attributable_spec.rb2
-rw-r--r--spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb65
-rw-r--r--spec/lib/gitlab/danger/commit_linter_spec.rb47
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb6
-rw-r--r--spec/lib/gitlab/data_builder/wiki_page_spec.rb2
-rw-r--r--spec/lib/gitlab/database/connection_timer_spec.rb100
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb157
-rw-r--r--spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb55
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb28
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb40
-rw-r--r--spec/lib/gitlab/elasticsearch/logs_spec.rb89
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb6
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb43
-rw-r--r--spec/lib/gitlab/file_type_detection_spec.rb29
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb62
-rw-r--r--spec/lib/gitlab/git/lfs_changes_spec.rb2
-rw-r--r--spec/lib/gitlab/git/merge_base_spec.rb2
-rw-r--r--spec/lib/gitlab/git/push_spec.rb3
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb116
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb37
-rw-r--r--spec/lib/gitlab/git_access_snippet_spec.rb212
-rw-r--r--spec/lib/gitlab/git_access_spec.rb82
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb4
-rw-r--r--spec/lib/gitlab/git_post_receive_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb14
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb8
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb13
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb24
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb17
-rw-r--r--spec/lib/gitlab/github_import/issuable_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/label_finder_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/milestone_finder_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/page_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/parallel_scheduling_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/user_finder_spec.rb22
-rw-r--r--spec/lib/gitlab/github_import_spec.rb2
-rw-r--r--spec/lib/gitlab/gl_repository/repo_type_spec.rb78
-rw-r--r--spec/lib/gitlab/gl_repository_spec.rb9
-rw-r--r--spec/lib/gitlab/global_id_spec.rb2
-rw-r--r--spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb49
-rw-r--r--spec/lib/gitlab/graphql/docs/renderer_spec.rb96
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb9
-rw-r--r--spec/lib/gitlab/graphql/timeout_spec.rb23
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb4
-rw-r--r--spec/lib/gitlab/hook_data/issuable_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb4
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/import/merge_request_helpers_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml14
-rw-r--r--spec/lib/gitlab/import_export/base/object_builder_spec.rb (renamed from spec/lib/gitlab/import_export/base_object_builder_spec.rb)4
-rw-r--r--spec/lib/gitlab/import_export/base/relation_factory_spec.rb (renamed from spec/lib/gitlab/import_export/base_relation_factory_spec.rb)15
-rw-r--r--spec/lib/gitlab/import_export/error_spec.rb31
-rw-r--r--spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/group/object_builder_spec.rb (renamed from spec/lib/gitlab/import_export/group_object_builder_spec.rb)2
-rw-r--r--spec/lib/gitlab/import_export/group/relation_factory_spec.rb (renamed from spec/lib/gitlab/import_export/group_relation_factory_spec.rb)4
-rw-r--r--spec/lib/gitlab/import_export/group/tree_restorer_spec.rb (renamed from spec/lib/gitlab/import_export/group_tree_restorer_spec.rb)2
-rw-r--r--spec/lib/gitlab/import_export/group/tree_saver_spec.rb (renamed from spec/lib/gitlab/import_export/group_tree_saver_spec.rb)8
-rw-r--r--spec/lib/gitlab/import_export/import_export_spec.rb8
-rw-r--r--spec/lib/gitlab/import_export/import_test_coverage_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/json/legacy_reader_spec.rb149
-rw-r--r--spec/lib/gitlab/import_export/json/legacy_writer_spec.rb79
-rw-r--r--spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb (renamed from spec/lib/gitlab/import_export/relation_tree_saver_spec.rb)2
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb294
-rw-r--r--spec/lib/gitlab/import_export/merge_request_parser_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/project/export_task_spec.rb69
-rw-r--r--spec/lib/gitlab/import_export/project/import_task_spec.rb96
-rw-r--r--spec/lib/gitlab/import_export/project/legacy_tree_saver_spec.rb (renamed from spec/lib/gitlab/import_export/project_tree_saver_spec.rb)6
-rw-r--r--spec/lib/gitlab/import_export/project/object_builder_spec.rb (renamed from spec/lib/gitlab/import_export/group_project_object_builder_spec.rb)2
-rw-r--r--spec/lib/gitlab/import_export/project/relation_factory_spec.rb (renamed from spec/lib/gitlab/import_export/project_relation_factory_spec.rb)10
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb (renamed from spec/lib/gitlab/import_export/project_tree_restorer_spec.rb)62
-rw-r--r--spec/lib/gitlab/import_export/project/tree_saver_spec.rb346
-rw-r--r--spec/lib/gitlab/import_export/project_tree_loader_spec.rb49
-rw-r--r--spec/lib/gitlab/import_export/relation_rename_service_spec.rb122
-rw-r--r--spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb27
-rw-r--r--spec/lib/gitlab/import_export/repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml10
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb70
-rw-r--r--spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb48
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb55
-rw-r--r--spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb56
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb4
-rw-r--r--spec/lib/gitlab/incoming_email_spec.rb11
-rw-r--r--spec/lib/gitlab/job_waiter_spec.rb35
-rw-r--r--spec/lib/gitlab/json_cache_spec.rb3
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/namespace_spec.rb8
-rw-r--r--spec/lib/gitlab/language_detection_spec.rb6
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/lograge/custom_options_spec.rb50
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb82
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb11
-rw-r--r--spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb12
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb33
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/profiler_spec.rb22
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb72
-rw-r--r--spec/lib/gitlab/project_template_spec.rb3
-rw-r--r--spec/lib/gitlab/prometheus/query_variables_spec.rb4
-rw-r--r--spec/lib/gitlab/quick_actions/extractor_spec.rb43
-rw-r--r--spec/lib/gitlab/quick_actions/substitution_definition_spec.rb2
-rw-r--r--spec/lib/gitlab/rate_limit_helpers_spec.rb50
-rw-r--r--spec/lib/gitlab/reactive_cache_set_cache_spec.rb74
-rw-r--r--spec/lib/gitlab/reference_counter_spec.rb62
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb18
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb93
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb3
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb43
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb91
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb6
-rw-r--r--spec/lib/gitlab/serverless/domain_spec.rb22
-rw-r--r--spec/lib/gitlab/serverless/function_uri_spec.rb81
-rw-r--r--spec/lib/gitlab/serverless/service_spec.rb10
-rw-r--r--spec/lib/gitlab/shell_spec.rb314
-rw-r--r--spec/lib/gitlab/sidekiq_cluster/cli_spec.rb282
-rw-r--r--spec/lib/gitlab/sidekiq_cluster_spec.rb196
-rw-r--r--spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb48
-rw-r--r--spec/lib/gitlab/sidekiq_config/worker_spec.rb17
-rw-r--r--spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb33
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb19
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb29
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb34
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb157
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb52
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb72
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb15
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb33
-rw-r--r--spec/lib/gitlab/sidekiq_middleware_spec.rb26
-rw-r--r--spec/lib/gitlab/sidekiq_queue_spec.rb87
-rw-r--r--spec/lib/gitlab/slash_commands/issue_move_spec.rb10
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb10
-rw-r--r--spec/lib/gitlab/template/finders/global_template_finder_spec.rb84
-rw-r--r--spec/lib/gitlab/template/finders/repo_template_finders_spec.rb3
-rw-r--r--spec/lib/gitlab/tracing_spec.rb69
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb64
-rw-r--r--spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb58
-rw-r--r--spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb75
-rw-r--r--spec/lib/gitlab/url_blockers/url_whitelist_spec.rb106
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb18
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb43
-rw-r--r--spec/lib/gitlab/user_access_snippet_spec.rb95
-rw-r--r--spec/lib/gitlab/user_access_spec.rb47
-rw-r--r--spec/lib/gitlab/utils/json_size_estimator_spec.rb39
-rw-r--r--spec/lib/gitlab/utils/log_limited_array_spec.rb22
-rw-r--r--spec/lib/gitlab/utils_spec.rb8
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/lib/gitlab/x509/commit_spec.rb16
262 files changed, 7570 insertions, 1994 deletions
diff --git a/spec/lib/gitlab/access/branch_protection_spec.rb b/spec/lib/gitlab/access/branch_protection_spec.rb
index 7f2979e8e28..e4b763357c4 100644
--- a/spec/lib/gitlab/access/branch_protection_spec.rb
+++ b/spec/lib/gitlab/access/branch_protection_spec.rb
@@ -51,4 +51,21 @@ describe Gitlab::Access::BranchProtection do
end
end
end
+
+ describe '#fully_protected?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false
+ Gitlab::Access::PROTECTION_FULL | true
+ end
+
+ with_them do
+ it do
+ expect(described_class.new(level).fully_protected?).to eq(result)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/alerting/alert_spec.rb b/spec/lib/gitlab/alerting/alert_spec.rb
index 90e93d189e2..790edbc9c60 100644
--- a/spec/lib/gitlab/alerting/alert_spec.rb
+++ b/spec/lib/gitlab/alerting/alert_spec.rb
@@ -17,6 +17,12 @@ describe Gitlab::Alerting::Alert do
end
end
+ shared_context 'full query' do
+ before do
+ payload['generatorURL'] = 'http://localhost:9090/graph?g0.expr=vector%281%29'
+ end
+ end
+
shared_examples 'invalid alert' do
it 'is invalid' do
expect(alert).not_to be_valid
@@ -180,10 +186,7 @@ describe Gitlab::Alerting::Alert do
context 'with gitlab alert' do
include_context 'gitlab alert'
-
- before do
- payload['generatorURL'] = 'http://localhost:9090/graph?g0.expr=vector%281%29'
- end
+ include_context 'full query'
it { is_expected.to eq(gitlab_alert.full_query) }
end
diff --git a/spec/lib/gitlab/auth/current_user_mode_spec.rb b/spec/lib/gitlab/auth/current_user_mode_spec.rb
index 7c2fdac6c25..2b910fac155 100644
--- a/spec/lib/gitlab/auth/current_user_mode_spec.rb
+++ b/spec/lib/gitlab/auth/current_user_mode_spec.rb
@@ -3,294 +3,330 @@
require 'spec_helper'
describe Gitlab::Auth::CurrentUserMode, :do_not_mock_admin_mode, :request_store do
- include_context 'custom session'
-
let(:user) { build_stubbed(:user) }
subject { described_class.new(user) }
- before do
- allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session])
- end
-
- shared_examples 'admin mode cannot be enabled' do
- it 'is false by default' do
- expect(subject.admin_mode?).to be(false)
- end
-
- it 'cannot be enabled with a valid password' do
- subject.enable_admin_mode!(password: user.password)
-
- expect(subject.admin_mode?).to be(false)
- end
-
- it 'cannot be enabled with an invalid password' do
- subject.enable_admin_mode!(password: nil)
-
- expect(subject.admin_mode?).to be(false)
- end
-
- it 'cannot be enabled with empty params' do
- subject.enable_admin_mode!
+ context 'when session is available' do
+ include_context 'custom session'
- expect(subject.admin_mode?).to be(false)
+ before do
+ allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session])
end
- it 'disable has no effect' do
- subject.enable_admin_mode!
- subject.disable_admin_mode!
-
- expect(subject.admin_mode?).to be(false)
- end
+ shared_examples 'admin mode cannot be enabled' do
+ it 'is false by default' do
+ expect(subject.admin_mode?).to be(false)
+ end
- context 'skipping password validation' do
it 'cannot be enabled with a valid password' do
- subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
+ subject.enable_admin_mode!(password: user.password)
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with an invalid password' do
- subject.enable_admin_mode!(skip_password_validation: true)
+ subject.enable_admin_mode!(password: nil)
expect(subject.admin_mode?).to be(false)
end
- end
- end
- describe '#admin_mode?' do
- context 'when the user is a regular user' do
- it_behaves_like 'admin mode cannot be enabled'
+ it 'cannot be enabled with empty params' do
+ subject.enable_admin_mode!
- context 'bypassing session' do
- it_behaves_like 'admin mode cannot be enabled' do
- around do |example|
- described_class.bypass_session!(user.id) { example.run }
- end
- end
+ expect(subject.admin_mode?).to be(false)
end
- end
-
- context 'when the user is an admin' do
- let(:user) { build_stubbed(:user, :admin) }
- context 'when admin mode not requested' do
- it 'is false by default' do
- expect(subject.admin_mode?).to be(false)
- end
-
- it 'raises exception if we try to enable it' do
- expect do
- subject.enable_admin_mode!(password: user.password)
- end.to raise_error(::Gitlab::Auth::CurrentUserMode::NotRequestedError)
+ it 'disable has no effect' do
+ subject.enable_admin_mode!
+ subject.disable_admin_mode!
- expect(subject.admin_mode?).to be(false)
- end
+ expect(subject.admin_mode?).to be(false)
end
- context 'when admin mode requested first' do
- before do
- subject.request_admin_mode!
- end
+ context 'skipping password validation' do
+ it 'cannot be enabled with a valid password' do
+ subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
- it 'is false by default' do
expect(subject.admin_mode?).to be(false)
end
it 'cannot be enabled with an invalid password' do
- subject.enable_admin_mode!(password: nil)
+ subject.enable_admin_mode!(skip_password_validation: true)
expect(subject.admin_mode?).to be(false)
end
+ end
+ end
- it 'can be enabled with a valid password' do
- subject.enable_admin_mode!(password: user.password)
+ describe '#admin_mode?' do
+ context 'when the user is a regular user' do
+ it_behaves_like 'admin mode cannot be enabled'
- expect(subject.admin_mode?).to be(true)
+ context 'bypassing session' do
+ it_behaves_like 'admin mode cannot be enabled' do
+ around do |example|
+ described_class.bypass_session!(user.id) { example.run }
+ end
+ end
end
+ end
- it 'can be disabled' do
- subject.enable_admin_mode!(password: user.password)
- subject.disable_admin_mode!
+ context 'when the user is an admin' do
+ let(:user) { build_stubbed(:user, :admin) }
- expect(subject.admin_mode?).to be(false)
+ context 'when admin mode not requested' do
+ it 'is false by default' do
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'raises exception if we try to enable it' do
+ expect do
+ subject.enable_admin_mode!(password: user.password)
+ end.to raise_error(::Gitlab::Auth::CurrentUserMode::NotRequestedError)
+
+ expect(subject.admin_mode?).to be(false)
+ end
end
- it 'will expire in the future' do
- subject.enable_admin_mode!(password: user.password)
- expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present'
+ context 'when admin mode requested first' do
+ before do
+ subject.request_admin_mode!
+ end
- Timecop.freeze(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do
- # in the future this will be a new request, simulate by clearing the RequestStore
- Gitlab::SafeRequestStore.clear!
+ it 'is false by default' do
+ expect(subject.admin_mode?).to be(false)
+ end
+
+ it 'cannot be enabled with an invalid password' do
+ subject.enable_admin_mode!(password: nil)
- expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future'
+ expect(subject.admin_mode?).to be(false)
end
- end
- context 'skipping password validation' do
it 'can be enabled with a valid password' do
- subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
+ subject.enable_admin_mode!(password: user.password)
expect(subject.admin_mode?).to be(true)
end
- it 'can be enabled with an invalid password' do
- subject.enable_admin_mode!(skip_password_validation: true)
+ it 'can be disabled' do
+ subject.enable_admin_mode!(password: user.password)
+ subject.disable_admin_mode!
- expect(subject.admin_mode?).to be(true)
+ expect(subject.admin_mode?).to be(false)
end
- end
- context 'with two independent sessions' do
- let(:another_session) { {} }
- let(:another_subject) { described_class.new(user) }
+ it 'will expire in the future' do
+ subject.enable_admin_mode!(password: user.password)
+ expect(subject.admin_mode?).to be(true), 'admin mode is not active in the present'
- before do
- allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session])
+ Timecop.freeze(Gitlab::Auth::CurrentUserMode::MAX_ADMIN_MODE_TIME.from_now) do
+ # in the future this will be a new request, simulate by clearing the RequestStore
+ Gitlab::SafeRequestStore.clear!
+
+ expect(subject.admin_mode?).to be(false), 'admin mode did not expire in the future'
+ end
end
- it 'can be enabled in one and seen in the other' do
- Gitlab::Session.with_session(another_session) do
- another_subject.request_admin_mode!
- another_subject.enable_admin_mode!(password: user.password)
+ context 'skipping password validation' do
+ it 'can be enabled with a valid password' do
+ subject.enable_admin_mode!(password: user.password, skip_password_validation: true)
+
+ expect(subject.admin_mode?).to be(true)
end
- expect(subject.admin_mode?).to be(true)
+ it 'can be enabled with an invalid password' do
+ subject.enable_admin_mode!(skip_password_validation: true)
+
+ expect(subject.admin_mode?).to be(true)
+ end
end
- end
- end
- context 'bypassing session' do
- it 'is active by default' do
- described_class.bypass_session!(user.id) do
- expect(subject.admin_mode?).to be(true)
+ context 'with two independent sessions' do
+ let(:another_session) { {} }
+ let(:another_subject) { described_class.new(user) }
+
+ before do
+ allow(ActiveSession).to receive(:list_sessions).with(user).and_return([session, another_session])
+ end
+
+ it 'can be enabled in one and seen in the other' do
+ Gitlab::Session.with_session(another_session) do
+ another_subject.request_admin_mode!
+ another_subject.enable_admin_mode!(password: user.password)
+ end
+
+ expect(subject.admin_mode?).to be(true)
+ end
end
end
- it 'enable has no effect' do
- described_class.bypass_session!(user.id) do
- subject.request_admin_mode!
- subject.enable_admin_mode!(password: user.password)
+ context 'bypassing session' do
+ it 'is active by default' do
+ described_class.bypass_session!(user.id) do
+ expect(subject.admin_mode?).to be(true)
+ end
+ end
- expect(subject.admin_mode?).to be(true)
+ it 'enable has no effect' do
+ described_class.bypass_session!(user.id) do
+ subject.request_admin_mode!
+ subject.enable_admin_mode!(password: user.password)
+
+ expect(subject.admin_mode?).to be(true)
+ end
end
- end
- it 'disable has no effect' do
- described_class.bypass_session!(user.id) do
- subject.disable_admin_mode!
+ it 'disable has no effect' do
+ described_class.bypass_session!(user.id) do
+ subject.disable_admin_mode!
- expect(subject.admin_mode?).to be(true)
+ expect(subject.admin_mode?).to be(true)
+ end
end
end
end
end
- end
- describe '#enable_admin_mode!' do
- let(:user) { build_stubbed(:user, :admin) }
+ describe '#enable_admin_mode!' do
+ let(:user) { build_stubbed(:user, :admin) }
- it 'creates a timestamp in the session' do
- subject.request_admin_mode!
- subject.enable_admin_mode!(password: user.password)
+ it 'creates a timestamp in the session' do
+ subject.request_admin_mode!
+ subject.enable_admin_mode!(password: user.password)
- expect(session).to include(expected_session_entry(be_within(1.second).of Time.now))
+ expect(session).to include(expected_session_entry(be_within(1.second).of Time.now))
+ end
end
- end
- describe '#enable_sessionless_admin_mode!' do
- let(:user) { build_stubbed(:user, :admin) }
+ describe '#disable_admin_mode!' do
+ let(:user) { build_stubbed(:user, :admin) }
- it 'enabled admin mode without password' do
- subject.enable_sessionless_admin_mode!
+ it 'sets the session timestamp to nil' do
+ subject.request_admin_mode!
+ subject.disable_admin_mode!
- expect(subject.admin_mode?).to be(true)
+ expect(session).to include(expected_session_entry(be_nil))
+ end
end
- end
- describe '#disable_admin_mode!' do
- let(:user) { build_stubbed(:user, :admin) }
+ describe '.with_current_request_admin_mode' do
+ context 'with a regular user' do
+ it 'user is not available inside nor outside the yielded block' do
+ described_class.with_current_admin(user) do
+ expect(described_class.current_admin).to be_nil
+ end
- it 'sets the session timestamp to nil' do
- subject.request_admin_mode!
- subject.disable_admin_mode!
+ expect(described_class.bypass_session_admin_id).to be_nil
+ end
+ end
- expect(session).to include(expected_session_entry(be_nil))
- end
- end
+ context 'with an admin user' do
+ let(:user) { build_stubbed(:user, :admin) }
- describe '.bypass_session!' do
- context 'with a regular user' do
- it 'admin mode is false' do
- described_class.bypass_session!(user.id) do
- expect(subject.admin_mode?).to be(false)
- expect(described_class.bypass_session_admin_id).to be(user.id)
+ context 'admin mode is disabled' do
+ it 'user is not available inside nor outside the yielded block' do
+ described_class.with_current_admin(user) do
+ expect(described_class.current_admin).to be_nil
+ end
+
+ expect(described_class.bypass_session_admin_id).to be_nil
+ end
end
- expect(described_class.bypass_session_admin_id).to be_nil
- end
- end
+ context 'admin mode is enabled' do
+ before do
+ subject.request_admin_mode!
+ subject.enable_admin_mode!(password: user.password)
+ end
- context 'with an admin user' do
- let(:user) { build_stubbed(:user, :admin) }
+ it 'user is available only inside the yielded block' do
+ described_class.with_current_admin(user) do
+ expect(described_class.current_admin).to be(user)
+ end
- it 'admin mode is true' do
- described_class.bypass_session!(user.id) do
- expect(subject.admin_mode?).to be(true)
- expect(described_class.bypass_session_admin_id).to be(user.id)
+ expect(described_class.current_admin).to be_nil
+ end
end
-
- expect(described_class.bypass_session_admin_id).to be_nil
end
end
- end
- describe '.with_current_request_admin_mode' do
- context 'with a regular user' do
- it 'user is not available inside nor outside the yielded block' do
- described_class.with_current_admin(user) do
- expect(described_class.current_admin).to be_nil
- end
+ def expected_session_entry(value_matcher)
+ {
+ Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including(
+ Gitlab::Auth::CurrentUserMode::ADMIN_MODE_START_TIME_KEY => value_matcher)
+ }
+ end
+ end
- expect(described_class.bypass_session_admin_id).to be_nil
+ context 'when no session available' do
+ around do |example|
+ Gitlab::Session.with_session(nil) do
+ example.run
end
end
- context 'with an admin user' do
- let(:user) { build_stubbed(:user, :admin) }
+ describe '.bypass_session!' do
+ context 'when providing a block' do
+ context 'with a regular user' do
+ it 'admin mode is false' do
+ described_class.bypass_session!(user.id) do
+ expect(Gitlab::Session.current).to be_nil
+ expect(subject.admin_mode?).to be(false)
+ expect(described_class.bypass_session_admin_id).to be(user.id)
+ end
- context 'admin mode is disabled' do
- it 'user is not available inside nor outside the yielded block' do
- described_class.with_current_admin(user) do
- expect(described_class.current_admin).to be_nil
+ expect(described_class.bypass_session_admin_id).to be_nil
end
+ end
- expect(described_class.bypass_session_admin_id).to be_nil
+ context 'with an admin user' do
+ let(:user) { build_stubbed(:user, :admin) }
+
+ it 'admin mode is true' do
+ described_class.bypass_session!(user.id) do
+ expect(Gitlab::Session.current).to be_nil
+ expect(subject.admin_mode?).to be(true)
+ expect(described_class.bypass_session_admin_id).to be(user.id)
+ end
+
+ expect(described_class.bypass_session_admin_id).to be_nil
+ end
end
end
- context 'admin mode is enabled' do
- before do
- subject.request_admin_mode!
- subject.enable_admin_mode!(password: user.password)
- end
+ context 'when not providing a block' do
+ context 'with a regular user' do
+ it 'admin mode is false' do
+ described_class.bypass_session!(user.id)
- it 'user is available only inside the yielded block' do
- described_class.with_current_admin(user) do
- expect(described_class.current_admin).to be(user)
+ expect(Gitlab::Session.current).to be_nil
+ expect(subject.admin_mode?).to be(false)
+ expect(described_class.bypass_session_admin_id).to be(user.id)
+
+ described_class.reset_bypass_session!
+
+ expect(described_class.bypass_session_admin_id).to be_nil
end
+ end
- expect(described_class.current_admin).to be_nil
+ context 'with an admin user' do
+ let(:user) { build_stubbed(:user, :admin) }
+
+ it 'admin mode is true' do
+ described_class.bypass_session!(user.id)
+
+ expect(Gitlab::Session.current).to be_nil
+ expect(subject.admin_mode?).to be(true)
+ expect(described_class.bypass_session_admin_id).to be(user.id)
+
+ described_class.reset_bypass_session!
+
+ expect(described_class.bypass_session_admin_id).to be_nil
+ end
end
end
end
end
-
- def expected_session_entry(value_matcher)
- {
- Gitlab::Auth::CurrentUserMode::SESSION_STORE_KEY => a_hash_including(
- Gitlab::Auth::CurrentUserMode::ADMIN_MODE_START_TIME_KEY => value_matcher)
- }
- end
end
diff --git a/spec/lib/gitlab/auth/key_status_checker_spec.rb b/spec/lib/gitlab/auth/key_status_checker_spec.rb
new file mode 100644
index 00000000000..b1a540eae81
--- /dev/null
+++ b/spec/lib/gitlab/auth/key_status_checker_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Auth::KeyStatusChecker do
+ let_it_be(:never_expires_key) { build(:personal_key, expires_at: nil) }
+ let_it_be(:expired_key) { build(:personal_key, expires_at: 3.days.ago) }
+ let_it_be(:expiring_soon_key) { build(:personal_key, expires_at: 3.days.from_now) }
+ let_it_be(:expires_in_future_key) { build(:personal_key, expires_at: 14.days.from_now) }
+
+ let(:key_status_checker) { described_class.new(key) }
+
+ describe '#show_console_message?' do
+ subject { key_status_checker.show_console_message? }
+
+ context 'for an expired key' do
+ let(:key) { expired_key }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'for a key expiring in the next 7 days' do
+ let(:key) { expiring_soon_key }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'for a key expiring after the next 7 days' do
+ let(:key) { expires_in_future_key }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'for a key that never expires' do
+ let(:key) { never_expires_key }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '#console_message' do
+ subject { key_status_checker.console_message }
+
+ context 'for an expired key' do
+ let(:key) { expired_key }
+
+ it { is_expected.to eq('INFO: Your SSH key has expired. Please generate a new key.') }
+ end
+
+ context 'for a key expiring in the next 7 days' do
+ let(:key) { expiring_soon_key }
+
+ it { is_expected.to eq('INFO: Your SSH key is expiring soon. Please generate a new key.') }
+ end
+
+ context 'for a key expiring after the next 7 days' do
+ let(:key) { expires_in_future_key }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'for a key that never expires' do
+ let(:key) { never_expires_key }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb
index f9eb4a30190..2f691429541 100644
--- a/spec/lib/gitlab/auth/ldap/access_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::LDAP::Access do
+describe Gitlab::Auth::Ldap::Access do
include LdapHelpers
let(:user) { create(:omniauth_user) }
@@ -64,7 +64,7 @@ describe Gitlab::Auth::LDAP::Access do
context 'and the user is disabled via active directory' do
before do
- allow(Gitlab::Auth::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(true)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:disabled_via_active_directory?).and_return(true)
end
it 'returns false' do
@@ -90,7 +90,7 @@ describe Gitlab::Auth::LDAP::Access do
context 'and has no disabled flag in active directory' do
before do
- allow(Gitlab::Auth::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:disabled_via_active_directory?).and_return(false)
end
it { is_expected.to be_truthy }
@@ -135,8 +135,8 @@ describe Gitlab::Auth::LDAP::Access do
context 'without ActiveDirectory enabled' do
before do
- allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
- allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance|
+ allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
+ allow_next_instance_of(Gitlab::Auth::Ldap::Config) do |instance|
allow(instance).to receive(:active_directory).and_return(false)
end
end
diff --git a/spec/lib/gitlab/auth/ldap/adapter_spec.rb b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
index 54486913b72..34853acdd0f 100644
--- a/spec/lib/gitlab/auth/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::LDAP::Adapter do
+describe Gitlab::Auth::Ldap::Adapter do
include LdapHelpers
let(:ldap) { double(:ldap) }
@@ -138,7 +138,7 @@ describe Gitlab::Auth::LDAP::Adapter do
it 'as many times as MAX_SEARCH_RETRIES' do
expect(ldap).to receive(:search).exactly(3).times
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::LDAPConnectionError)
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::LdapConnectionError)
end
context 'when no more retries' do
@@ -147,11 +147,11 @@ describe Gitlab::Auth::LDAP::Adapter do
end
it 'raises the exception' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::LDAPConnectionError)
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::LdapConnectionError)
end
it 'logs the error' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::LDAPConnectionError)
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::LdapConnectionError)
expect(Rails.logger).to have_received(:warn).with(
"LDAP search raised exception Net::LDAP::Error: some error")
end
@@ -161,6 +161,6 @@ describe Gitlab::Auth::LDAP::Adapter do
end
def ldap_attributes
- Gitlab::Auth::LDAP::Person.ldap_attributes(Gitlab::Auth::LDAP::Config.new('ldapmain'))
+ Gitlab::Auth::Ldap::Person.ldap_attributes(Gitlab::Auth::Ldap::Config.new('ldapmain'))
end
end
diff --git a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
index f1050b9f830..7bc92d0abea 100644
--- a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::LDAP::AuthHash do
+describe Gitlab::Auth::Ldap::AuthHash do
include LdapHelpers
let(:auth_hash) do
@@ -58,7 +58,7 @@ describe Gitlab::Auth::LDAP::AuthHash do
end
before do
- allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance|
+ allow_next_instance_of(Gitlab::Auth::Ldap::Config) do |instance|
allow(instance).to receive(:attributes).and_return(attributes)
end
end
diff --git a/spec/lib/gitlab/auth/ldap/authentication_spec.rb b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
index ebaf8383ce5..1f8b1474539 100644
--- a/spec/lib/gitlab/auth/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
@@ -2,15 +2,15 @@
require 'spec_helper'
-describe Gitlab::Auth::LDAP::Authentication do
+describe Gitlab::Auth::Ldap::Authentication do
let(:dn) { 'uid=John Smith, ou=People, dc=example, dc=com' }
- let(:user) { create(:omniauth_user, extern_uid: Gitlab::Auth::LDAP::Person.normalize_dn(dn)) }
+ let(:user) { create(:omniauth_user, extern_uid: Gitlab::Auth::Ldap::Person.normalize_dn(dn)) }
let(:login) { 'john' }
let(:password) { 'password' }
describe 'login' do
before do
- allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
+ allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
end
it "finds the user if authentication is successful" do
@@ -48,7 +48,7 @@ describe Gitlab::Auth::LDAP::Authentication do
end
it "fails if ldap is disabled" do
- allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(false)
+ allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(false)
expect(described_class.login(login, password)).to be_falsey
end
diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb
index e4a90d4018d..0967c45d36b 100644
--- a/spec/lib/gitlab/auth/ldap/config_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::LDAP::Config do
+describe Gitlab::Auth::Ldap::Config do
include LdapHelpers
let(:config) { described_class.new('ldapmain') }
diff --git a/spec/lib/gitlab/auth/ldap/dn_spec.rb b/spec/lib/gitlab/auth/ldap/dn_spec.rb
index 63656efba29..7aaffa52ae4 100644
--- a/spec/lib/gitlab/auth/ldap/dn_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/dn_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::LDAP::DN do
+describe Gitlab::Auth::Ldap::DN do
using RSpec::Parameterized::TableSyntax
describe '#normalize_value' do
@@ -15,7 +15,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'John Smith,' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'DN string ended unexpectedly')
end
end
@@ -23,7 +23,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { '#aa aa' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"")
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"")
end
end
@@ -31,7 +31,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { '#aaXaaa' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"")
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"")
end
end
@@ -39,7 +39,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { '#aaaYaa' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"")
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"")
end
end
@@ -47,7 +47,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { '"Sebasti\\cX\\a1n"' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"")
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"")
end
end
@@ -55,7 +55,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { '"James' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'DN string ended unexpectedly')
end
end
@@ -63,7 +63,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'J\ames' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'Invalid escaped hex code "\am"')
end
end
@@ -71,7 +71,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'foo\\' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'DN string ended unexpectedly')
end
end
end
@@ -88,7 +88,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'uid=john smith+telephonenumber=+1 555-555-5555,ou=people,dc=example,dc=com' }
it 'raises UnsupportedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::UnsupportedError)
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::UnsupportedError)
end
end
@@ -97,7 +97,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'uid = John Smith + telephoneNumber = + 1 555-555-5555 , ou = People,dc=example,dc=com' }
it 'raises UnsupportedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::UnsupportedError)
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::UnsupportedError)
end
end
@@ -105,7 +105,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'uid = John Smith + telephoneNumber = +1 555-555-5555 , ou = People,dc=example,dc=com' }
it 'raises UnsupportedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::UnsupportedError)
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::UnsupportedError)
end
end
end
@@ -117,7 +117,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'uid=John Smith,' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'DN string ended unexpectedly')
end
end
@@ -125,7 +125,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { '0.9.2342.19200300.100.1.25=#aa aa' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"")
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"")
end
end
@@ -133,7 +133,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { '0.9.2342.19200300.100.1.25=#aaXaaa' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"")
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"")
end
end
@@ -141,7 +141,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { '0.9.2342.19200300.100.1.25=#aaaYaa' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"")
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"")
end
end
@@ -149,7 +149,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'uid="Sebasti\\cX\\a1n"' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"")
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"")
end
end
@@ -157,7 +157,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'John' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'DN string ended unexpectedly')
end
end
@@ -165,7 +165,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'cn="James' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'DN string ended unexpectedly')
end
end
@@ -173,7 +173,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'cn=J\ames' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'Invalid escaped hex code "\am"')
end
end
@@ -181,7 +181,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'cn=\\' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'DN string ended unexpectedly')
end
end
@@ -189,7 +189,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { '1.2.d=Value' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized RDN OID attribute type name character "d"')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'Unrecognized RDN OID attribute type name character "d"')
end
end
@@ -197,7 +197,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'd1.2=Value' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "."')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'Unrecognized RDN attribute type name character "."')
end
end
@@ -205,7 +205,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { ' -uid=John Smith' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized first character of an RDN attribute type name "-"')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'Unrecognized first character of an RDN attribute type name "-"')
end
end
@@ -213,7 +213,7 @@ describe Gitlab::Auth::LDAP::DN do
let(:given) { 'uid\\=john' }
it 'raises MalformedError' do
- expect { subject }.to raise_error(Gitlab::Auth::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "\\"')
+ expect { subject }.to raise_error(Gitlab::Auth::Ldap::DN::MalformedError, 'Unrecognized RDN attribute type name character "\\"')
end
end
end
diff --git a/spec/lib/gitlab/auth/ldap/person_spec.rb b/spec/lib/gitlab/auth/ldap/person_spec.rb
index 985732e69f9..e90917cfce1 100644
--- a/spec/lib/gitlab/auth/ldap/person_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/person_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::LDAP::Person do
+describe Gitlab::Auth::Ldap::Person do
include LdapHelpers
let(:entry) { ldap_user_entry('john.doe') }
@@ -61,7 +61,7 @@ describe Gitlab::Auth::LDAP::Person do
}
}
)
- config = Gitlab::Auth::LDAP::Config.new('ldapmain')
+ config = Gitlab::Auth::Ldap::Config.new('ldapmain')
ldap_attributes = described_class.ldap_attributes(config)
expect(ldap_attributes).to match_array(%w(dn uid cn mail memberof))
diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb
index 071d687b2bf..867633e54df 100644
--- a/spec/lib/gitlab/auth/ldap/user_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Auth::LDAP::User do
+describe Gitlab::Auth::Ldap::User do
include LdapHelpers
let(:ldap_user) { described_class.new(auth_hash) }
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 022a544395e..62b83ff8b88 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -22,7 +22,7 @@ describe Gitlab::Auth::OAuth::User do
}
}
end
- let(:ldap_user) { Gitlab::Auth::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
+ let(:ldap_user) { Gitlab::Auth::Ldap::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
describe '#persisted?' do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
@@ -230,7 +230,7 @@ describe Gitlab::Auth::OAuth::User do
context "and no account for the LDAP user" do
before do
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
end
@@ -269,7 +269,7 @@ describe Gitlab::Auth::OAuth::User do
let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
it "adds the omniauth identity to the LDAP account" do
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
@@ -290,8 +290,8 @@ describe Gitlab::Auth::OAuth::User do
context 'when an LDAP person is not found by uid' do
it 'tries to find an LDAP person by email and adds the omniauth identity to the user' do
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil)
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(nil)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).and_return(ldap_user)
oauth_user.save
@@ -301,9 +301,9 @@ describe Gitlab::Auth::OAuth::User do
context 'when also not found by email' do
it 'tries to find an LDAP person by DN and adds the omniauth identity to the user' do
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil)
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).and_return(nil)
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(nil)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).and_return(nil)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_dn).and_return(ldap_user)
oauth_user.save
@@ -344,7 +344,7 @@ describe Gitlab::Auth::OAuth::User do
context 'and no account for the LDAP user' do
it 'creates a user favoring the LDAP username and strips email domain' do
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
@@ -356,7 +356,7 @@ describe Gitlab::Auth::OAuth::User do
context "and no corresponding LDAP person" do
before do
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(nil)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(nil)
end
include_examples "to verify compliance with allow_single_sign_on"
@@ -405,13 +405,13 @@ describe Gitlab::Auth::OAuth::User do
allow(ldap_user).to receive(:username) { uid }
allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] }
allow(ldap_user).to receive(:dn) { dn }
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
end
context "and no account for the LDAP user" do
context 'dont block on create (LDAP)' do
before do
- allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance|
+ allow_next_instance_of(Gitlab::Auth::Ldap::Config) do |instance|
allow(instance).to receive_messages(block_auto_created_users: false)
end
end
@@ -425,7 +425,7 @@ describe Gitlab::Auth::OAuth::User do
context 'block on create (LDAP)' do
before do
- allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance|
+ allow_next_instance_of(Gitlab::Auth::Ldap::Config) do |instance|
allow(instance).to receive_messages(block_auto_created_users: true)
end
end
@@ -443,7 +443,7 @@ describe Gitlab::Auth::OAuth::User do
context 'dont block on create (LDAP)' do
before do
- allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance|
+ allow_next_instance_of(Gitlab::Auth::Ldap::Config) do |instance|
allow(instance).to receive_messages(block_auto_created_users: false)
end
end
@@ -457,7 +457,7 @@ describe Gitlab::Auth::OAuth::User do
context 'block on create (LDAP)' do
before do
- allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance|
+ allow_next_instance_of(Gitlab::Auth::Ldap::Config) do |instance|
allow(instance).to receive_messages(block_auto_created_users: true)
end
end
@@ -503,7 +503,7 @@ describe Gitlab::Auth::OAuth::User do
context 'dont block on create (LDAP)' do
before do
- allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance|
+ allow_next_instance_of(Gitlab::Auth::Ldap::Config) do |instance|
allow(instance).to receive_messages(block_auto_created_users: false)
end
end
@@ -517,7 +517,7 @@ describe Gitlab::Auth::OAuth::User do
context 'block on create (LDAP)' do
before do
- allow_next_instance_of(Gitlab::Auth::LDAP::Config) do |instance|
+ allow_next_instance_of(Gitlab::Auth::Ldap::Config) do |instance|
allow(instance).to receive_messages(block_auto_created_users: true)
end
end
@@ -806,7 +806,7 @@ describe Gitlab::Auth::OAuth::User do
end
it 'returns nil' do
- adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain')
+ adapter = Gitlab::Auth::Ldap::Adapter.new('ldapmain')
hash = OmniAuth::AuthHash.new(uid: 'whatever', provider: 'ldapmain')
expect(oauth_user.send(:find_ldap_person, hash, adapter)).to be_nil
diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb
index 4c400636ddf..55d2f22b923 100644
--- a/spec/lib/gitlab/auth/saml/user_spec.rb
+++ b/spec/lib/gitlab/auth/saml/user_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::Auth::Saml::User do
email: 'john@mail.com'
}
end
- let(:ldap_user) { Gitlab::Auth::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
+ let(:ldap_user) { Gitlab::Auth::Ldap::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
describe '#save' do
before do
@@ -161,10 +161,10 @@ describe Gitlab::Auth::Saml::User do
allow(ldap_user).to receive(:username) { uid }
allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) }
allow(ldap_user).to receive(:dn) { dn }
- allow(Gitlab::Auth::LDAP::Adapter).to receive(:new).and_return(adapter)
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).with(uid, adapter).and_return(ldap_user)
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_dn).with(dn, adapter).and_return(ldap_user)
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_email).with('john@mail.com', adapter).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Adapter).to receive(:new).and_return(adapter)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).with(uid, adapter).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_dn).with(dn, adapter).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).with('john@mail.com', adapter).and_return(ldap_user)
end
context 'and no account for the LDAP user' do
@@ -212,10 +212,10 @@ describe Gitlab::Auth::Saml::User do
nil_types = uid_types - [uid_type]
nil_types.each do |type|
- allow(Gitlab::Auth::LDAP::Person).to receive(:"find_by_#{type}").and_return(nil)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:"find_by_#{type}").and_return(nil)
end
- allow(Gitlab::Auth::LDAP::Person).to receive(:"find_by_#{uid_type}").and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:"find_by_#{uid_type}").and_return(ldap_user)
end
it 'adds the omniauth identity to the LDAP account' do
@@ -282,7 +282,7 @@ describe Gitlab::Auth::Saml::User do
it 'adds the LDAP identity to the existing SAML user' do
create(:omniauth_user, email: 'john@mail.com', extern_uid: dn, provider: 'saml', username: 'john')
- allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).with(dn, adapter).and_return(ldap_user)
+ allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).with(dn, adapter).and_return(ldap_user)
local_hash = OmniAuth::AuthHash.new(uid: dn, provider: provider, info: info_hash)
local_saml_user = described_class.new(local_hash)
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index ed763f63756..e0c1f830165 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -3,10 +3,9 @@
require 'spec_helper'
describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
+ let_it_be(:project) { create(:project) }
let(:gl_auth) { described_class }
- set(:project) { create(:project) }
-
describe 'constants' do
it 'API_SCOPES contains all scopes for API access' do
expect(subject::API_SCOPES).to eq %i[api read_user]
@@ -591,23 +590,23 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context "with ldap enabled" do
before do
- allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
+ allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
end
it "tries to autheticate with db before ldap" do
- expect(Gitlab::Auth::LDAP::Authentication).not_to receive(:login)
+ expect(Gitlab::Auth::Ldap::Authentication).not_to receive(:login)
expect(gl_auth.find_with_user_password(username, password)).to eq(user)
end
it "does not find user by using ldap as fallback to for authentication" do
- expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil)
+ expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(nil)
expect(gl_auth.find_with_user_password('ldap_user', 'password')).to be_nil
end
it "find new user by using ldap as fallback to for authentication" do
- expect(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(user)
+ expect(Gitlab::Auth::Ldap::Authentication).to receive(:login).and_return(user)
expect(gl_auth.find_with_user_password('ldap_user', 'password')).to eq(user)
end
@@ -624,7 +623,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context "with ldap enabled" do
before do
- allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
+ allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true)
end
it "does not find non-ldap user by valid login/password" do
diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb
index adf36cf1050..d89eb9ef114 100644
--- a/spec/lib/gitlab/authorized_keys_spec.rb
+++ b/spec/lib/gitlab/authorized_keys_spec.rb
@@ -162,10 +162,10 @@ describe Gitlab::AuthorizedKeys do
end
end
- describe '#rm_key' do
+ describe '#remove_key' do
let(:key) { 'key-741' }
- subject { authorized_keys.rm_key(key) }
+ subject { authorized_keys.remove_key(key) }
context 'authorized_keys file exists' do
let(:other_line) { "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E" }
diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
index e299e2a366f..b77c67b120f 100644
--- a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
+++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, :migration, schema: 20180105212544 do
+describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, schema: 20180105212544 do
let(:projects_table) { table(:projects) }
let(:merge_requests_table) { table(:merge_requests) }
let(:merge_request_diffs_table) { table(:merge_request_diffs) }
diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
index 2a7cffb2f3e..5c8dcb38511 100644
--- a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
+++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 20180529152628 do
+describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, schema: 20180529152628 do
include TraceHelpers
let(:namespaces) { table(:namespaces) }
diff --git a/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
index e802613490b..f64c3ccc058 100644
--- a/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillHashedProjectRepositories, :migration, schema: 20181130102132 do
+describe Gitlab::BackgroundMigration::BackfillHashedProjectRepositories, schema: 20181130102132 do
it_behaves_like 'backfill migration for project repositories', :hashed
end
diff --git a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
index 947c99b860f..806d044ab40 100644
--- a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
@@ -2,6 +2,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories, :migration, schema: 20181212171634 do
+describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories, schema: 20181212171634 do
it_behaves_like 'backfill migration for project repositories', :legacy
end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
index 4714712f733..91ede05f395 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, :migration, schema: 20181010133639 do
+describe Gitlab::BackgroundMigration::BackfillProjectFullpathInRepoConfig, schema: 20181010133639 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:group) { namespaces.create!(name: 'foo', path: 'foo') }
diff --git a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
index 718109bb720..a2b4e003d82 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::BackfillProjectSettings, :migration, schema: 20200114113341 do
+describe Gitlab::BackgroundMigration::BackfillProjectSettings, schema: 20200114113341 do
let(:projects) { table(:projects) }
let(:project_settings) { table(:project_settings) }
let(:namespace) { table(:namespaces).create(name: 'user', path: 'user') }
diff --git a/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
new file mode 100644
index 00000000000..08d3b7bec6a
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_snippet_repositories_spec.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::BackfillSnippetRepositories, :migration, schema: 2020_02_26_162723 do
+ let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:users) { table(:users) }
+ let(:snippets) { table(:snippets) }
+ let(:snippet_repositories) { table(:snippet_repositories) }
+
+ let(:user) { users.create(id: 1, email: 'user@example.com', projects_limit: 10, username: 'test', name: 'Test') }
+ let!(:snippet_with_repo) { snippets.create(id: 1, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
+ let!(:snippet_with_empty_repo) { snippets.create(id: 2, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
+ let!(:snippet_without_repo) { snippets.create(id: 3, type: 'PersonalSnippet', author_id: user.id, file_name: file_name, content: content) }
+
+ let(:file_name) { 'file_name.rb' }
+ let(:content) { 'content' }
+ let(:ids) { snippets.pluck('MIN(id)', 'MAX(id)').first }
+ let(:service) { described_class.new }
+
+ subject { service.perform(*ids) }
+
+ before do
+ allow(snippet_with_repo).to receive(:disk_path).and_return(disk_path(snippet_with_repo))
+
+ TestEnv.copy_repo(snippet_with_repo,
+ bare_repo: TestEnv.factory_repo_path_bare,
+ refs: TestEnv::BRANCH_SHA)
+
+ raw_repository(snippet_with_empty_repo).create_repository
+ end
+
+ after do
+ raw_repository(snippet_with_repo).remove
+ raw_repository(snippet_without_repo).remove
+ raw_repository(snippet_with_empty_repo).remove
+ end
+
+ describe '#perform' do
+ it 'logs successful migrated snippets' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance|
+ expect(instance).to receive(:info).exactly(3).times
+ end
+
+ subject
+ end
+
+ context 'when snippet has a non empty repository' do
+ it 'does not perform any action' do
+ expect(service).not_to receive(:create_repository_and_files).with(snippet_with_repo)
+
+ subject
+ end
+ end
+
+ shared_examples 'commits the file to the repository' do
+ it do
+ subject
+
+ blob = blob_at(snippet, file_name)
+
+ aggregate_failures do
+ expect(blob).to be
+ expect(blob.data).to eq content
+ end
+ end
+ end
+
+ context 'when snippet has an empty repo' do
+ before do
+ expect(repository_exists?(snippet_with_empty_repo)).to be_truthy
+ end
+
+ it_behaves_like 'commits the file to the repository' do
+ let(:snippet) { snippet_with_empty_repo }
+ end
+ end
+
+ context 'when snippet does not have a repository' do
+ it 'creates the repository' do
+ expect { subject }.to change { repository_exists?(snippet_without_repo) }.from(false).to(true)
+ end
+
+ it_behaves_like 'commits the file to the repository' do
+ let(:snippet) { snippet_without_repo }
+ end
+ end
+
+ context 'when an error is raised' do
+ before do
+ allow(service).to receive(:create_commit).and_raise(StandardError)
+ end
+
+ it 'logs errors' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance|
+ expect(instance).to receive(:error).exactly(3).times
+ end
+
+ subject
+ end
+
+ it "retries #{described_class::MAX_RETRIES} times the operation if it fails" do
+ expect(service).to receive(:create_commit).exactly(snippets.count * described_class::MAX_RETRIES).times
+
+ subject
+ end
+
+ it 'destroys the snippet repository' do
+ expect(service).to receive(:destroy_snippet_repository).exactly(3).times.and_call_original
+
+ subject
+
+ expect(snippet_repositories.count).to eq 0
+ end
+
+ it 'deletes the repository on disk' do
+ subject
+
+ aggregate_failures do
+ expect(repository_exists?(snippet_with_repo)).to be_falsey
+ expect(repository_exists?(snippet_without_repo)).to be_falsey
+ expect(repository_exists?(snippet_with_empty_repo)).to be_falsey
+ end
+ end
+ end
+ end
+
+ def blob_at(snippet, path)
+ raw_repository(snippet).blob_at('master', path)
+ end
+
+ def repository_exists?(snippet)
+ gitlab_shell.repository_exists?('default', "#{disk_path(snippet)}.git")
+ end
+
+ def raw_repository(snippet)
+ Gitlab::Git::Repository.new('default',
+ "#{disk_path(snippet)}.git",
+ Gitlab::GlRepository::SNIPPET.identifier_for_container(snippet),
+ "@snippets/#{snippet.id}")
+ end
+
+ def hashed_repository(snippet)
+ Storage::Hashed.new(snippet, prefix: '@snippets')
+ end
+
+ def disk_path(snippet)
+ hashed_repository(snippet).disk_path
+ end
+
+ def ls_files(snippet)
+ raw_repository(snippet).ls_files(nil)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/digest_column_spec.rb b/spec/lib/gitlab/background_migration/digest_column_spec.rb
index a25dcb06005..0c76ebe9c66 100644
--- a/spec/lib/gitlab/background_migration/digest_column_spec.rb
+++ b/spec/lib/gitlab/background_migration/digest_column_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::DigestColumn, :migration, schema: 20180913142237 do
+describe Gitlab::BackgroundMigration::DigestColumn, schema: 20180913142237 do
let(:personal_access_tokens) { table(:personal_access_tokens) }
let(:users) { table(:users) }
diff --git a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
index 3c2ed6d3a6d..6d3ccde7df2 100644
--- a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
+++ b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::EncryptColumns, :migration, schema: 20180910115836 do
+describe Gitlab::BackgroundMigration::EncryptColumns, schema: 20180910115836 do
let(:model) { Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook }
let(:web_hooks) { table(:web_hooks) }
diff --git a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
index 54af9807e7b..89262788d9b 100644
--- a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
+++ b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::EncryptRunnersTokens, :migration, schema: 20181121111200 do
+describe Gitlab::BackgroundMigration::EncryptRunnersTokens, schema: 20181121111200 do
let(:settings) { table(:application_settings) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
diff --git a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
index f3127cbf5df..cc4ce023f04 100644
--- a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, :migration, schema: 20180702120647 do
+describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, schema: 20180702120647 do
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
let(:issues_table) { table(:issues) }
diff --git a/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb b/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
index 0dca542cb9f..056ddd7adf9 100644
--- a/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_projects_without_project_feature_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, :migration, schema: 2020_01_27_111840 do
+describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, schema: 2020_01_27_111840 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:project_features) { table(:project_features) }
diff --git a/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb b/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb
index 73c855ac184..141a0af6c29 100644
--- a/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixPromotedEpicsDiscussionIds, :migration, schema: 20190715193142 do
+describe Gitlab::BackgroundMigration::FixPromotedEpicsDiscussionIds, schema: 20190715193142 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
let(:epics) { table(:epics) }
diff --git a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
index 5938ecca459..52760cdd115 100644
--- a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixUserNamespaceNames, :migration, schema: 20190620112608 do
+describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20190620112608 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
let(:user) { users.create(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
diff --git a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
index d1d6d8411d1..0fb7eea2bd7 100644
--- a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, :migration, schema: 20190620112608 do
+describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20190620112608 do
let(:namespaces) { table(:namespaces) }
let(:users) { table(:users) }
let(:routes) { table(:routes) }
diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
index f2de73d5aea..850ef48d44a 100644
--- a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
+++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
@@ -149,10 +149,12 @@ describe Gitlab::BackgroundMigration::LegacyUploadMover do
context 'when an upload belongs to a legacy_diff_note' do
let!(:merge_request) { create(:merge_request, source_project: project) }
+
let!(:note) do
create(:legacy_diff_note_on_merge_request,
note: 'some note', project: project, noteable: merge_request)
end
+
let(:legacy_upload) do
create(:upload, :with_file, :attachment_upload,
path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note)
@@ -193,6 +195,17 @@ describe Gitlab::BackgroundMigration::LegacyUploadMover do
it_behaves_like 'move error'
end
+ context 'when upload has mount_point nil' do
+ let(:legacy_upload) do
+ create(:upload, :with_file, :attachment_upload,
+ path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note, mount_point: nil)
+ end
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
context 'when the file can be handled correctly' do
it_behaves_like 'migrates the file correctly'
it_behaves_like 'legacy local file'
diff --git a/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb b/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
new file mode 100644
index 00000000000..5700cac2e0f
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::LinkLfsObjectsProjects, :migration, schema: 2020_03_10_075115 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:fork_networks) { table(:fork_networks) }
+ let(:fork_network_members) { table(:fork_network_members) }
+ let(:lfs_objects) { table(:lfs_objects) }
+ let(:lfs_objects_projects) { table(:lfs_objects_projects) }
+
+ let(:namespace) { namespaces.create(name: 'GitLab', path: 'gitlab') }
+
+ let(:fork_network) { fork_networks.create(root_project_id: source_project.id) }
+ let(:another_fork_network) { fork_networks.create(root_project_id: another_source_project.id) }
+
+ let(:source_project) { projects.create(namespace_id: namespace.id) }
+ let(:another_source_project) { projects.create(namespace_id: namespace.id) }
+ let(:project) { projects.create(namespace_id: namespace.id) }
+ let(:another_project) { projects.create(namespace_id: namespace.id) }
+ let(:partially_linked_project) { projects.create(namespace_id: namespace.id) }
+ let(:fully_linked_project) { projects.create(namespace_id: namespace.id) }
+
+ let(:lfs_object) { lfs_objects.create(oid: 'abc123', size: 100) }
+ let(:another_lfs_object) { lfs_objects.create(oid: 'def456', size: 200) }
+
+ let!(:source_project_lop_1) do
+ lfs_objects_projects.create(
+ lfs_object_id: lfs_object.id,
+ project_id: source_project.id
+ )
+ end
+
+ let!(:source_project_lop_2) do
+ lfs_objects_projects.create(
+ lfs_object_id: another_lfs_object.id,
+ project_id: source_project.id
+ )
+ end
+
+ let!(:another_source_project_lop_1) do
+ lfs_objects_projects.create(
+ lfs_object_id: lfs_object.id,
+ project_id: another_source_project.id
+ )
+ end
+
+ let!(:another_source_project_lop_2) do
+ lfs_objects_projects.create(
+ lfs_object_id: another_lfs_object.id,
+ project_id: another_source_project.id
+ )
+ end
+
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+
+ # Create links between projects
+ fork_network_members.create(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil)
+
+ [project, partially_linked_project, fully_linked_project].each do |p|
+ fork_network_members.create(
+ fork_network_id: fork_network.id,
+ project_id: p.id,
+ forked_from_project_id: fork_network.root_project_id
+ )
+ end
+
+ fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil)
+ fork_network_members.create(fork_network_id: another_fork_network.id, project_id: another_project.id, forked_from_project_id: another_fork_network.root_project_id)
+
+ # Links LFS objects to some projects
+ lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: fully_linked_project.id)
+ lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: fully_linked_project.id)
+ lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: partially_linked_project.id)
+ end
+
+ context 'when there are LFS objects to be linked' do
+ it 'creates LfsObjectsProject records for forks based on the specified range of LfsObjectProject id' do
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |logger|
+ expect(logger).to receive(:info).exactly(4).times
+ end
+
+ expect { subject.perform(source_project_lop_1.id, another_source_project_lop_2.id) }.to change { lfs_objects_projects.count }.by(5)
+
+ expect(lfs_object_ids_for(project)).to match_array(lfs_object_ids_for(source_project))
+ expect(lfs_object_ids_for(another_project)).to match_array(lfs_object_ids_for(another_source_project))
+ expect(lfs_object_ids_for(partially_linked_project)).to match_array(lfs_object_ids_for(source_project))
+
+ expect { subject.perform(source_project_lop_1.id, another_source_project_lop_2.id) }.not_to change { lfs_objects_projects.count }
+ end
+ end
+
+ context 'when there are no LFS objects to be linked' do
+ before do
+ # Links LFS objects to all projects
+ projects.all.each do |p|
+ lfs_objects_projects.create(lfs_object_id: lfs_object.id, project_id: p.id)
+ lfs_objects_projects.create(lfs_object_id: another_lfs_object.id, project_id: p.id)
+ end
+ end
+
+ it 'does not create LfsObjectProject records' do
+ expect { subject.perform(source_project_lop_1.id, another_source_project_lop_2.id) }
+ .not_to change { lfs_objects_projects.count }
+ end
+ end
+
+ def lfs_object_ids_for(project)
+ lfs_objects_projects.where(project_id: project.id).pluck(:lfs_object_id)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
index a496f8416bf..adf358f5320 100644
--- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 20180212101928 do
+describe Gitlab::BackgroundMigration::MigrateBuildStage, schema: 20180212101928 do
let(:projects) { table(:projects) }
let(:pipelines) { table(:ci_pipelines) }
let(:stages) { table(:ci_stages) }
diff --git a/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb b/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb
index 3ccb2379936..79a8cd926a7 100644
--- a/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateFingerprintSha256WithinKeys, :migration, schema: 20200106071113 do
+describe Gitlab::BackgroundMigration::MigrateFingerprintSha256WithinKeys, schema: 20200106071113 do
subject(:fingerprint_migrator) { described_class.new }
let(:key_table) { table(:keys) }
diff --git a/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb b/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
index 4c70877befe..7dae28f72a5 100644
--- a/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, :migration, schema: 20200130145430 do
+describe Gitlab::BackgroundMigration::MigrateIssueTrackersSensitiveData, schema: 20200130145430 do
let(:services) { table(:services) }
# we need to define the classes due to encryption
diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
index 268626d58fd..5f2a27acd9b 100644
--- a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts, :migration, schema: 20180816161409 do
+describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts, schema: 20180816161409 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:pipelines) { table(:ci_pipelines) }
diff --git a/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
index c45c64f6a23..ff88d2a5d00 100644
--- a/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateNullPrivateProfileToFalse, :migration, schema: 20190620105427 do
+describe Gitlab::BackgroundMigration::MigrateNullPrivateProfileToFalse, schema: 20190620105427 do
let(:users) { table(:users) }
it 'correctly migrates nil private_profile to false' do
diff --git a/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb b/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
index d94a312f605..10a1d4ee1b9 100644
--- a/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigratePagesMetadata, :migration, schema: 20190919040324 do
+describe Gitlab::BackgroundMigration::MigratePagesMetadata, schema: 20190919040324 do
let(:projects) { table(:projects) }
subject(:migrate_pages_metadata) { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
index 1a8b0355fd9..437be125cf0 100644
--- a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateStageIndex, :migration, schema: 20180420080616 do
+describe Gitlab::BackgroundMigration::MigrateStageIndex, schema: 20180420080616 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:pipelines) { table(:ci_pipelines) }
diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
index 128e118ac17..d445858b8e8 100644
--- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :migration, schema: 20181022173835 do
+describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, schema: 20181022173835 do
include MigrationHelpers::ClusterHelpers
let(:migration) { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
index c1f6871a568..e65b3549de0 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migration, schema: 20190315191339 do
+describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, schema: 20190315191339 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:users) { table(:users) }
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
index ea1eaa6417d..0250ebd7759 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
-describe Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile, :migration, schema: 20180208183958 do
+describe Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile, schema: 20180208183958 do
include MigrationsHelpers::TrackUntrackedUploadsHelpers
let!(:appearances) { table(:appearances) }
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
index f25804c3ca2..44cec112bfd 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
-describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :migration, schema: 20180208183958 do
+describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, schema: 20180208183958 do
include MigrationsHelpers::TrackUntrackedUploadsHelpers
subject { described_class.new }
diff --git a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
index 9072431e8f6..2957d0bed15 100644
--- a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
-describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, :migration, schema: 20180208183958 do
+describe Gitlab::BackgroundMigration::PrepareUntrackedUploads, schema: 20180208183958 do
include MigrationsHelpers::TrackUntrackedUploadsHelpers
let!(:untracked_files_for_uploads) { table(:untracked_files_for_uploads) }
diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
index 1ef2c451aa2..4699cc42b38 100644
--- a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
+++ b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, :migration, schema: 20200204113223 do
+describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizations, schema: 20200204113223 do
let(:users_table) { table(:users) }
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb
index 14ba57eecbf..edb46efad7c 100644
--- a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb
+++ b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizationsWithMinMaxUserId, :migration, schema: 20200204113224 do
+describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizationsWithMinMaxUserId, schema: 20200204113224 do
let(:users_table) { table(:users) }
let(:min) { 1 }
let(:max) { 5 }
diff --git a/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb b/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
index dae754112dc..3de24f577ab 100644
--- a/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
+++ b/spec/lib/gitlab/background_migration/remove_restricted_todos_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, :migration, schema: 20180704204006 do
+describe Gitlab::BackgroundMigration::RemoveRestrictedTodos, schema: 20180704204006 do
let(:projects) { table(:projects) }
let(:users) { table(:users) }
let(:todos) { table(:todos) }
diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
index fd35ddc49a2..40340f89448 100644
--- a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
+++ b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::ResetMergeStatus, :migration, schema: 20190528180441 do
+describe Gitlab::BackgroundMigration::ResetMergeStatus, schema: 20190528180441 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
diff --git a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
index cee9a3e8822..387e3343ede 100644
--- a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
+++ b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb')
-describe ScheduleCalculateWikiSizes, :migration do
+describe ScheduleCalculateWikiSizes do
let(:migration_class) { Gitlab::BackgroundMigration::CalculateWikiSizes }
let(:migration_name) { migration_class.to_s.demodulize }
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
index 3600755ada7..5ce4a322e51 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices, :migration, schema: 20180122154930 do
+describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices, schema: 20180122154930 do
let(:services) { table(:services) }
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
index 5cd9c02fd3f..08f1f543f5d 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks, :migration, schema: 20180104131052 do
+describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks, schema: 20180104131052 do
let(:web_hooks) { table(:web_hooks) }
describe '#perform' do
diff --git a/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb b/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
index 37280110b91..70397ae1e30 100644
--- a/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
+++ b/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::UpdateExistingSubgroupToMatchVisibilityLevelOfParent, :migration, schema: 2020_01_10_121314 do
+describe Gitlab::BackgroundMigration::UpdateExistingSubgroupToMatchVisibilityLevelOfParent, schema: 2020_01_10_121314 do
include MigrationHelpers::NamespacesHelpers
context 'private visibility level' do
diff --git a/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
new file mode 100644
index 00000000000..a273dcf9e5c
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/user_mentions/create_resource_user_mention_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require './db/post_migrate/20200128134110_migrate_commit_notes_mentions_to_db'
+require './db/post_migrate/20200211155539_migrate_merge_request_mentions_to_db'
+
+describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMention, schema: 20200211155539 do
+ include MigrationsHelpers
+
+ context 'when migrating data' do
+ let(:users) { table(:users) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:notes) { table(:notes) }
+
+ let(:author) { users.create!(email: 'author@example.com', notification_email: 'author@example.com', name: 'author', username: 'author', projects_limit: 10, state: 'active') }
+ let(:member) { users.create!(email: 'member@example.com', notification_email: 'member@example.com', name: 'member', username: 'member', projects_limit: 10, state: 'active') }
+ let(:admin) { users.create!(email: 'administrator@example.com', notification_email: 'administrator@example.com', name: 'administrator', username: 'administrator', admin: 1, projects_limit: 10, state: 'active') }
+ let(:john_doe) { users.create!(email: 'john_doe@example.com', notification_email: 'john_doe@example.com', name: 'john_doe', username: 'john_doe', projects_limit: 10, state: 'active') }
+ let(:skipped) { users.create!(email: 'skipped@example.com', notification_email: 'skipped@example.com', name: 'skipped', username: 'skipped', projects_limit: 10, state: 'active') }
+
+ let(:mentioned_users) { [author, member, admin, john_doe, skipped] }
+ let(:mentioned_users_refs) { mentioned_users.map { |u| "@#{u.username}" }.join(' ') }
+
+ let(:group) { namespaces.create!(name: 'test1', path: 'test1', runners_token: 'my-token1', project_creation_level: 1, visibility_level: 20, type: 'Group') }
+ let(:inaccessible_group) { namespaces.create!(name: 'test2', path: 'test2', runners_token: 'my-token2', project_creation_level: 1, visibility_level: 0, type: 'Group') }
+ let(:project) { projects.create!(name: 'gitlab1', path: 'gitlab1', namespace_id: group.id, visibility_level: 0) }
+
+ let(:mentioned_groups) { [group, inaccessible_group] }
+ let(:group_mentions) { [group, inaccessible_group].map { |gr| "@#{gr.path}" }.join(' ') }
+ let(:description_mentions) { "description with mentions #{mentioned_users_refs} and #{group_mentions}" }
+
+ before do
+ # build personal namespaces and routes for users
+ mentioned_users.each { |u| u.becomes(User).save! }
+
+ # build namespaces and routes for groups
+ mentioned_groups.each do |gr|
+ gr.name += '-org'
+ gr.path += '-org'
+ gr.becomes(Namespace).save!
+ end
+ end
+
+ context 'migrate merge request mentions' do
+ let(:merge_requests) { table(:merge_requests) }
+ let(:merge_request_user_mentions) { table(:merge_request_user_mentions) }
+
+ let!(:mr1) do
+ merge_requests.create!(
+ title: "title 1", state_id: 1, target_branch: 'feature1', source_branch: 'master',
+ source_project_id: project.id, target_project_id: project.id, author_id: author.id,
+ description: description_mentions
+ )
+ end
+
+ let!(:mr2) do
+ merge_requests.create!(
+ title: "title 2", state_id: 1, target_branch: 'feature2', source_branch: 'master',
+ source_project_id: project.id, target_project_id: project.id, author_id: author.id,
+ description: 'some description'
+ )
+ end
+
+ let!(:mr3) do
+ merge_requests.create!(
+ title: "title 3", state_id: 1, target_branch: 'feature3', source_branch: 'master',
+ source_project_id: project.id, target_project_id: project.id, author_id: author.id,
+ description: 'description with an email@example.com and some other @ char here.')
+ end
+
+ let(:user_mentions) { merge_request_user_mentions }
+ let(:resource) { merge_request }
+
+ it_behaves_like 'resource mentions migration', MigrateMergeRequestMentionsToDb, MergeRequest
+ end
+
+ context 'migrate commit mentions' do
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
+ let(:commit) { Commit.new(RepoHelpers.sample_commit, project.becomes(Project)) }
+ let(:commit_user_mentions) { table(:commit_user_mentions) }
+
+ let!(:note1) { notes.create!(commit_id: commit.id, noteable_type: 'Commit', project_id: project.id, author_id: author.id, note: description_mentions) }
+ let!(:note2) { notes.create!(commit_id: commit.id, noteable_type: 'Commit', project_id: project.id, author_id: author.id, note: 'sample note') }
+ let!(:note3) { notes.create!(commit_id: commit.id, noteable_type: 'Commit', project_id: project.id, author_id: author.id, note: description_mentions, system: true) }
+
+ # this not does not have actual mentions
+ let!(:note4) { notes.create!(commit_id: commit.id, noteable_type: 'Commit', project_id: project.id, author_id: author.id, note: 'note for an email@somesite.com and some other random @ ref' ) }
+ # this should have pointed to an innexisted commit record in a commits table
+ # but because commit is not an AR we'll just make it so that it does not have mentions
+ let!(:note5) { notes.create!(commit_id: 'abc', noteable_type: 'Commit', project_id: project.id, author_id: author.id, note: 'note for an email@somesite.com and some other random @ ref') }
+
+ let(:user_mentions) { commit_user_mentions }
+ let(:resource) { commit }
+
+ it_behaves_like 'resource notes mentions migration', MigrateCommitNotesMentionsToDb, Commit
+ end
+ end
+
+ context 'checks no_quote_columns' do
+ it 'has correct no_quote_columns' do
+ expect(Gitlab::BackgroundMigration::UserMentions::Models::MergeRequest.no_quote_columns).to match([:note_id, :merge_request_id])
+ end
+
+ it 'commit has correct no_quote_columns' do
+ expect(Gitlab::BackgroundMigration::UserMentions::Models::Commit.no_quote_columns).to match([:note_id])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index 0607e2232a1..d2ecb1869fc 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -54,16 +54,15 @@ describe ::Gitlab::BareRepositoryImport::Repository do
end
context 'hashed storage' do
- let(:gitlab_shell) { Gitlab::Shell.new }
- let(:repository_storage) { 'default' }
- let(:root_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
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") }
before do
- gitlab_shell.create_repository(repository_storage, hashed_path, 'group/project')
+ TestEnv.create_bare_repository(repo_path)
+
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repository = Rugged::Repository.new(repo_path)
repository.config['gitlab.fullpath'] = 'to/repo'
@@ -71,7 +70,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do
end
after do
- gitlab_shell.remove_repository(repository_storage, hashed_path)
+ FileUtils.rm_rf(repo_path)
end
subject { described_class.new(root_path, repo_path) }
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index b0d07c6e0b0..b95175efc0c 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -80,8 +80,7 @@ describe Gitlab::BitbucketImport::Importer do
end
let(:importer) { described_class.new(project) }
- let(:gitlab_shell) { double }
-
+ let(:sample) { RepoHelpers.sample_compare }
let(:issues_statuses_sample_data) do
{
count: sample_issues_statuses.count,
@@ -89,12 +88,6 @@ describe Gitlab::BitbucketImport::Importer do
}
end
- let(:sample) { RepoHelpers.sample_compare }
-
- before do
- allow(importer).to receive(:gitlab_shell) { gitlab_shell }
- end
-
subject { described_class.new(project) }
describe '#import_pull_requests' do
@@ -316,7 +309,7 @@ describe Gitlab::BitbucketImport::Importer do
describe 'wiki import' do
it 'is skipped when the wiki exists' do
expect(project.wiki).to receive(:repository_exists?) { true }
- expect(importer.gitlab_shell).not_to receive(:import_wiki_repository)
+ expect(project.wiki.repository).not_to receive(:import_repository)
importer.execute
@@ -325,7 +318,7 @@ describe Gitlab::BitbucketImport::Importer do
it 'imports to the project disk_path' do
expect(project.wiki).to receive(:repository_exists?) { false }
- expect(importer.gitlab_shell).to receive(:import_wiki_repository)
+ expect(project.wiki.repository).to receive(:import_repository)
importer.execute
diff --git a/spec/lib/gitlab/github_import/caching_spec.rb b/spec/lib/gitlab/cache/import/caching_spec.rb
index 18c3e382532..e4aec0f4dec 100644
--- a/spec/lib/gitlab/github_import/caching_spec.rb
+++ b/spec/lib/gitlab/cache/import/caching_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Caching, :clean_gitlab_redis_cache do
+describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache do
describe '.read' do
it 'reads a value from the cache' do
described_class.write('foo', 'bar')
diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb
index 7cc1722dfd4..fd7eaa1603f 100644
--- a/spec/lib/gitlab/checks/branch_check_spec.rb
+++ b/spec/lib/gitlab/checks/branch_check_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Checks::BranchCheck do
let(:ref) { 'refs/heads/master' }
it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'The default branch of a project cannot be deleted.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'The default branch of a project cannot be deleted.')
end
end
@@ -28,7 +28,7 @@ describe Gitlab::Checks::BranchCheck do
it 'raises an error if the user is not allowed to do forced pushes to protected branches' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to force push code to a protected branch on this project.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to force push code to a protected branch on this project.')
end
it 'raises an error if the user is not allowed to merge to protected branches' do
@@ -38,13 +38,13 @@ describe Gitlab::Checks::BranchCheck do
expect(user_access).to receive(:can_merge_to_branch?).and_return(false)
expect(user_access).to receive(:can_push_to_branch?).and_return(false)
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to merge code into protected branches on this project.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to merge code into protected branches on this project.')
end
it 'raises an error if the user is not allowed to push to protected branches' do
expect(user_access).to receive(:can_push_to_branch?).and_return(false)
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to push code to protected branches on this project.')
end
context 'when project repository is empty' do
@@ -58,7 +58,7 @@ describe Gitlab::Checks::BranchCheck do
end
it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /Ask a project Owner or Maintainer to create a default branch/)
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, /Ask a project Owner or Maintainer to create a default branch/)
end
end
@@ -109,7 +109,7 @@ describe Gitlab::Checks::BranchCheck do
end
it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to create protected branches on this project.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to create protected branches on this project.')
end
end
@@ -135,7 +135,7 @@ describe Gitlab::Checks::BranchCheck do
end
it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only use an existing protected branch ref as the basis of a new protected branch.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can only use an existing protected branch ref as the basis of a new protected branch.')
end
end
@@ -157,7 +157,7 @@ describe Gitlab::Checks::BranchCheck do
context 'via SSH' do
it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only create protected branches using the web interface and API.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can only create protected branches using the web interface and API.')
end
end
end
@@ -171,7 +171,7 @@ describe Gitlab::Checks::BranchCheck do
context 'if the user is not allowed to delete protected branches' do
it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to delete protected branches from this project. Only a project maintainer or owner can delete a protected branch.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to delete protected branches from this project. Only a project maintainer or owner can delete a protected branch.')
end
end
@@ -190,7 +190,7 @@ describe Gitlab::Checks::BranchCheck do
context 'over SSH or HTTP' do
it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only delete protected branches using the web interface.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can only delete protected branches using the web interface.')
end
end
end
diff --git a/spec/lib/gitlab/checks/diff_check_spec.rb b/spec/lib/gitlab/checks/diff_check_spec.rb
index b9134b8d6ab..467b4ed3a21 100644
--- a/spec/lib/gitlab/checks/diff_check_spec.rb
+++ b/spec/lib/gitlab/checks/diff_check_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::Checks::DiffCheck do
context 'when change is sent by a different user' do
it 'raises an error if the user is not allowed to update the file' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "The path 'README' is locked in Git LFS by #{lock.user.name}")
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "The path 'README' is locked in Git LFS by #{lock.user.name}")
end
end
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index 9432be083d3..334dd8635a3 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::Checks::ForcePush do
- set(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
describe '.force_push?' do
it 'returns false if the repo is empty' do
diff --git a/spec/lib/gitlab/checks/lfs_check_spec.rb b/spec/lib/gitlab/checks/lfs_check_spec.rb
index dad14e100a7..c86481d1abe 100644
--- a/spec/lib/gitlab/checks/lfs_check_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_check_spec.rb
@@ -50,7 +50,7 @@ describe Gitlab::Checks::LfsCheck do
end
it 'fails if any LFS blobs are missing' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /LFS objects are missing/)
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, /LFS objects are missing/)
end
it 'succeeds if LFS objects have already been uploaded' do
diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb
index 373fef2a240..bbc97155374 100644
--- a/spec/lib/gitlab/checks/project_created_spec.rb
+++ b/spec/lib/gitlab/checks/project_created_spec.rb
@@ -3,24 +3,29 @@
require 'spec_helper'
describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
+ let(:protocol) { 'http' }
+ let(:git_user) { user }
+ let(:repository) { project.repository }
+
+ subject { described_class.new(repository, git_user, 'http') }
describe '.fetch_message' do
context 'with a project created message queue' do
- let(:project_created) { described_class.new(project, user, 'http') }
-
before do
- project_created.add_message
+ subject.add_message
end
it 'returns project created message' do
- expect(described_class.fetch_message(user.id, project.id)).to eq(project_created.message)
+ expect(described_class.fetch_message(user.id, project.id)).to eq(subject.message)
end
it 'deletes the project created message from redis' do
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).not_to be_nil
+
described_class.fetch_message(user.id, project.id)
+
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).to be_nil
end
end
@@ -34,15 +39,15 @@ describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
describe '#add_message' do
it 'queues a project created message' do
- project_created = described_class.new(project, user, 'http')
-
- expect(project_created.add_message).to eq('OK')
+ expect(subject.add_message).to eq('OK')
end
- it 'handles anonymous push' do
- project_created = described_class.new(nil, user, 'http')
+ context 'when user is nil' do
+ let(:git_user) { nil }
- expect(project_created.add_message).to be_nil
+ it 'handles anonymous push' do
+ expect(subject.add_message).to be_nil
+ end
end
end
end
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index 3de397760b4..1d1d6211088 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -3,24 +3,30 @@
require 'spec_helper'
describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
+ let(:repository) { project.repository }
+ let(:protocol) { 'http' }
+ let(:git_user) { user }
+ let(:redirect_path) { 'foo/bar' }
+
+ subject { described_class.new(repository, git_user, protocol, redirect_path) }
describe '.fetch_message' do
context 'with a redirect message queue' do
- it 'returns the redirect message' do
- project_moved = described_class.new(project, user, 'http', 'foo/bar')
- project_moved.add_message
+ before do
+ subject.add_message
+ end
- expect(described_class.fetch_message(user.id, project.id)).to eq(project_moved.message)
+ it 'returns the redirect message' do
+ expect(described_class.fetch_message(user.id, project.id)).to eq(subject.message)
end
it 'deletes the redirect message from redis' do
- project_moved = described_class.new(project, user, 'http', 'foo/bar')
- project_moved.add_message
-
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).not_to be_nil
+
described_class.fetch_message(user.id, project.id)
+
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).to be_nil
end
end
@@ -34,29 +40,82 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
describe '#add_message' do
it 'queues a redirect message' do
- project_moved = described_class.new(project, user, 'http', 'foo/bar')
- expect(project_moved.add_message).to eq("OK")
+ expect(subject.add_message).to eq("OK")
end
- it 'handles anonymous clones' do
- project_moved = described_class.new(project, nil, 'http', 'foo/bar')
+ context 'when user is nil' do
+ let(:git_user) { nil }
- expect(project_moved.add_message).to eq(nil)
+ it 'handles anonymous clones' do
+ expect(subject.add_message).to be_nil
+ end
end
end
describe '#message' do
- it 'returns a redirect message' do
- project_moved = described_class.new(project, user, 'http', 'foo/bar')
- message = <<~MSG
- Project 'foo/bar' was moved to '#{project.full_path}'.
+ shared_examples 'errors per protocol' do
+ shared_examples 'returns redirect message' do
+ it do
+ message = <<~MSG
+ Project '#{redirect_path}' was moved to '#{project.full_path}'.
+
+ Please update your Git remote:
+
+ git remote set-url origin #{url_to_repo}
+ MSG
+
+ expect(subject.message).to eq(message)
+ end
+ end
+
+ context 'when protocol is http' do
+ it_behaves_like 'returns redirect message' do
+ let(:url_to_repo) { http_url_to_repo }
+ end
+ end
+
+ context 'when protocol is ssh' do
+ let(:protocol) { 'ssh' }
- Please update your Git remote:
+ it_behaves_like 'returns redirect message' do
+ let(:url_to_repo) { ssh_url_to_repo }
+ end
+ end
+ end
+
+ context 'with project' do
+ it_behaves_like 'errors per protocol' do
+ let(:http_url_to_repo) { project.http_url_to_repo }
+ let(:ssh_url_to_repo) { project.ssh_url_to_repo }
+ end
+ end
+
+ context 'with wiki' do
+ let(:repository) { project.wiki.repository }
- git remote set-url origin #{project.http_url_to_repo}
- MSG
+ it_behaves_like 'errors per protocol' do
+ let(:http_url_to_repo) { project.wiki.http_url_to_repo }
+ let(:ssh_url_to_repo) { project.wiki.ssh_url_to_repo }
+ end
+ end
- expect(project_moved.message).to eq(message)
+ context 'with project snippet' do
+ let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
+ let(:repository) { snippet.repository }
+
+ it_behaves_like 'errors per protocol' do
+ let(:http_url_to_repo) { snippet.http_url_to_repo }
+ let(:ssh_url_to_repo) { snippet.ssh_url_to_repo }
+ end
+ end
+
+ context 'with personal snippet' do
+ let_it_be(:snippet) { create(:personal_snippet, :repository, author: user) }
+ let(:repository) { snippet.repository }
+
+ it 'returns nil' do
+ expect(subject.add_message).to be_nil
+ end
end
end
end
diff --git a/spec/lib/gitlab/checks/push_check_spec.rb b/spec/lib/gitlab/checks/push_check_spec.rb
index e1bd52d6c0b..857d71732fe 100644
--- a/spec/lib/gitlab/checks/push_check_spec.rb
+++ b/spec/lib/gitlab/checks/push_check_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Checks::PushCheck do
expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
expect(project).to receive(:branch_allows_collaboration?).with(user_access.user, 'master').and_return(false)
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to push code to this project.')
end
end
end
diff --git a/spec/lib/gitlab/checks/push_file_count_check_spec.rb b/spec/lib/gitlab/checks/push_file_count_check_spec.rb
new file mode 100644
index 00000000000..58ba7d579a3
--- /dev/null
+++ b/spec/lib/gitlab/checks/push_file_count_check_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Checks::PushFileCountCheck do
+ let(:snippet) { create(:personal_snippet, :repository) }
+ let(:changes) { { oldrev: oldrev, newrev: newrev, ref: ref } }
+ let(:timeout) { Gitlab::GitAccess::INTERNAL_TIMEOUT }
+ let(:logger) { Gitlab::Checks::TimedLogger.new(timeout: timeout) }
+
+ subject { described_class.new(changes, repository: snippet.repository, limit: 1, logger: logger) }
+
+ describe '#validate!' do
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ allow(snippet.repository).to receive(:new_commits).and_return(
+ snippet.repository.commits_between(oldrev, newrev)
+ )
+ end
+
+ context 'initial creation' do
+ let(:oldrev) { Gitlab::Git::EMPTY_TREE_ID }
+ let(:newrev) { TestEnv::BRANCH_SHA["snippet/single-file"] }
+ let(:ref) { "refs/heads/snippet/single-file" }
+
+ it 'allows creation' do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+
+ where(:old, :new, :valid, :message) do
+ 'single-file' | 'edit-file' | true | nil
+ 'single-file' | 'multiple-files' | false | 'The repository can contain at most 1 file(s).'
+ 'single-file' | 'no-files' | false | 'The repository must contain at least 1 file.'
+ 'edit-file' | 'rename-and-edit-file' | true | nil
+ end
+
+ with_them do
+ let(:oldrev) { TestEnv::BRANCH_SHA["snippet/#{old}"] }
+ let(:newrev) { TestEnv::BRANCH_SHA["snippet/#{new}"] }
+ let(:ref) { "refs/heads/snippet/#{new}" }
+
+ it "verifies" do
+ if valid
+ expect { subject.validate! }.not_to raise_error
+ else
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, message)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/checks/snippet_check_spec.rb b/spec/lib/gitlab/checks/snippet_check_spec.rb
new file mode 100644
index 00000000000..3eee5ccfc0a
--- /dev/null
+++ b/spec/lib/gitlab/checks/snippet_check_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Checks::SnippetCheck do
+ include_context 'change access checks context'
+
+ let(:snippet) { create(:personal_snippet, :repository) }
+ let(:user_access) { Gitlab::UserAccessSnippet.new(user, snippet: snippet) }
+
+ subject { Gitlab::Checks::SnippetCheck.new(changes, logger: logger) }
+
+ describe '#validate!' do
+ it 'does not raise any error' do
+ expect { subject.validate! }.not_to raise_error
+ end
+
+ context 'trying to delete the branch' do
+ let(:newrev) { '0000000000000000000000000000000000000000' }
+
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.')
+ end
+ end
+
+ context 'trying to create the branch' do
+ let(:oldrev) { '0000000000000000000000000000000000000000' }
+ let(:ref) { 'refs/heads/feature' }
+
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.')
+ end
+
+ context "when branch is 'master'" do
+ let(:ref) { 'refs/heads/master' }
+
+ it "allows the operation" do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/checks/tag_check_spec.rb b/spec/lib/gitlab/checks/tag_check_spec.rb
index 80e9eb504ad..0c94171646e 100644
--- a/spec/lib/gitlab/checks/tag_check_spec.rb
+++ b/spec/lib/gitlab/checks/tag_check_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::Checks::TagCheck do
it 'raises an error when user does not have access' do
allow(user_access).to receive(:can_do_action?).with(:admin_tag).and_return(false)
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to change existing tags on this project.')
end
context 'with protected tag' do
@@ -27,7 +27,7 @@ describe Gitlab::Checks::TagCheck do
let(:newrev) { '0000000000000000000000000000000000000000' }
it 'is prevented' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be deleted/)
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, /cannot be deleted/)
end
end
@@ -36,7 +36,7 @@ describe Gitlab::Checks::TagCheck do
let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
it 'is prevented' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be updated/)
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, /cannot be updated/)
end
end
end
@@ -47,7 +47,7 @@ describe Gitlab::Checks::TagCheck do
let(:ref) { 'refs/tags/v9.1.0' }
it 'prevents creation below access level' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /allowed to create this tag as it is protected/)
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, /allowed to create this tag as it is protected/)
end
context 'when user has access' do
diff --git a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
new file mode 100644
index 00000000000..04017b9ae3e
--- /dev/null
+++ b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::ArtifactFileReader do
+ let(:job) { create(:ci_build) }
+ let(:path) { 'generated.yml' } # included in the ci_build_artifacts.zip
+
+ describe '#read' do
+ subject { described_class.new(job).read(path) }
+
+ context 'when job has artifacts and metadata' do
+ let!(:artifacts) { create(:ci_job_artifact, :archive, job: job) }
+ let!(:metadata) { create(:ci_job_artifact, :metadata, job: job) }
+
+ it 'returns the content at the path' do
+ is_expected.to be_present
+ expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom')
+ end
+
+ context 'when path does not exist' do
+ let(:path) { 'file/does/not/exist.txt' }
+ let(:expected_error) do
+ "Path `#{path}` does not exist inside the `#{job.name}` artifacts archive!"
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::Error, expected_error)
+ end
+ end
+
+ context 'when path points to a directory' do
+ let(:path) { 'other_artifacts_0.1.2' }
+ let(:expected_error) do
+ "Path `#{path}` was expected to be a file but it was a directory!"
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::Error, expected_error)
+ end
+ end
+
+ context 'when path is nested' do
+ # path exists in ci_build_artifacts.zip
+ let(:path) { 'other_artifacts_0.1.2/doc_sample.txt' }
+
+ it 'returns the content at the nested path' do
+ is_expected.to be_present
+ end
+ end
+
+ context 'when artifact archive size is greater than the limit' do
+ let(:expected_error) do
+ "Artifacts archive for job `#{job.name}` is too large: max 1 KB"
+ end
+
+ before do
+ stub_const("#{described_class}::MAX_ARCHIVE_SIZE", 1.kilobyte)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::Error, expected_error)
+ end
+ end
+
+ context 'when metadata entry shows size greater than the limit' do
+ let(:expected_error) do
+ "Artifacts archive for job `#{job.name}` is too large: max 5 MB"
+ end
+
+ before do
+ expect_next_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) do |entry|
+ expect(entry).to receive(:total_size).and_return(10.megabytes)
+ end
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::Error, expected_error)
+ end
+ end
+ end
+
+ context 'when job does not have metadata artifacts' do
+ let!(:artifacts) { create(:ci_job_artifact, :archive, job: job) }
+ let(:expected_error) do
+ "Job `#{job.name}` has missing artifacts metadata and cannot be extracted!"
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::Error, expected_error)
+ end
+ end
+
+ context 'when job does not have artifacts' do
+ it 'raises ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError, 'Job does not have artifacts')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
index 48ac2e4e657..07f52605929 100644
--- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Changes do
- set(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
describe '#satisfied_by?' do
describe 'paths matching' do
@@ -89,7 +89,7 @@ describe Gitlab::Ci::Build::Policy::Changes do
end
describe 'gitaly integration' do
- set(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
let(:pipeline) do
create(:ci_empty_pipeline, project: project,
@@ -119,6 +119,7 @@ describe Gitlab::Ci::Build::Policy::Changes do
end
context 'when branch is created' do
+ let_it_be(:project) { create(:project, :repository) }
let(:pipeline) do
create(:ci_empty_pipeline, project: project,
ref: 'feature',
diff --git a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
index bc2e6fe6b8d..6d96bdc30c7 100644
--- a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
@@ -18,7 +18,7 @@ describe Gitlab::Ci::Build::Policy::Kubernetes do
end
context 'when kubernetes service is inactive' do
- set(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
it 'is not satisfied by a pipeline without kubernetes available' do
expect(described_class.new('active'))
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
index 66f2cb640b9..0e75726b81c 100644
--- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Variables do
- set(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
let(:pipeline) do
build(:ci_empty_pipeline, project: project, ref: 'master', source: :push)
diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
index ad388886681..d08ce30618d 100644
--- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb
@@ -106,6 +106,7 @@ describe Gitlab::Ci::Config::Entry::Bridge do
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] },
+ variables: {},
scheduling_type: :stage)
end
end
@@ -128,6 +129,7 @@ describe Gitlab::Ci::Config::Entry::Bridge do
ignore: false,
stage: 'test',
only: { refs: %w[branches tags] },
+ variables: {},
scheduling_type: :stage)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
new file mode 100644
index 00000000000..073f93ce542
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/inherit/default_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::Gitlab::Ci::Config::Entry::Inherit::Default do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(config) }
+
+ context 'validations' do
+ where(:config, :valid) do
+ true | true
+ false | true
+ %w[image] | true
+ %w[unknown] | false
+ %i[image] | false
+ [true] | false
+ "string" | false
+ end
+
+ with_them do
+ it do
+ expect(subject.valid?).to eq(valid)
+ end
+ end
+ end
+
+ describe '#inherit?' do
+ where(:config, :inherit) do
+ true | true
+ false | false
+ %w[image] | true
+ %w[before_script] | false
+ end
+
+ with_them do
+ it do
+ expect(subject.inherit?('image')).to eq(inherit)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
new file mode 100644
index 00000000000..06deed11c15
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/inherit/variables_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::Gitlab::Ci::Config::Entry::Inherit::Variables do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(config) }
+
+ context 'validations' do
+ where(:config, :valid) do
+ true | true
+ false | true
+ %w[A] | true
+ %w[A B] | true
+ %i[image] | true
+ [true] | false
+ "string" | false
+ end
+
+ with_them do
+ it do
+ expect(subject.valid?).to eq(valid)
+ end
+ end
+ end
+
+ describe '#inherit?' do
+ where(:config, :inherit) do
+ true | true
+ false | false
+ %w[A] | true
+ %w[B] | false
+ end
+
+ with_them do
+ it do
+ expect(subject.inherit?('A')).to eq(inherit)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 313b504ab59..b6279485426 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -6,6 +6,7 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:entry) { described_class.new(config, name: :rspec) }
it_behaves_like 'with inheritable CI config' do
+ let(:config) { { script: 'echo' } }
let(:inheritable_key) { 'default' }
let(:inheritable_class) { Gitlab::Ci::Config::Entry::Default }
@@ -15,6 +16,10 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:ignored_inheritable_columns) do
%i[]
end
+
+ before do
+ allow(entry).to receive_message_chain(:inherit_entry, :default_entry, :inherit?).and_return(true)
+ end
end
describe '.nodes' do
@@ -24,7 +29,8 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:result) do
%i[before_script script stage type after_script cache
image services only except rules needs variables artifacts
- environment coverage retry interruptible timeout release tags]
+ environment coverage retry interruptible timeout release tags
+ inherit]
end
it { is_expected.to match_array result }
@@ -500,7 +506,13 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:unspecified) { double('unspecified', 'specified?' => false) }
let(:default) { double('default', '[]' => unspecified) }
let(:workflow) { double('workflow', 'has_rules?' => false) }
- let(:deps) { double('deps', 'default' => default, '[]' => unspecified, 'workflow' => workflow) }
+
+ let(:deps) do
+ double('deps',
+ 'default_entry' => default,
+ 'workflow_entry' => workflow,
+ 'variables_value' => nil)
+ end
context 'when job config overrides default config' do
before do
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index c8c188d71bf..203342ab620 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -99,6 +99,7 @@ describe Gitlab::Ci::Config::Entry::Jobs do
only: { refs: %w[branches tags] },
stage: 'test',
trigger: { project: 'my/project' },
+ variables: {},
scheduling_type: :stage
},
regular_job: {
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
new file mode 100644
index 00000000000..8447a29c772
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -0,0 +1,381 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Processable do
+ let(:node_class) do
+ Class.new(::Gitlab::Config::Entry::Node) do
+ include Gitlab::Ci::Config::Entry::Processable
+
+ entry :tags, ::Gitlab::Config::Entry::ArrayOfStrings,
+ description: 'Set the default tags.',
+ inherit: true
+
+ def self.name
+ 'job'
+ end
+ end
+ end
+
+ let(:entry) { node_class.new(config, name: :rspec) }
+
+ describe 'validations' do
+ before do
+ entry.compose!
+ end
+
+ context 'when entry config value is correct' do
+ let(:config) { { stage: 'test' } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ context 'when job name is empty' do
+ let(:entry) { node_class.new(config, name: ''.to_sym) }
+
+ it 'reports error' do
+ expect(entry.errors).to include "job name can't be blank"
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ context 'incorrect config value type' do
+ let(:config) { ['incorrect'] }
+
+ describe '#errors' do
+ it 'reports error about a config type' do
+ expect(entry.errors)
+ .to include 'job config should be a hash'
+ end
+ end
+ end
+
+ context 'when config is empty' do
+ let(:config) { {} }
+
+ describe '#valid' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+
+ context 'when extends key is not a string' do
+ let(:config) { { extends: 123 } }
+
+ it 'returns error about wrong value type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include "job extends should be an array of strings or a string"
+ end
+ end
+
+ context 'when it uses both "when:" and "rules:"' do
+ let(:config) do
+ {
+ script: 'echo',
+ when: 'on_failure',
+ rules: [{ if: '$VARIABLE', when: 'on_success' }]
+ }
+ end
+
+ it 'returns an error about when: being combined with rules' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job config key may not be used with `rules`: when'
+ end
+ end
+
+ context 'when only: is used with rules:' do
+ let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'and only: is blank' do
+ let(:config) { { only: nil, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'and rules: is blank' do
+ let(:config) { { only: ['merge_requests'], rules: nil } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
+ context 'when except: is used with rules:' do
+ let(:config) { { except: { refs: %w[master] }, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'and except: is blank' do
+ let(:config) { { except: nil, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'and rules: is blank' do
+ let(:config) { { except: { refs: %w[master] }, rules: nil } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
+ context 'when only: and except: are both used with rules:' do
+ let(:config) do
+ {
+ only: %w[merge_requests],
+ except: { refs: %w[master] },
+ rules: [{ if: '$THIS' }]
+ }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'when only: and except: as both blank' do
+ let(:config) do
+ { only: nil, except: nil, rules: [{ if: '$THIS' }] }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'when rules: is blank' do
+ let(:config) do
+ { only: %w[merge_requests], except: { refs: %w[master] }, rules: nil }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+ end
+ end
+
+ describe '#relevant?' do
+ it 'is a relevant entry' do
+ entry = node_class.new({ stage: 'test' }, name: :rspec)
+
+ expect(entry).to be_relevant
+ end
+ end
+
+ describe '#compose!' do
+ let(:unspecified) { double('unspecified', 'specified?' => false) }
+ let(:default) { double('default', '[]' => unspecified) }
+ let(:workflow) { double('workflow', 'has_rules?' => false) }
+ let(:variables) { }
+
+ let(:deps) do
+ double('deps',
+ default_entry: default,
+ workflow_entry: workflow,
+ variables_value: variables)
+ end
+
+ context 'with workflow rules' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:name, :has_workflow_rules?, :only, :rules, :result) do
+ "uses default only" | false | nil | nil | { refs: %w[branches tags] }
+ "uses user only" | false | %w[branches] | nil | { refs: %w[branches] }
+ "does not define only" | false | nil | [] | nil
+ "does not define only" | true | nil | nil | nil
+ "uses user only" | true | %w[branches] | nil | { refs: %w[branches] }
+ "does not define only" | true | nil | [] | nil
+ end
+
+ with_them do
+ let(:config) { { script: 'ls', rules: rules, only: only }.compact }
+
+ it "#{name}" do
+ expect(workflow).to receive(:has_rules?) { has_workflow_rules? }
+
+ entry.compose!(deps)
+
+ expect(entry.only_value).to eq(result)
+ end
+ end
+ end
+
+ context 'when workflow rules is used' do
+ context 'when rules are used' do
+ let(:config) { { script: 'ls', cache: { key: 'test' }, rules: [] } }
+
+ it 'does not define only' do
+ expect(entry).not_to be_only_defined
+ end
+ end
+
+ context 'when rules are not used' do
+ let(:config) { { script: 'ls', cache: { key: 'test' }, only: [] } }
+
+ it 'does not define only' do
+ expect(entry).not_to be_only_defined
+ end
+ end
+ end
+
+ context 'with inheritance' do
+ context 'of variables' do
+ let(:config) do
+ { variables: { A: 'job', B: 'job' } }
+ end
+
+ before do
+ entry.compose!(deps)
+ end
+
+ context 'with only job variables' do
+ it 'does return defined variables' do
+ expect(entry.value).to include(
+ variables: { 'A' => 'job', 'B' => 'job' }
+ )
+ end
+ end
+
+ context 'when root yaml variables are used' do
+ let(:variables) do
+ Gitlab::Ci::Config::Entry::Variables.new(
+ A: 'root', C: 'root', D: 'root'
+ ).value
+ end
+
+ it 'does return all variables and overwrite them' do
+ expect(entry.value).to include(
+ variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root', 'D' => 'root' }
+ )
+ end
+
+ context 'when inherit of defaults is disabled' do
+ let(:config) do
+ {
+ variables: { A: 'job', B: 'job' },
+ inherit: { variables: false }
+ }
+ end
+
+ it 'does return only job variables' do
+ expect(entry.value).to include(
+ variables: { 'A' => 'job', 'B' => 'job' }
+ )
+ end
+ end
+
+ context 'when inherit of only specific variable is enabled' do
+ let(:config) do
+ {
+ variables: { A: 'job', B: 'job' },
+ inherit: { variables: ['D'] }
+ }
+ end
+
+ it 'does return only job variables' do
+ expect(entry.value).to include(
+ variables: { 'A' => 'job', 'B' => 'job', 'D' => 'root' }
+ )
+ end
+ end
+ end
+ end
+
+ context 'of default:tags' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:name, :default_tags, :tags, :inherit_default, :result) do
+ "only local tags" | nil | %w[a b] | nil | %w[a b]
+ "only local tags" | nil | %w[a b] | true | %w[a b]
+ "only local tags" | nil | %w[a b] | false | %w[a b]
+ "global and local tags" | %w[b c] | %w[a b] | nil | %w[a b]
+ "global and local tags" | %w[b c] | %w[a b] | true | %w[a b]
+ "global and local tags" | %w[b c] | %w[a b] | false | %w[a b]
+ "only global tags" | %w[b c] | nil | nil | %w[b c]
+ "only global tags" | %w[b c] | nil | true | %w[b c]
+ "only global tags" | %w[b c] | nil | false | nil
+ "only global tags" | %w[b c] | nil | %w[image] | nil
+ "only global tags" | %w[b c] | nil | %w[tags] | %w[b c]
+ end
+
+ with_them do
+ let(:config) do
+ { tags: tags,
+ inherit: { default: inherit_default } }
+ end
+
+ let(:default_specified_tags) do
+ double('tags',
+ 'specified?' => true,
+ 'valid?' => true,
+ 'value' => default_tags,
+ 'errors' => [])
+ end
+
+ before do
+ allow(default).to receive('[]').with(:tags).and_return(default_specified_tags)
+
+ entry.compose!(deps)
+
+ expect(entry).to be_valid
+ end
+
+ it { expect(entry.tags_value).to eq(result) }
+ end
+ end
+ end
+ end
+
+ context 'when composed' do
+ before do
+ entry.compose!
+ end
+
+ describe '#value' do
+ context 'when entry is correct' do
+ let(:config) do
+ { stage: 'test' }
+ end
+
+ it 'returns correct value' do
+ expect(entry.value).to eq(
+ name: :rspec,
+ stage: 'test',
+ only: { refs: %w[branches tags] },
+ variables: {}
+ )
+ end
+ 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 c64bb0a4cc3..2c8f76c8f34 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -44,6 +44,8 @@ describe Gitlab::Ci::Config::Entry::Reports do
:license_scanning | 'gl-license-scanning-report.json'
:performance | 'performance.json'
:lsif | 'lsif.json'
+ :dotenv | 'build.dotenv'
+ :cobertura | 'cobertura-coverage.xml'
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 cf0a3cfa963..c3871b6b3cf 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::Ci::Config::Entry::Root do
image: 'ruby:2.2',
default: {},
services: ['postgres:9.1', 'mysql:5.5'],
- variables: { VAR: 'value' },
+ variables: { VAR: 'root' },
after_script: ['make clean'],
stages: %w(build pages release),
cache: { key: 'k', untracked: true, paths: ['public/'] },
@@ -42,6 +42,7 @@ describe Gitlab::Ci::Config::Entry::Root do
stage: 'release',
before_script: [],
after_script: [],
+ variables: { 'VAR' => 'job' },
script: ["make changelog | tee release_changelog.txt"],
release: {
tag_name: 'v0.06',
@@ -127,7 +128,7 @@ 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' },
- variables: {},
+ variables: { 'VAR' => 'root' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -141,7 +142,7 @@ 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' },
- variables: {},
+ variables: { 'VAR' => 'root' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -157,7 +158,7 @@ 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" },
only: { refs: %w(branches tags) },
- variables: {},
+ variables: { 'VAR' => 'job' },
after_script: [],
ignore: false,
scheduling_type: :stage }
@@ -175,11 +176,11 @@ describe Gitlab::Ci::Config::Entry::Root do
image: 'ruby:2.1',
services: ['postgres:9.1', 'mysql:5.5']
},
- variables: { VAR: 'value' },
+ variables: { VAR: 'root' },
stages: %w(build pages),
cache: { key: 'k', untracked: true, paths: ['public/'] },
rspec: { script: %w[rspec ls] },
- spinach: { before_script: [], variables: { VAR: 'AA' }, script: 'spinach' } }
+ spinach: { before_script: [], variables: { VAR: 'job' }, script: 'spinach' } }
end
context 'when composed' do
@@ -203,7 +204,7 @@ 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" },
- variables: {},
+ variables: { 'VAR' => 'root' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
@@ -215,7 +216,7 @@ 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" },
- variables: { 'VAR' => 'AA' },
+ variables: { 'VAR' => 'job' },
ignore: false,
after_script: ['make clean'],
only: { refs: %w[branches tags] },
diff --git a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
new file mode 100644
index 00000000000..a8eb13c47bc
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb
@@ -0,0 +1,167 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::External::File::Artifact do
+ let(:parent_pipeline) { create(:ci_pipeline) }
+ let(:context) do
+ Gitlab::Ci::Config::External::Context.new(parent_pipeline: parent_pipeline)
+ end
+
+ let(:external_file) { described_class.new(params, context) }
+
+ describe '#matching?' do
+ context 'when params contain artifact location' do
+ let(:params) { { artifact: 'generated.yml' } }
+
+ it 'returns true' do
+ expect(external_file).to be_matching
+ end
+ end
+
+ context 'when params does not contain artifact location' do
+ let(:params) { {} }
+
+ it 'returns false' do
+ expect(external_file).not_to be_matching
+ end
+ end
+ end
+
+ describe '#valid?' do
+ shared_examples 'is invalid' do
+ it 'is not valid' do
+ expect(external_file).not_to be_valid
+ end
+
+ it 'sets the expected error' do
+ expect(external_file.errors)
+ .to contain_exactly(expected_error)
+ end
+ end
+
+ describe 'when used in non child pipeline context' do
+ let(:parent_pipeline) { nil }
+ let(:params) { { artifact: 'generated.yml' } }
+
+ let(:expected_error) do
+ 'Including configs from artifacts is only allowed when triggering child pipelines'
+ end
+
+ it_behaves_like 'is invalid'
+ end
+
+ context 'when used in child pipeline context' do
+ let(:parent_pipeline) { create(:ci_pipeline) }
+
+ context 'when job is not provided' do
+ let(:params) { { artifact: 'generated.yml' } }
+
+ let(:expected_error) do
+ 'Job must be provided when including configs from artifacts'
+ end
+
+ it_behaves_like 'is invalid'
+ end
+
+ context 'when job is provided' do
+ let(:params) { { artifact: 'generated.yml', job: 'generator' } }
+
+ context 'when job does not exist in the parent pipeline' do
+ let(:expected_error) do
+ 'Job `generator` not found in parent pipeline or does not have artifacts!'
+ end
+
+ it_behaves_like 'is invalid'
+ end
+
+ context 'when job exists in the parent pipeline' do
+ let!(:generator_job) { create(:ci_build, name: 'generator', pipeline: parent_pipeline) }
+
+ context 'when job does not have artifacts' do
+ let(:expected_error) do
+ 'Job `generator` not found in parent pipeline or does not have artifacts!'
+ end
+
+ it_behaves_like 'is invalid'
+ end
+
+ context 'when job has archive artifacts' do
+ let!(:artifacts) do
+ create(:ci_job_artifact, :archive,
+ job: generator_job,
+ file: fixture_file_upload(Rails.root.join('spec/fixtures/pages.zip'), 'application/zip'))
+ end
+
+ let(:expected_error) do
+ 'Job `generator` has missing artifacts metadata and cannot be extracted!'
+ end
+
+ it_behaves_like 'is invalid'
+
+ context 'when job has artifacts exceeding the max allowed size' do
+ let(:expected_error) do
+ "Artifacts archive for job `generator` is too large: max 1 KB"
+ end
+
+ before do
+ stub_const("#{Gitlab::Ci::ArtifactFileReader}::MAX_ARCHIVE_SIZE", 1.kilobyte)
+ end
+
+ it_behaves_like 'is invalid'
+ end
+
+ context 'when job has artifacts metadata' do
+ let!(:metadata) do
+ create(:ci_job_artifact, :metadata, job: generator_job)
+ end
+
+ let(:expected_error) do
+ 'Path `generated.yml` does not exist inside the `generator` artifacts archive!'
+ end
+
+ it_behaves_like 'is invalid'
+
+ context 'when file is found in metadata' do
+ let!(:artifacts) { create(:ci_job_artifact, :archive, job: generator_job) }
+ let!(:metadata) { create(:ci_job_artifact, :metadata, job: generator_job) }
+
+ context 'when file is empty' do
+ before do
+ allow_next_instance_of(Gitlab::Ci::ArtifactFileReader) do |reader|
+ allow(reader).to receive(:read).and_return('')
+ end
+ end
+
+ let(:expected_error) do
+ 'File `generated.yml` is empty!'
+ end
+
+ it_behaves_like 'is invalid'
+ end
+
+ context 'when file is not empty' do
+ it 'is valid' do
+ expect(external_file).to be_valid
+ expect(external_file.content).to be_present
+ end
+
+ it 'propagates parent_pipeline to nested includes' do
+ expected_attrs = {
+ parent_pipeline: parent_pipeline,
+ project: anything,
+ sha: anything,
+ user: anything
+ }
+ expect(context).to receive(:mutate).with(expected_attrs).and_call_original
+ external_file.content
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
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 95f0c93e758..c9851239859 100644
--- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb
@@ -3,15 +3,22 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Local do
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
-
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
let(:sha) { '12345' }
- let(:context_params) { { project: project, sha: sha, user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
-
let(:params) { { local: location } }
let(:local_file) { described_class.new(params, context) }
+ let(:parent_pipeline) { double(:parent_pipeline) }
+
+ let(:context_params) do
+ {
+ project: project,
+ sha: sha,
+ user: user,
+ parent_pipeline: parent_pipeline
+ }
+ end
before do
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
@@ -119,7 +126,11 @@ describe Gitlab::Ci::Config::External::File::Local do
subject { local_file.send(:expand_context_attrs) }
it 'inherits project, user and sha' do
- is_expected.to include(user: user, project: project, sha: sha)
+ is_expected.to include(
+ user: user,
+ project: project,
+ sha: sha,
+ parent_pipeline: parent_pipeline)
end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
index 0aea3a59b33..b2924ae9d91 100644
--- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb
@@ -3,15 +3,23 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Project do
- set(:context_project) { create(:project) }
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
-
+ let_it_be(:context_project) { create(:project) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
let(:context_user) { user }
- let(:context_params) { { project: context_project, sha: '12345', user: context_user } }
+ let(:parent_pipeline) { double(:parent_pipeline) }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:project_file) { described_class.new(params, context) }
+ let(:context_params) do
+ {
+ project: context_project,
+ sha: '12345',
+ user: context_user,
+ parent_pipeline: parent_pipeline
+ }
+ end
+
before do
project.add_developer(user)
@@ -153,7 +161,11 @@ describe Gitlab::Ci::Config::External::File::Project do
subject { project_file.send(:expand_context_attrs) }
it 'inherits user, and target project and sha' do
- is_expected.to include(user: user, project: project, sha: project.commit('master').id)
+ is_expected.to include(
+ user: user,
+ project: project,
+ sha: project.commit('master').id,
+ parent_pipeline: parent_pipeline)
end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/template_spec.rb b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
index ee1660e4dfd..7ea5aadac52 100644
--- a/spec/lib/gitlab/ci/config/external/file/template_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/template_spec.rb
@@ -3,14 +3,12 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Template do
- set(:project) { create(:project) }
- set(:user) { create(:user) }
-
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
let(:context_params) { { project: project, sha: '12345', user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:template) { 'Auto-DevOps.gitlab-ci.yml' }
let(:params) { { template: template } }
-
let(:template_file) { described_class.new(params, context) }
before do
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index 2a5f62f7e74..6839002c3ab 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -5,9 +5,8 @@ require 'spec_helper'
describe Gitlab::Ci::Config::External::Mapper do
include StubRequests
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
-
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index bb2d3f66972..ff08d4703fe 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -5,10 +5,9 @@ require 'spec_helper'
describe Gitlab::Ci::Config::External::Processor do
include StubRequests
- set(:project) { create(:project, :repository) }
- set(:another_project) { create(:project, :repository) }
- set(:user) { create(:user) }
-
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:another_project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
let(:sha) { '12345' }
let(:context_params) { { project: project, sha: sha, user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 63a36995284..d8101e8a621 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe Gitlab::Ci::Config do
include StubRequests
- set(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
before do
allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance|
@@ -79,26 +79,6 @@ describe Gitlab::Ci::Config do
it { is_expected.to eq %w[.pre stage1 stage2 .post] }
end
-
- context 'with feature disabled' do
- before do
- stub_feature_flags(ci_pre_post_pipeline_stages: false)
- end
-
- let(:yml) do
- <<-EOS
- stages:
- - stage1
- - stage2
- job1:
- stage: stage1
- script:
- - ls
- EOS
- end
-
- it { is_expected.to eq %w[stage1 stage2] }
- end
end
end
@@ -376,23 +356,6 @@ describe Gitlab::Ci::Config do
end
end
- context 'when context expansion timeout is disabled' do
- before do
- allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance|
- allow(instance).to receive(:check_execution_time!).and_call_original
- end
-
- allow(Feature)
- .to receive(:enabled?)
- .with(:ci_limit_yaml_expansion, project, default_enabled: true)
- .and_return(false)
- end
-
- it 'does not raises errors' do
- expect { config }.not_to raise_error
- end
- end
-
describe 'external file version' do
context 'when external local file SHA is defined' do
it 'is using a defined value' do
@@ -541,5 +504,76 @@ describe Gitlab::Ci::Config do
end
end
end
+
+ context 'when including file from artifact' do
+ let(:config) do
+ described_class.new(
+ gitlab_ci_yml,
+ project: nil,
+ sha: nil,
+ user: nil,
+ parent_pipeline: parent_pipeline)
+ end
+
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - artifact: generated.yml
+ job: rspec
+ HEREDOC
+ end
+
+ let(:parent_pipeline) { nil }
+
+ context 'when used in the context of a child pipeline' do
+ # This job has ci_build_artifacts.zip artifact archive which
+ # contains generated.yml
+ let!(:job) { create(:ci_build, :artifacts, name: 'rspec', pipeline: parent_pipeline) }
+ let(:parent_pipeline) { create(:ci_pipeline) }
+
+ it 'returns valid config' do
+ expect(config).to be_valid
+ end
+
+ context 'when job key is missing' do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - artifact: generated.yml
+ HEREDOC
+ end
+
+ it 'raises an error' do
+ expect { config }.to raise_error(
+ described_class::ConfigError,
+ 'Job must be provided when including configs from artifacts'
+ )
+ end
+ end
+
+ context 'when artifact key is missing' do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - job: rspec
+ HEREDOC
+ end
+
+ it 'raises an error' do
+ expect { config }.to raise_error(
+ described_class::ConfigError,
+ /needs to match exactly one accessor!/
+ )
+ end
+ end
+ end
+
+ it 'disallows the use in parent pipelines' do
+ expect { config }.to raise_error(
+ described_class::ConfigError,
+ 'Including configs from artifacts is only allowed when triggering child pipelines'
+ )
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
new file mode 100644
index 00000000000..e97544683db
--- /dev/null
+++ b/spec/lib/gitlab/ci/parsers/coverage/cobertura_spec.rb
@@ -0,0 +1,176 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Ci::Parsers::Coverage::Cobertura do
+ describe '#parse!' do
+ subject { described_class.new.parse!(cobertura, coverage_report) }
+
+ let(:coverage_report) { Gitlab::Ci::Reports::CoverageReports.new }
+
+ context 'when data is Cobertura style XML' do
+ context 'when there is no <class>' do
+ let(:cobertura) { '' }
+
+ it 'parses XML and returns empty coverage' do
+ expect { subject }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
+
+ context 'when there is a single <class>' do
+ context 'with no lines' do
+ let(:cobertura) do
+ <<-EOF.strip_heredoc
+ <classes><class filename="app.rb"></class></classes>
+ EOF
+ end
+
+ it 'parses XML and returns empty coverage' do
+ expect { subject }.not_to raise_error
+
+ expect(coverage_report.files).to eq({})
+ end
+ end
+
+ context 'with a single line' do
+ let(:cobertura) do
+ <<-EOF.strip_heredoc
+ <classes>
+ <class filename="app.rb"><lines>
+ <line number="1" hits="2"/>
+ </lines></class>
+ </classes>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with coverage' do
+ expect { subject }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
+ end
+ end
+
+ context 'with multipe lines and methods info' do
+ let(:cobertura) do
+ <<-EOF.strip_heredoc
+ <classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ </classes>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with coverage' do
+ expect { subject }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+ end
+
+ context 'when there are multipe <class>' do
+ context 'with the same filename and different lines' do
+ let(:cobertura) do
+ <<-EOF.strip_heredoc
+ <classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="app.rb"><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with merged coverage' do
+ expect { subject }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
+ end
+ end
+
+ context 'with the same filename and lines' do
+ let(:cobertura) do
+ <<-EOF.strip_heredoc
+ <packages><package><classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="1"/>
+ <line number="2" hits="1"/>
+ </lines></class>
+ </classes></package></packages>
+ EOF
+ end
+
+ it 'parses XML and returns a single file with summed-up coverage' do
+ expect { subject }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 3, 2 => 1 } })
+ end
+ end
+
+ context 'with missing filename' do
+ let(:cobertura) do
+ <<-EOF.strip_heredoc
+ <classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class><methods/><lines>
+ <line number="6" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes>
+ EOF
+ end
+
+ it 'parses XML and ignores class with missing name' do
+ expect { subject }.not_to raise_error
+
+ expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
+ end
+ end
+
+ context 'with invalid line information' do
+ let(:cobertura) do
+ <<-EOF.strip_heredoc
+ <classes>
+ <class filename="app.rb"><methods/><lines>
+ <line number="1" hits="2"/>
+ <line number="2" hits="0"/>
+ </lines></class>
+ <class filename="app.rb"><methods/><lines>
+ <line null="test" hits="1"/>
+ <line number="7" hits="1"/>
+ </lines></class>
+ </classes>
+ EOF
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::CoberturaParserError)
+ end
+ end
+ end
+ end
+
+ context 'when data is not Cobertura style XML' do
+ let(:cobertura) { { coverage: '12%' }.to_json }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::CoberturaParserError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
index b91cf1dd3ed..9a486c312d4 100644
--- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
@@ -205,6 +205,75 @@ describe Gitlab::Ci::Parsers::Test::Junit do
end
end
+ context 'when data contains an attachment tag' do
+ let(:junit) do
+ <<~EOF
+ <testsuites>
+ <testsuite>
+ <testcase classname='Calculator' name='sumTest1' time='0.01'>
+ <failure>Some failure</failure>
+ <system-out>[[ATTACHMENT|some/path.png]]</system-out>
+ </testcase>
+ </testsuite>
+ </testsuites>
+ EOF
+ end
+
+ it 'add attachment to a test case' do
+ expect { subject }.not_to raise_error
+
+ expect(test_cases[0].has_attachment?).to be_truthy
+ expect(test_cases[0].attachment).to eq("some/path.png")
+ end
+ end
+
+ context 'when data contains multiple attachments tag' do
+ let(:junit) do
+ <<~EOF
+ <testsuites>
+ <testsuite>
+ <testcase classname='Calculator' name='sumTest1' time='0.01'>
+ <failure>Some failure</failure>
+ <system-out>
+ [[ATTACHMENT|some/path.png]]
+ [[ATTACHMENT|some/path.html]]
+ </system-out>
+ </testcase>
+ </testsuite>
+ </testsuites>
+ EOF
+ end
+
+ it 'adds the first match attachment to a test case' do
+ expect { subject }.not_to raise_error
+
+ expect(test_cases[0].has_attachment?).to be_truthy
+ expect(test_cases[0].attachment).to eq("some/path.png")
+ end
+ end
+
+ context 'when data does not match attachment tag regex' do
+ let(:junit) do
+ <<~EOF
+ <testsuites>
+ <testsuite>
+ <testcase classname='Calculator' name='sumTest1' time='0.01'>
+ <failure>Some failure</failure>
+ <system-out>[[attachment]some/path.png]]</system-out>
+ </testcase>
+ </testsuite>
+ </testsuites>
+ EOF
+ end
+
+ it 'does not add attachment to a test case' do
+ expect { subject }.not_to raise_error
+
+ expect(test_cases[0].has_attachment?).to be_falsy
+ expect(test_cases[0].attachment).to be_nil
+ end
+ end
+
private
def flattened_test_cases(test_suite)
diff --git a/spec/lib/gitlab/ci/parsers_spec.rb b/spec/lib/gitlab/ci/parsers_spec.rb
index 4b647bffe59..9d6896b3cb4 100644
--- a/spec/lib/gitlab/ci/parsers_spec.rb
+++ b/spec/lib/gitlab/ci/parsers_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Ci::Parsers do
describe '.fabricate!' do
subject { described_class.fabricate!(file_type) }
- context 'when file_type exists' do
+ context 'when file_type is junit' do
let(:file_type) { 'junit' }
it 'fabricates the class' do
@@ -14,6 +14,14 @@ describe Gitlab::Ci::Parsers do
end
end
+ context 'when file_type is cobertura' do
+ let(:file_type) { 'cobertura' }
+
+ it 'fabricates the class' do
+ is_expected.to be_a(described_class::Coverage::Cobertura)
+ end
+ end
+
context 'when file_type does not exist' do
let(:file_type) { 'undefined' }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
new file mode 100644
index 00000000000..542a2462b59
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/build/associations_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::Build::Associations do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user, developer_projects: [project]) }
+ let(:pipeline) { Ci::Pipeline.new }
+ let(:step) { described_class.new(pipeline, command) }
+
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ source: :push,
+ origin_ref: 'master',
+ checkout_sha: project.commit.id,
+ after_sha: nil,
+ before_sha: nil,
+ trigger_request: nil,
+ schedule: nil,
+ merge_request: nil,
+ project: project,
+ current_user: user,
+ bridge: bridge)
+ end
+
+ context 'when a bridge is passed in to the pipeline creation' do
+ let(:bridge) { create(:ci_bridge) }
+
+ it 'links the pipeline to the upstream bridge job' do
+ step.perform!
+
+ expect(pipeline.source_pipeline).to be_present
+ expect(pipeline.source_pipeline).to be_valid
+ expect(pipeline.source_pipeline).to have_attributes(
+ source_pipeline: bridge.pipeline, source_project: bridge.project,
+ source_bridge: bridge, project: project
+ )
+ end
+
+ it 'never breaks the chain' do
+ step.perform!
+
+ expect(step.break?).to eq(false)
+ end
+ end
+
+ context 'when a bridge is not passed in to the pipeline creation' do
+ let(:bridge) { nil }
+
+ it 'leaves the source pipeline empty' do
+ step.perform!
+
+ expect(pipeline.source_pipeline).to be_nil
+ end
+
+ it 'never breaks the chain' do
+ step.perform!
+
+ expect(step.break?).to eq(false)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index b81094f8b4a..094563bd979 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Build do
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user, developer_projects: [project]) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user, developer_projects: [project]) }
let(:pipeline) { Ci::Pipeline.new }
let(:variables_attributes) do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 5775e934cfd..56707f4e6e4 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Command do
- set(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
describe '#initialize' do
subject do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
index 650ab193997..f18ad05a704 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Create do
- set(:project) { create(:project) }
- set(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
let(:pipeline) do
build(:ci_empty_pipeline, project: project, ref: 'master')
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 52e9432dc92..6dab5679e60 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Populate do
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
let(:pipeline) do
build(:ci_pipeline, project: project, ref: 'master', user: user)
diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
index 9cb59442dfd..9033b71b19f 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
@@ -3,9 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Sequence do
- set(:project) { create(:project) }
- set(:user) { create(:user) }
-
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
let(:pipeline) { build_stubbed(:ci_pipeline) }
let(:command) { Gitlab::Ci::Pipeline::Chain::Command.new }
let(:first_step) { spy('first step') }
diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
index fe46633ed1b..55d6d35340e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Skip do
- set(:project) { create(:project) }
- set(:user) { create(:user) }
- set(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:pipeline, reload: true) { create(:ci_pipeline, project: project) }
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index 24d3beb35b9..8bf8bdf08ff 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
let(:pipeline) do
build_stubbed(:ci_pipeline, project: project)
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
index b866355906e..83271007ab0 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
@@ -3,10 +3,9 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
let(:pipeline) { build_stubbed(:ci_pipeline) }
-
let!(:step) { described_class.new(pipeline, command) }
before do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 1f5fc000832..01f65939da7 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -214,24 +214,98 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.to be_a(::Ci::Build) }
it { is_expected.to be_valid }
- context 'when job has environment name' do
- let(:attributes) { { name: 'rspec', ref: 'master', environment: 'production' } }
-
+ shared_examples_for 'deployment job' do
it 'returns a job with deployment' do
expect(subject.deployment).not_to be_nil
expect(subject.deployment.deployable).to eq(subject)
- expect(subject.deployment.environment.name).to eq('production')
+ expect(subject.deployment.environment.name).to eq(expected_environment_name)
end
+ end
+
+ shared_examples_for 'non-deployment job' do
+ it 'returns a job without deployment' do
+ expect(subject.deployment).to be_nil
+ end
+ end
+
+ shared_examples_for 'ensures environment existence' do
+ it 'has environment' do
+ expect(subject).to be_has_environment
+ expect(subject.environment).to eq(environment_name)
+ expect(subject.metadata.expanded_environment_name).to eq(expected_environment_name)
+ expect(Environment.exists?(name: expected_environment_name)).to eq(true)
+ end
+ end
+
+ shared_examples_for 'ensures environment inexistence' do
+ it 'does not have environment' do
+ expect(subject).not_to be_has_environment
+ expect(subject.environment).to be_nil
+ expect(subject.metadata.expanded_environment_name).to be_nil
+ expect(Environment.exists?(name: expected_environment_name)).to eq(false)
+ end
+ end
+
+ context 'when job deploys to production' do
+ let(:environment_name) { 'production' }
+ let(:expected_environment_name) { 'production' }
+ let(:attributes) { { name: 'deploy', ref: 'master', environment: 'production' } }
+
+ it_behaves_like 'deployment job'
+ it_behaves_like 'ensures environment existence'
context 'when the environment name is invalid' do
- let(:attributes) { { name: 'rspec', ref: 'master', environment: '!!!' } }
+ let(:attributes) { { name: 'deploy', ref: 'master', environment: '!!!' } }
- it 'returns a job without deployment' do
- expect(subject.deployment).to be_nil
+ it_behaves_like 'non-deployment job'
+ it_behaves_like 'ensures environment inexistence'
+
+ it 'tracks an exception' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ .with(an_instance_of(described_class::EnvironmentCreationFailure),
+ project_id: project.id,
+ reason: %q{Name can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', and spaces, but it cannot start or end with '/'})
+ .once
+
+ subject
end
end
end
+ context 'when job starts a review app' do
+ let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' }
+ let(:expected_environment_name) { "review/#{pipeline.ref}" }
+
+ let(:attributes) do
+ {
+ name: 'deploy', ref: 'master', environment: environment_name,
+ options: { environment: { name: environment_name } }
+ }
+ end
+
+ it_behaves_like 'deployment job'
+ it_behaves_like 'ensures environment existence'
+ end
+
+ context 'when job stops a review app' do
+ let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' }
+ let(:expected_environment_name) { "review/#{pipeline.ref}" }
+
+ let(:attributes) do
+ {
+ name: 'deploy', ref: 'master', environment: environment_name,
+ options: { environment: { name: environment_name, action: 'stop' } }
+ }
+ end
+
+ it 'returns a job without deployment' do
+ expect(subject.deployment).to be_nil
+ end
+
+ it_behaves_like 'non-deployment job'
+ it_behaves_like 'ensures environment existence'
+ end
+
context 'when job belongs to a resource group' do
let(:attributes) { { name: 'rspec', ref: 'master', resource_group_key: 'iOS' } }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
index c5c91135f60..ceb3cb28bc9 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
@@ -3,14 +3,15 @@
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Seed::Deployment do
- let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:project, refind: true) { create(:project, :repository) }
let(:pipeline) do
create(:ci_pipeline, project: project,
sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0')
end
let(:job) { build(:ci_build, project: project, pipeline: pipeline) }
- let(:seed) { described_class.new(job) }
+ let(:environment) { Gitlab::Ci::Pipeline::Seed::Environment.new(job).to_resource }
+ let(:seed) { described_class.new(job, environment) }
let(:attributes) { {} }
before do
@@ -24,10 +25,12 @@ describe Gitlab::Ci::Pipeline::Seed::Deployment do
let(:attributes) do
{
environment: 'production',
- options: { environment: { name: 'production' } }
+ options: { environment: { name: 'production', **kubernetes_options } }
}
end
+ let(:kubernetes_options) { {} }
+
it 'returns a deployment object with environment' do
expect(subject).to be_a(Deployment)
expect(subject.iid).to be_present
@@ -37,14 +40,30 @@ describe Gitlab::Ci::Pipeline::Seed::Deployment do
end
context 'when environment has deployment platform' do
- let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+ let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project], managed: managed_cluster) }
+ let(:managed_cluster) { true }
it 'sets the cluster and deployment_cluster' do
expect(subject.cluster).to eq(cluster) # until we stop double writing in 12.9: https://gitlab.com/gitlab-org/gitlab/issues/202628
- expect(subject.deployment_cluster).to have_attributes(
- cluster_id: cluster.id,
- kubernetes_namespace: subject.environment.deployment_namespace
- )
+ expect(subject.deployment_cluster.cluster).to eq(cluster)
+ end
+
+ context 'when a custom namespace is given' do
+ let(:kubernetes_options) { { kubernetes: { namespace: 'the-custom-namespace' } } }
+
+ context 'when cluster is managed' do
+ it 'does not set the custom namespace' do
+ expect(subject.deployment_cluster.kubernetes_namespace).not_to eq('the-custom-namespace')
+ end
+ end
+
+ context 'when cluster is not managed' do
+ let(:managed_cluster) { false }
+
+ it 'sets the custom namespace' do
+ expect(subject.deployment_cluster.kubernetes_namespace).to eq('the-custom-namespace')
+ end
+ end
end
end
@@ -82,5 +101,13 @@ describe Gitlab::Ci::Pipeline::Seed::Deployment do
is_expected.to be_nil
end
end
+
+ context 'when job does not have environment attribute' do
+ let(:attributes) { { name: 'test' } }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
index 71389999c6e..4c0464e5e7c 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
@@ -15,29 +15,68 @@ describe Gitlab::Ci::Pipeline::Seed::Environment do
describe '#to_resource' do
subject { seed.to_resource }
- context 'when job has environment attribute' do
- let(:attributes) do
- {
- environment: 'production',
- options: { environment: { name: 'production' } }
- }
- end
-
+ shared_examples_for 'returning a correct environment' do
it 'returns a persisted environment object' do
+ expect { subject }.to change { Environment.count }.by(1)
+
expect(subject).to be_a(Environment)
expect(subject).to be_persisted
expect(subject.project).to eq(project)
- expect(subject.name).to eq('production')
+ expect(subject.name).to eq(expected_environment_name)
end
context 'when environment has already existed' do
- let!(:environment) { create(:environment, project: project, name: 'production') }
+ let!(:environment) { create(:environment, project: project, name: expected_environment_name) }
it 'returns the existing environment object' do
+ expect { subject }.not_to change { Environment.count }
+
expect(subject).to be_persisted
expect(subject).to eq(environment)
end
end
end
+
+ context 'when job has environment attribute' do
+ let(:environment_name) { 'production' }
+ let(:expected_environment_name) { 'production' }
+
+ let(:attributes) do
+ {
+ environment: environment_name,
+ options: { environment: { name: environment_name } }
+ }
+ end
+
+ it_behaves_like 'returning a correct environment'
+ end
+
+ context 'when job starts a review app' do
+ let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' }
+ let(:expected_environment_name) { "review/#{job.ref}" }
+
+ let(:attributes) do
+ {
+ environment: environment_name,
+ options: { environment: { name: environment_name } }
+ }
+ end
+
+ it_behaves_like 'returning a correct environment'
+ end
+
+ context 'when job stops a review app' do
+ let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' }
+ let(:expected_environment_name) { "review/#{job.ref}" }
+
+ let(:attributes) do
+ {
+ environment: environment_name,
+ options: { environment: { name: environment_name, action: 'stop' } }
+ }
+ end
+
+ it_behaves_like 'returning a correct environment'
+ end
end
end
diff --git a/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb b/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb
new file mode 100644
index 00000000000..7cf43ceab32
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/coverage_reports_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Reports::CoverageReports do
+ let(:coverage_report) { described_class.new }
+
+ it { expect(coverage_report.files).to eq({}) }
+
+ describe '#pick' do
+ before do
+ coverage_report.add_file('app.rb', { 1 => 0, 2 => 1 })
+ coverage_report.add_file('routes.rb', { 3 => 1, 4 => 0 })
+ end
+
+ it 'returns only picked files while ignoring nonexistent ones' do
+ expect(coverage_report.pick(['routes.rb', 'nonexistent.txt'])).to eq({
+ files: { 'routes.rb' => { 3 => 1, 4 => 0 } }
+ })
+ end
+ end
+
+ describe '#add_file' do
+ context 'when providing two individual files' do
+ before do
+ coverage_report.add_file('app.rb', { 1 => 0, 2 => 1 })
+ coverage_report.add_file('routes.rb', { 3 => 1, 4 => 0 })
+ end
+
+ it 'initializes a new test suite and returns it' do
+ expect(coverage_report.files).to eq({
+ 'app.rb' => { 1 => 0, 2 => 1 },
+ 'routes.rb' => { 3 => 1, 4 => 0 }
+ })
+ end
+ end
+
+ context 'when providing the same files twice' do
+ context 'with different line coverage' do
+ before do
+ coverage_report.add_file('admin.rb', { 1 => 0, 2 => 1 })
+ coverage_report.add_file('admin.rb', { 3 => 1, 4 => 0 })
+ end
+
+ it 'initializes a new test suite and returns it' do
+ expect(coverage_report.files).to eq({
+ 'admin.rb' => { 1 => 0, 2 => 1, 3 => 1, 4 => 0 }
+ })
+ end
+ end
+
+ context 'with identical line coverage' do
+ before do
+ coverage_report.add_file('projects.rb', { 1 => 0, 2 => 1 })
+ coverage_report.add_file('projects.rb', { 1 => 0, 2 => 1 })
+ end
+
+ it 'initializes a new test suite and returns it' do
+ expect(coverage_report.files).to eq({
+ 'projects.rb' => { 1 => 0, 2 => 2 }
+ })
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/reports/test_case_spec.rb b/spec/lib/gitlab/ci/reports/test_case_spec.rb
index 20c489ee94c..c13161f3e7c 100644
--- a/spec/lib/gitlab/ci/reports/test_case_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_case_spec.rb
@@ -88,5 +88,17 @@ describe Gitlab::Ci::Reports::TestCase do
expect { test_case }.to raise_error(ArgumentError)
end
end
+
+ context 'when attachment is present' do
+ let(:attachment_test_case) { build(:test_case, :with_attachment) }
+
+ it "initializes the attachment if present" do
+ expect(attachment_test_case.attachment).to eq("some/path.png")
+ end
+
+ it '#has_attachment?' do
+ expect(attachment_test_case.has_attachment?).to be_truthy
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb
index 857483a9e0a..b9d4c39e0c2 100644
--- a/spec/lib/gitlab/ci/status/composite_spec.rb
+++ b/spec/lib/gitlab/ci/status/composite_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::Ci::Status::Composite do
- set(:pipeline) { create(:ci_pipeline) }
+ let_it_be(:pipeline) { create(:ci_pipeline) }
before(:all) do
@statuses = HasStatus::STATUSES_ENUM.map do |status, idx|
diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb
index b52064b3036..bc3d5b89220 100644
--- a/spec/lib/gitlab/ci/templates/templates_spec.rb
+++ b/spec/lib/gitlab/ci/templates/templates_spec.rb
@@ -2,33 +2,43 @@
require 'spec_helper'
-describe "CI YML Templates" do
- using RSpec::Parameterized::TableSyntax
-
+describe 'CI YML Templates' do
subject { Gitlab::Ci::YamlProcessor.new(content) }
- where(:template_name) do
- Gitlab::Template::GitlabCiYmlTemplate.all.map(&:full_name)
- end
-
- with_them do
- let(:content) do
- <<~EOS
- include:
- - template: #{template_name}
+ let(:all_templates) { Gitlab::Template::GitlabCiYmlTemplate.all.map(&:full_name) }
- concrete_build_implemented_by_a_user:
- stage: test
- script: do something
- EOS
+ let(:disabled_templates) do
+ Gitlab::Template::GitlabCiYmlTemplate.disabled_templates.map do |template|
+ template + Gitlab::Template::GitlabCiYmlTemplate.extension
end
+ end
+
+ context 'included in a CI YAML configuration' do
+ using RSpec::Parameterized::TableSyntax
- it 'is valid' do
- expect { subject }.not_to raise_error
+ where(:template_name) do
+ all_templates - disabled_templates
end
- it 'require default stages to be included' do
- expect(subject.stages).to include(*Gitlab::Ci::Config::Entry::Stages.default)
+ with_them do
+ let(:content) do
+ <<~EOS
+ include:
+ - template: #{template_name}
+
+ concrete_build_implemented_by_a_user:
+ stage: test
+ script: do something
+ EOS
+ end
+
+ it 'is valid' do
+ expect { subject }.not_to raise_error
+ end
+
+ it 'require default stages to be included' do
+ expect(subject.stages).to include(*Gitlab::Ci::Config::Entry::Stages.default)
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
index 1b034656e7d..92b3e5562a9 100644
--- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
+++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
include ChunkedIOHelpers
- set(:build) { create(:ci_build, :running) }
+ let_it_be(:build) { create(:ci_build, :running) }
let(:chunked_io) { described_class.new(build) }
before do
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 45b59541ce6..ea79073840d 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
- set(:build) { create(:ci_build, :running) }
+ let_it_be(:build) { create(:ci_build, :running) }
before do
stub_feature_flags(ci_enable_live_trace: true)
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index cfc3d852de0..af0a85f6c4e 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -509,28 +509,48 @@ module Gitlab
describe "before_script" do
context "in global context" do
- let(:config) do
- {
- before_script: ["global script"],
- test: { script: ["script"] }
- }
- end
+ using RSpec::Parameterized::TableSyntax
- it "return commands with scripts concatenated" do
- expect(subject[:options][:before_script]).to eq(["global script"])
+ where(:inherit, :result) do
+ nil | ["global script"]
+ { default: false } | nil
+ { default: true } | ["global script"]
+ { default: %w[before_script] } | ["global script"]
+ { default: %w[image] } | nil
end
- end
- context "in default context" do
- let(:config) do
- {
- default: { before_script: ["global script"] },
- test: { script: ["script"] }
- }
+ with_them do
+ let(:config) do
+ {
+ before_script: ["global script"],
+ test: { script: ["script"], inherit: inherit }
+ }
+ end
+
+ it { expect(subject[:options][:before_script]).to eq(result) }
end
- it "return commands with scripts concatenated" do
- expect(subject[:options][:before_script]).to eq(["global script"])
+ context "in default context" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:inherit, :result) do
+ nil | ["global script"]
+ { default: false } | nil
+ { default: true } | ["global script"]
+ { default: %w[before_script] } | ["global script"]
+ { default: %w[image] } | nil
+ end
+
+ with_them do
+ let(:config) do
+ {
+ default: { before_script: ["global script"] },
+ test: { script: ["script"], inherit: inherit }
+ }
+ end
+
+ it { expect(subject[:options][:before_script]).to eq(result) }
+ end
end
end
@@ -793,7 +813,7 @@ module Gitlab
context 'when job and global variables are defined' do
let(:global_variables) do
- { 'VAR1' => 'global1', 'VAR3' => 'global3' }
+ { 'VAR1' => 'global1', 'VAR3' => 'global3', 'VAR4' => 'global4' }
end
let(:job_variables) do
{ 'VAR1' => 'value1', 'VAR2' => 'value2' }
@@ -802,16 +822,44 @@ module Gitlab
{
before_script: ['pwd'],
variables: global_variables,
- rspec: { script: 'rspec', variables: job_variables }
+ rspec: { script: 'rspec', variables: job_variables, inherit: inherit }
}
end
- it 'returns all unique variables' do
- expect(subject).to contain_exactly(
- { key: 'VAR3', value: 'global3', public: true },
- { key: 'VAR1', value: 'value1', public: true },
- { key: 'VAR2', value: 'value2', public: true }
- )
+ context 'when no inheritance is specified' do
+ let(:inherit) { }
+
+ it 'returns all unique variables' do
+ expect(subject).to contain_exactly(
+ { key: 'VAR4', value: 'global4', public: true },
+ { key: 'VAR3', value: 'global3', public: true },
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ end
+ end
+
+ context 'when inheritance is disabled' do
+ let(:inherit) { { variables: false } }
+
+ it 'does not inherit variables' do
+ expect(subject).to contain_exactly(
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ end
+ end
+
+ context 'when specific variables are to inherited' do
+ let(:inherit) { { variables: %w[VAR1 VAR4] } }
+
+ it 'returns all unique variables and inherits only specified variables' do
+ expect(subject).to contain_exactly(
+ { key: 'VAR4', value: 'global4', public: true },
+ { key: 'VAR1', value: 'value1', public: true },
+ { key: 'VAR2', value: 'value2', public: true }
+ )
+ end
end
end
@@ -2419,7 +2467,9 @@ module Gitlab
it 'returns errors and empty configuration' do
expect(subject.valid?).to eq(false)
- expect(subject.errors).to eq(['jobs:rspec config contains unknown keys: bad_tags', 'jobs:rspec rules should be an array of hashes'])
+ expect(subject.errors).to contain_exactly(
+ 'jobs:rspec config contains unknown keys: bad_tags',
+ 'jobs:rspec rules should be an array of hashes')
expect(subject.content).to be_blank
end
end
diff --git a/spec/lib/gitlab/config/entry/attributable_spec.rb b/spec/lib/gitlab/config/entry/attributable_spec.rb
index bc29a194181..64a4670f483 100644
--- a/spec/lib/gitlab/config/entry/attributable_spec.rb
+++ b/spec/lib/gitlab/config/entry/attributable_spec.rb
@@ -59,7 +59,7 @@ describe Gitlab::Config::Entry::Attributable do
end
end
- expectation.to raise_error(ArgumentError, 'Method already defined: length')
+ expectation.to raise_error(ArgumentError, /Method 'length' already defined in/)
end
end
end
diff --git a/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb b/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb
new file mode 100644
index 00000000000..badfd56d571
--- /dev/null
+++ b/spec/lib/gitlab/config_checker/puma_rugged_checker_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ConfigChecker::PumaRuggedChecker do
+ describe '#check' do
+ subject { described_class.check }
+
+ context 'application is not puma' do
+ before do
+ allow(Gitlab::Runtime).to receive(:puma?).and_return(false)
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'application is puma' do
+ let(:notice_multi_threaded_puma_with_rugged) do
+ {
+ type: 'warning',
+ message: 'Puma is running with a thread count above 1 and the Rugged '\
+ 'service is enabled. This may decrease performance in some environments. '\
+ 'See our <a href="https://docs.gitlab.com/ee/administration/operations/puma.html#performance-caveat-when-using-puma-with-rugged">documentation</a> '\
+ 'for details of this issue.'
+ }
+ end
+
+ before do
+ allow(Gitlab::Runtime).to receive(:puma?).and_return(true)
+ allow(described_class).to receive(:running_puma_with_multiple_threads?).and_return(multithreaded_puma)
+ allow(described_class).to receive(:rugged_enabled_through_feature_flag?).and_return(rugged_enabled)
+ end
+
+ context 'not multithreaded_puma and rugged API enabled' do
+ let(:multithreaded_puma) { false }
+ let(:rugged_enabled) { true }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'not multithreaded_puma and rugged API is not enabled' do
+ let(:multithreaded_puma) { false }
+ let(:rugged_enabled) { false }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'multithreaded_puma and rugged API is not enabled' do
+ let(:multithreaded_puma) { true }
+ let(:rugged_enabled) { false }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'multithreaded_puma and rugged API is enabled' do
+ let(:multithreaded_puma) { true }
+ let(:rugged_enabled) { true }
+
+ it 'report multi_threaded_puma_with_rugged notices' do
+ is_expected.to contain_exactly(notice_multi_threaded_puma_with_rugged)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/danger/commit_linter_spec.rb b/spec/lib/gitlab/danger/commit_linter_spec.rb
index 0cf7ac64e43..a4760c942dc 100644
--- a/spec/lib/gitlab/danger/commit_linter_spec.rb
+++ b/spec/lib/gitlab/danger/commit_linter_spec.rb
@@ -152,6 +152,18 @@ describe Gitlab::Danger::CommitLinter do
end
end
+ context 'when subject is a WIP' do
+ let(:final_message) { 'A B C' }
+ # commit message with prefix will be over max length. commit message without prefix will be of maximum size
+ let(:commit_message) { described_class::WIP_PREFIX + final_message + 'D' * (described_class::WARN_SUBJECT_LENGTH - final_message.size) }
+
+ it 'does not have any problems' do
+ commit_linter.lint
+
+ expect(commit_linter.problems).to be_empty
+ end
+ end
+
context 'when subject is too short and too long' do
let(:commit_message) { 'A ' + 'B' * described_class::MAX_LINE_LENGTH }
@@ -183,7 +195,40 @@ describe Gitlab::Danger::CommitLinter do
end
end
- context 'when subject ands with a period' do
+ [
+ '[ci skip] A commit message',
+ '[Ci skip] A commit message',
+ '[API] A commit message'
+ ].each do |message|
+ context "when subject is '#{message}'" do
+ let(:commit_message) { message }
+
+ it 'does not add a problem' do
+ expect(commit_linter).not_to receive(:add_problem)
+
+ commit_linter.lint
+ end
+ end
+ end
+
+ [
+ '[ci skip]A commit message',
+ '[Ci skip] A commit message',
+ '[ci skip] a commit message',
+ '! A commit message'
+ ].each do |message|
+ context "when subject is '#{message}'" do
+ let(:commit_message) { message }
+
+ it 'adds a problem' do
+ expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class::DEFAULT_SUBJECT_DESCRIPTION)
+
+ commit_linter.lint
+ end
+ end
+ end
+
+ context 'when subject ends with a period' do
let(:commit_message) { 'A B C.' }
it 'adds a problem' do
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 2561e763429..dd183281977 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -175,9 +175,12 @@ describe Gitlab::Danger::Helper do
'spec/javascripts/foo' | :frontend
'spec/frontend/bar' | :frontend
'vendor/assets/foo' | :frontend
+ 'babel.config.js' | :frontend
'jest.config.js' | :frontend
'package.json' | :frontend
'yarn.lock' | :frontend
+ 'config/foo.js' | :frontend
+ 'config/deep/foo.js' | :frontend
'ee/app/assets/foo' | :frontend
'ee/app/views/foo' | :frontend
@@ -218,6 +221,9 @@ describe Gitlab::Danger::Helper do
'scripts/foo' | :engineering_productivity
'lib/gitlab/danger/foo' | :engineering_productivity
'ee/lib/gitlab/danger/foo' | :engineering_productivity
+ '.overcommit.yml.example' | :engineering_productivity
+ '.editorconfig' | :engineering_productivity
+ 'tooling/overcommit/foo' | :engineering_productivity
'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend
diff --git a/spec/lib/gitlab/data_builder/wiki_page_spec.rb b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
index 404d54bf2da..ae338e30a69 100644
--- a/spec/lib/gitlab/data_builder/wiki_page_spec.rb
+++ b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::DataBuilder::WikiPage do
- set(:project) { create(:project, :repository, :wiki_repo) }
+ let_it_be(:project) { create(:project, :repository, :wiki_repo) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/database/connection_timer_spec.rb b/spec/lib/gitlab/database/connection_timer_spec.rb
new file mode 100644
index 00000000000..c9e9d770343
--- /dev/null
+++ b/spec/lib/gitlab/database/connection_timer_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Database::ConnectionTimer do
+ let(:current_clock_value) { 1234.56 }
+
+ before do
+ allow(described_class).to receive(:current_clock_value).and_return(current_clock_value)
+ end
+
+ describe '.starting_now' do
+ let(:default_interval) { described_class::DEFAULT_INTERVAL }
+ let(:random_value) { 120 }
+
+ before do
+ allow(described_class).to receive(:rand).and_return(random_value)
+ end
+
+ context 'when the configured interval is positive' do
+ before do
+ allow(described_class).to receive(:interval).and_return(default_interval)
+ end
+
+ it 'randomizes the interval of the created timer' do
+ timer = described_class.starting_now
+
+ expect(timer.interval).to eq(default_interval + random_value)
+ end
+ end
+
+ context 'when the configured interval is not positive' do
+ before do
+ allow(described_class).to receive(:interval).and_return(0)
+ end
+
+ it 'sets the interval of the created timer to nil' do
+ timer = described_class.starting_now
+
+ expect(timer.interval).to be_nil
+ end
+ end
+ end
+
+ describe '.expired?' do
+ context 'when the interval is positive' do
+ context 'when the interval has elapsed' do
+ it 'returns true' do
+ timer = described_class.new(20, current_clock_value - 30)
+
+ expect(timer).to be_expired
+ end
+ end
+
+ context 'when the interval has not elapsed' do
+ it 'returns false' do
+ timer = described_class.new(20, current_clock_value - 10)
+
+ expect(timer).not_to be_expired
+ end
+ end
+ end
+
+ context 'when the interval is not positive' do
+ context 'when the interval has elapsed' do
+ it 'returns false' do
+ timer = described_class.new(0, current_clock_value - 30)
+
+ expect(timer).not_to be_expired
+ end
+ end
+
+ context 'when the interval has not elapsed' do
+ it 'returns false' do
+ timer = described_class.new(0, current_clock_value + 10)
+
+ expect(timer).not_to be_expired
+ end
+ end
+ end
+
+ context 'when the interval is nil' do
+ it 'returns false' do
+ timer = described_class.new(nil, current_clock_value - 30)
+
+ expect(timer).not_to be_expired
+ end
+ end
+ end
+
+ describe '.reset!' do
+ it 'updates the timer clock value' do
+ timer = described_class.new(20, current_clock_value - 20)
+ expect(timer.starting_clock_value).not_to eql(current_clock_value)
+
+ timer.reset!
+ expect(timer.starting_clock_value).to eql(current_clock_value)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 4cf2553b90d..1fd6157ce43 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -383,7 +383,8 @@ describe Gitlab::Database::MigrationHelpers do
it 'raises an error' do
expect(model).to receive(:foreign_key_exists?).and_return(false)
- expect { model.validate_foreign_key(:projects, :user_id) }.to raise_error(/cannot find/)
+ error_message = /Could not find foreign key "fk_name" on table "projects"/
+ expect { model.validate_foreign_key(:projects, :user_id, name: :fk_name) }.to raise_error(error_message)
end
end
end
@@ -587,6 +588,8 @@ describe Gitlab::Database::MigrationHelpers do
end
describe '#add_column_with_default' do
+ let(:column) { Project.columns.find { |c| c.name == "id" } }
+
context 'outside of a transaction' do
context 'when a column limit is not set' do
before do
@@ -601,6 +604,9 @@ describe Gitlab::Database::MigrationHelpers do
expect(model).to receive(:change_column_default)
.with(:projects, :foo, 10)
+
+ expect(model).to receive(:column_for)
+ .with(:projects, :foo).and_return(column)
end
it 'adds the column while allowing NULL values' do
@@ -655,6 +661,7 @@ describe Gitlab::Database::MigrationHelpers do
it 'adds the column with a limit' do
allow(model).to receive(:transaction_open?).and_return(false)
allow(model).to receive(:transaction).and_yield
+ allow(model).to receive(:column_for).with(:projects, :foo).and_return(column)
allow(model).to receive(:update_column_in_batches).with(:projects, :foo, 10)
allow(model).to receive(:change_column_null).with(:projects, :foo, false)
allow(model).to receive(:change_column_default).with(:projects, :foo, 10)
@@ -721,50 +728,68 @@ describe Gitlab::Database::MigrationHelpers do
before do
allow(model).to receive(:transaction_open?).and_return(false)
- allow(model).to receive(:column_for).and_return(old_column)
end
- it 'renames a column concurrently' do
- expect(model).to receive(:check_trigger_permissions!).with(:users)
+ context 'when the column to rename exists' do
+ before do
+ allow(model).to receive(:column_for).and_return(old_column)
+ end
- expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, '"users"', '"old"', '"new"')
+ it 'renames a column concurrently' do
+ expect(model).to receive(:check_trigger_permissions!).with(:users)
- expect(model).to receive(:add_column)
- .with(:users, :new, :integer,
- limit: old_column.limit,
- precision: old_column.precision,
- scale: old_column.scale)
+ expect(model).to receive(:install_rename_triggers_for_postgresql)
+ .with(trigger_name, '"users"', '"old"', '"new"')
- expect(model).to receive(:change_column_default)
- .with(:users, :new, old_column.default)
+ expect(model).to receive(:add_column)
+ .with(:users, :new, :integer,
+ limit: old_column.limit,
+ precision: old_column.precision,
+ scale: old_column.scale)
- expect(model).to receive(:update_column_in_batches)
+ expect(model).to receive(:change_column_default)
+ .with(:users, :new, old_column.default)
+
+ expect(model).to receive(:update_column_in_batches)
+
+ expect(model).to receive(:change_column_null).with(:users, :new, false)
+
+ expect(model).to receive(:copy_indexes).with(:users, :old, :new)
+ expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
- expect(model).to receive(:change_column_null).with(:users, :new, false)
+ model.rename_column_concurrently(:users, :old, :new)
+ end
- expect(model).to receive(:copy_indexes).with(:users, :old, :new)
- expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
+ context 'when default is false' do
+ let(:old_column) do
+ double(:column,
+ type: :boolean,
+ limit: nil,
+ default: false,
+ null: false,
+ precision: nil,
+ scale: nil)
+ end
- model.rename_column_concurrently(:users, :old, :new)
+ it 'copies the default to the new column' do
+ expect(model).to receive(:change_column_default)
+ .with(:users, :new, old_column.default)
+
+ model.rename_column_concurrently(:users, :old, :new)
+ end
+ end
end
- context 'when default is false' do
- let(:old_column) do
- double(:column,
- type: :boolean,
- limit: nil,
- default: false,
- null: false,
- precision: nil,
- scale: nil)
+ context 'when the column to be renamed does not exist' do
+ before do
+ allow(model).to receive(:columns).and_return([])
end
- it 'copies the default to the new column' do
- expect(model).to receive(:change_column_default)
- .with(:users, :new, old_column.default)
+ it 'raises an error with appropriate message' do
+ expect(model).to receive(:check_trigger_permissions!).with(:users)
- model.rename_column_concurrently(:users, :old, :new)
+ error_message = /Could not find column "missing_column" on table "users"/
+ expect { model.rename_column_concurrently(:users, :missing_column, :new) }.to raise_error(error_message)
end
end
end
@@ -1133,8 +1158,9 @@ describe Gitlab::Database::MigrationHelpers do
expect(column.name).to eq('id')
end
- it 'returns nil when a column does not exist' do
- expect(model.column_for(:users, :kittens)).to be_nil
+ it 'raises an error when a column does not exist' do
+ error_message = /Could not find column "kittens" on table "users"/
+ expect { model.column_for(:users, :kittens) }.to raise_error(error_message)
end
end
@@ -1332,6 +1358,15 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
+
+ context 'with other_arguments option' do
+ it 'queues jobs correctly' do
+ model.queue_background_migration_jobs_by_range_at_intervals(User, 'FooJob', 10.minutes, other_arguments: [1, 2])
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq(['FooJob', [id1, id3, 1, 2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.minutes.from_now.to_f)
+ end
+ end
end
context "when the model doesn't have an ID column" do
@@ -1893,4 +1928,60 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
+
+ describe '#migrate_async' do
+ it 'calls BackgroundMigrationWorker.perform_async' do
+ expect(BackgroundMigrationWorker).to receive(:perform_async).with("Class", "hello", "world")
+
+ model.migrate_async("Class", "hello", "world")
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.migrate_async('Class', 'hello', 'world')
+ end
+ end
+
+ describe '#migrate_in' do
+ it 'calls BackgroundMigrationWorker.perform_in' do
+ expect(BackgroundMigrationWorker).to receive(:perform_in).with(10.minutes, 'Class', 'Hello', 'World')
+
+ model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.migrate_in(10.minutes, 'Class', 'Hello', 'World')
+ end
+ end
+
+ describe '#bulk_migrate_async' do
+ it 'calls BackgroundMigrationWorker.bulk_perform_async' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_async).with([%w(Class hello world)])
+
+ model.bulk_migrate_async([%w(Class hello world)])
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.bulk_migrate_async([%w(Class hello world)])
+ end
+ end
+
+ describe '#bulk_migrate_in' do
+ it 'calls BackgroundMigrationWorker.bulk_perform_in_' do
+ expect(BackgroundMigrationWorker).to receive(:bulk_perform_in).with(10.minutes, [%w(Class hello world)])
+
+ model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
+ end
+
+ it 'pushes a context with the current class name as caller_id' do
+ expect(Gitlab::ApplicationContext).to receive(:with_context).with(caller_id: model.class.to_s)
+
+ model.bulk_migrate_in(10.minutes, [%w(Class hello world)])
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
new file mode 100644
index 00000000000..0523066b593
--- /dev/null
+++ b/spec/lib/gitlab/database/postgresql_adapter/force_disconnectable_mixin_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin do
+ describe 'checking in a connection to the pool' do
+ let(:model) do
+ Class.new(ActiveRecord::Base) do
+ self.abstract_class = true
+
+ def self.name
+ 'ForceDisconnectTestModel'
+ end
+ end
+ end
+ let(:config) { Rails.application.config_for(:database).merge(pool: 1) }
+ let(:pool) { model.establish_connection(config) }
+
+ it 'calls the force disconnect callback on checkin' do
+ connection = pool.connection
+
+ expect(pool.active_connection?).to be_truthy
+ expect(connection).to receive(:force_disconnect_if_old!).and_call_original
+
+ model.clear_active_connections!
+ end
+ end
+
+ describe 'disconnecting from the database' do
+ let(:connection) { ActiveRecord::Base.connection_pool.connection }
+ let(:timer) { connection.force_disconnect_timer }
+
+ context 'when the timer is expired' do
+ it 'disconnects from the database' do
+ allow(timer).to receive(:expired?).and_return(true)
+
+ expect(connection).to receive(:disconnect!).and_call_original
+ expect(timer).to receive(:reset!).and_call_original
+
+ connection.force_disconnect_if_old!
+ end
+ end
+
+ context 'when the timer is not expired' do
+ it 'does not disconnect from the database' do
+ allow(timer).to receive(:expired?).and_return(false)
+
+ expect(connection).not_to receive(:disconnect!)
+ expect(timer).not_to receive(:reset!)
+
+ connection.force_disconnect_if_old!
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index 0b8f64e97a1..923f620a81d 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -50,7 +50,7 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
end
end
- context 'with application settings and admin users' do
+ context 'with application settings and admin users', :request_store do
let(:project) { result[:project] }
let(:group) { result[:group] }
let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
@@ -58,8 +58,9 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
let!(:user) { create(:user, :admin) }
before do
- allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
- application_setting.allow_local_requests_from_web_hooks_and_services = true
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+
+ application_setting.update(allow_local_requests_from_web_hooks_and_services: true)
end
shared_examples 'has prometheus service' do |listen_address|
@@ -130,12 +131,17 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
it 'saves the project id' do
expect(result[:status]).to eq(:success)
- expect(application_setting.self_monitoring_project_id).to eq(project.id)
+ expect(application_setting.reload.self_monitoring_project_id).to eq(project.id)
end
- it 'expires application_setting cache' do
- expect(Gitlab::CurrentSettings).to receive(:expire_current_application_settings)
+ it 'creates a Prometheus service' do
expect(result[:status]).to eq(:success)
+
+ services = result[:project].reload.services
+
+ expect(services.count).to eq(1)
+ # Ensures PrometheusService#self_monitoring_project? is true
+ expect(services.first.allow_local_api_url?).to be_truthy
end
it 'creates an environment for the project' do
@@ -158,8 +164,8 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
end
it 'returns error when saving project ID fails' do
- allow(application_setting).to receive(:update).and_call_original
- allow(application_setting).to receive(:update)
+ allow(subject.application_settings).to receive(:update).and_call_original
+ allow(subject.application_settings).to receive(:update)
.with(self_monitoring_project_id: anything)
.and_return(false)
@@ -175,8 +181,8 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
let(:existing_project) { create(:project, namespace: existing_group) }
before do
- application_setting.instance_administrators_group_id = existing_group.id
- application_setting.self_monitoring_project_id = existing_project.id
+ application_setting.update(instance_administrators_group_id: existing_group.id,
+ self_monitoring_project_id: existing_project.id)
end
it 'returns success' do
@@ -189,7 +195,7 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
context 'when local requests from hooks and services are not allowed' do
before do
- application_setting.allow_local_requests_from_web_hooks_and_services = false
+ application_setting.update(allow_local_requests_from_web_hooks_and_services: false)
end
it_behaves_like 'has prometheus service', 'http://localhost:9090'
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index 218c393c409..a16e5e185bb 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -53,26 +53,22 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
fallback_diff_refs: diffs.fallback_diff_refs)
end
- it 'does not calculate highlighting when reading from cache' do
+ before do
cache.write_if_empty
cache.decorate(diff_file)
+ end
+ it 'does not calculate highlighting when reading from cache' do
expect_any_instance_of(Gitlab::Diff::Highlight).not_to receive(:highlight)
diff_file.highlighted_diff_lines
end
it 'assigns highlighted diff lines to the DiffFile' do
- cache.write_if_empty
- cache.decorate(diff_file)
-
expect(diff_file.highlighted_diff_lines.size).to be > 5
end
it 'assigns highlighted diff lines which rich_text are HTML-safe' do
- cache.write_if_empty
- cache.decorate(diff_file)
-
rich_texts = diff_file.highlighted_diff_lines.map(&:rich_text)
expect(rich_texts).to all(be_html_safe)
@@ -101,6 +97,28 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
let(:paths) { merge_request.diffs.raw_diff_files.select(&:text?).map(&:file_path) }
end
+ it 'updates memory usage metrics if Redis version >= 4' do
+ allow_next_instance_of(Redis) do |redis|
+ allow(redis).to receive(:info).and_return({ "redis_version" => "4.0.0" })
+
+ expect(described_class.gitlab_redis_diff_caching_memory_usage_bytes)
+ .to receive(:observe).and_call_original
+
+ cache.send(:write_to_redis_hash, diff_hash)
+ end
+ end
+
+ it 'does not update memory usage metrics if Redis version < 4' do
+ allow_next_instance_of(Redis) do |redis|
+ allow(redis).to receive(:info).and_return({ "redis_version" => "3.0.0" })
+
+ expect(described_class.gitlab_redis_diff_caching_memory_usage_bytes)
+ .not_to receive(:observe).and_call_original
+
+ cache.send(:write_to_redis_hash, diff_hash)
+ end
+ end
+
context 'different diff_collections for the same diffable' do
before do
cache.write_if_empty
@@ -149,5 +167,13 @@ describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
it 'defines :gitlab_redis_diff_caching_memory_usage_bytes histogram' do
expect(described_class).to respond_to(:gitlab_redis_diff_caching_memory_usage_bytes)
end
+
+ it 'defines :gitlab_redis_diff_caching_hit' do
+ expect(described_class).to respond_to(:gitlab_redis_diff_caching_hit)
+ end
+
+ it 'defines :gitlab_redis_diff_caching_miss' do
+ expect(described_class).to respond_to(:gitlab_redis_diff_caching_miss)
+ end
end
end
diff --git a/spec/lib/gitlab/elasticsearch/logs_spec.rb b/spec/lib/gitlab/elasticsearch/logs_spec.rb
new file mode 100644
index 00000000000..f82c4acb82b
--- /dev/null
+++ b/spec/lib/gitlab/elasticsearch/logs_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Elasticsearch::Logs do
+ let(:client) { Elasticsearch::Transport::Client }
+
+ let(:es_message_1) { { timestamp: "2019-12-13T14:35:34.034Z", message: "10.8.2.1 - - [25/Oct/2019:08:03:22 UTC] \"GET / HTTP/1.1\" 200 13" } }
+ let(:es_message_2) { { timestamp: "2019-12-13T14:35:35.034Z", message: "10.8.2.1 - - [27/Oct/2019:23:49:54 UTC] \"GET / HTTP/1.1\" 200 13" } }
+ let(:es_message_3) { { timestamp: "2019-12-13T14:35:36.034Z", message: "10.8.2.1 - - [04/Nov/2019:23:09:24 UTC] \"GET / HTTP/1.1\" 200 13" } }
+ let(:es_message_4) { { timestamp: "2019-12-13T14:35:37.034Z", message: "- -\u003e /" } }
+
+ let(:es_response) { JSON.parse(fixture_file('lib/elasticsearch/logs_response.json')) }
+
+ subject { described_class.new(client) }
+
+ let(:namespace) { "autodevops-deploy-9-production" }
+ let(:pod_name) { "production-6866bc8974-m4sk4" }
+ let(:container_name) { "auto-deploy-app" }
+ let(:search) { "foo +bar "}
+ let(:start_time) { "2019-12-13T14:35:34.034Z" }
+ let(:end_time) { "2019-12-13T14:35:34.034Z" }
+ let(:cursor) { "9999934,1572449784442" }
+
+ let(:body) { JSON.parse(fixture_file('lib/elasticsearch/query.json')) }
+ let(:body_with_container) { JSON.parse(fixture_file('lib/elasticsearch/query_with_container.json')) }
+ let(:body_with_search) { JSON.parse(fixture_file('lib/elasticsearch/query_with_search.json')) }
+ let(:body_with_times) { JSON.parse(fixture_file('lib/elasticsearch/query_with_times.json')) }
+ let(:body_with_start_time) { JSON.parse(fixture_file('lib/elasticsearch/query_with_start_time.json')) }
+ let(:body_with_end_time) { JSON.parse(fixture_file('lib/elasticsearch/query_with_end_time.json')) }
+ let(:body_with_cursor) { JSON.parse(fixture_file('lib/elasticsearch/query_with_cursor.json')) }
+
+ RSpec::Matchers.define :a_hash_equal_to_json do |expected|
+ match do |actual|
+ actual.as_json == expected
+ end
+ end
+
+ describe '#pod_logs' do
+ it 'returns the logs as an array' do
+ expect(client).to receive(:search).with(body: a_hash_equal_to_json(body)).and_return(es_response)
+
+ result = subject.pod_logs(namespace, pod_name)
+ expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
+ end
+
+ it 'can further filter the logs by container name' do
+ expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_container)).and_return(es_response)
+
+ result = subject.pod_logs(namespace, pod_name, container_name: container_name)
+ expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
+ end
+
+ it 'can further filter the logs by search' do
+ expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_search)).and_return(es_response)
+
+ result = subject.pod_logs(namespace, pod_name, search: search)
+ expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
+ end
+
+ it 'can further filter the logs by start_time and end_time' do
+ expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_times)).and_return(es_response)
+
+ result = subject.pod_logs(namespace, pod_name, start_time: start_time, end_time: end_time)
+ expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
+ end
+
+ it 'can further filter the logs by only start_time' do
+ expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_start_time)).and_return(es_response)
+
+ result = subject.pod_logs(namespace, pod_name, start_time: start_time)
+ expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
+ end
+
+ it 'can further filter the logs by only end_time' do
+ expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_end_time)).and_return(es_response)
+
+ result = subject.pod_logs(namespace, pod_name, end_time: end_time)
+ expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
+ end
+
+ it 'can search after a cursor' do
+ expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_cursor)).and_return(es_response)
+
+ result = subject.pod_logs(namespace, pod_name, cursor: cursor)
+ expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index d091b6c1601..e6dfd8728aa 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -128,6 +128,12 @@ describe Gitlab::EncodingHelper do
expect { ext_class.encode_utf8('') }.not_to raise_error
end
+ it 'replaces invalid and undefined chars with the replace argument' do
+ str = 'hællo'.encode(Encoding::UTF_16LE).force_encoding(Encoding::ASCII_8BIT)
+
+ expect(ext_class.encode_utf8(str, replace: "\u{FFFD}")).to eq("h�llo")
+ end
+
context 'with strings that can be forcefully encoded into utf8' do
let(:test_string) do
"refs/heads/FixSymbolsTitleDropdown".encode("ASCII-8BIT")
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index 1506794cbb5..a39c50ab038 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -30,7 +30,12 @@ describe Gitlab::Experimentation do
end
describe '#set_experimentation_subject_id_cookie' do
+ let(:do_not_track) { nil }
+ let(:cookie) { cookies.permanent.signed[:experimentation_subject_id] }
+
before do
+ request.headers['DNT'] = do_not_track if do_not_track.present?
+
get :index
end
@@ -46,12 +51,30 @@ describe Gitlab::Experimentation do
context 'cookie is not present' do
it 'sets a permanent signed cookie' do
- expect(cookies.permanent.signed[:experimentation_subject_id]).to be_present
+ expect(cookie).to be_present
+ end
+
+ context 'DNT: 0' do
+ let(:do_not_Track) { '0' }
+
+ it 'sets a permanent signed cookie' do
+ expect(cookie).to be_present
+ end
+ end
+
+ context 'DNT: 1' do
+ let(:do_not_track) { '1' }
+
+ it 'does nothing' do
+ expect(cookie).not_to be_present
+ end
end
end
end
describe '#experiment_enabled?' do
+ subject { controller.experiment_enabled?(:test_experiment) }
+
context 'cookie is not present' do
it 'calls Gitlab::Experimentation.enabled_for_user? with the name of the experiment and an experimentation_subject_index of nil' do
expect(Gitlab::Experimentation).to receive(:enabled_for_user?).with(:test_experiment, nil)
@@ -72,11 +95,25 @@ describe Gitlab::Experimentation do
end
end
+ it 'returns true when DNT: 0 is set in the request' do
+ allow(Gitlab::Experimentation).to receive(:enabled_for_user?) { true }
+ controller.request.headers['DNT'] = '0'
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns false when DNT: 1 is set in the request' do
+ allow(Gitlab::Experimentation).to receive(:enabled_for_user?) { true }
+ controller.request.headers['DNT'] = '1'
+
+ is_expected.to be_falsy
+ end
+
describe 'URL parameter to force enable experiment' do
- it 'returns true' do
+ it 'returns true unconditionally' do
get :index, params: { force_experiment: :test_experiment }
- expect(controller.experiment_enabled?(:test_experiment)).to be_truthy
+ is_expected.to be_truthy
end
end
end
diff --git a/spec/lib/gitlab/file_type_detection_spec.rb b/spec/lib/gitlab/file_type_detection_spec.rb
index 05008bf895c..2f1fc57c559 100644
--- a/spec/lib/gitlab/file_type_detection_spec.rb
+++ b/spec/lib/gitlab/file_type_detection_spec.rb
@@ -2,6 +2,35 @@
require 'spec_helper'
describe Gitlab::FileTypeDetection do
+ describe '.extension_match?' do
+ let(:extensions) { %w[foo bar] }
+
+ it 'returns false when filename is blank' do
+ expect(described_class.extension_match?(nil, extensions)).to eq(false)
+ expect(described_class.extension_match?('', extensions)).to eq(false)
+ end
+
+ it 'returns true when filename matches extensions' do
+ expect(described_class.extension_match?('file.foo', extensions)).to eq(true)
+ expect(described_class.extension_match?('file.bar', extensions)).to eq(true)
+ end
+
+ it 'returns false when filename does not match extensions' do
+ expect(described_class.extension_match?('file.baz', extensions)).to eq(false)
+ end
+
+ it 'can match case insensitive filenames' do
+ expect(described_class.extension_match?('file.FOO', extensions)).to eq(true)
+ end
+
+ it 'can match filenames with periods' do
+ expect(described_class.extension_match?('my.file.foo', extensions)).to eq(true)
+ end
+
+ it 'can match filenames with directories' do
+ expect(described_class.extension_match?('my/file.foo', extensions)).to eq(true)
+ end
+ end
context 'when class is an uploader' do
let(:uploader) do
example_uploader = Class.new(CarrierWave::Uploader::Base) do
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 294e67a19d4..9c2f0e910b1 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -12,10 +12,34 @@ describe Gitlab::Git::Blob, :seed_helper do
let(:blob) { Gitlab::Git::Blob.new(name: 'test') }
it 'handles nil data' do
+ expect(described_class).not_to receive(:gitlab_blob_size)
+
expect(blob.name).to eq('test')
expect(blob.size).to eq(nil)
expect(blob.loaded_size).to eq(nil)
end
+
+ it 'records blob size' do
+ expect(described_class).to receive(:gitlab_blob_size).and_call_original
+
+ Gitlab::Git::Blob.new(name: 'test', size: 4, data: 'abcd')
+ end
+
+ context 'when untruncated' do
+ it 'attempts to record gitlab_blob_truncated_false' do
+ expect(described_class).to receive(:gitlab_blob_truncated_false).and_call_original
+
+ Gitlab::Git::Blob.new(name: 'test', size: 4, data: 'abcd')
+ end
+ end
+
+ context 'when truncated' do
+ it 'attempts to record gitlab_blob_truncated_true' do
+ expect(described_class).to receive(:gitlab_blob_truncated_true).and_call_original
+
+ Gitlab::Git::Blob.new(name: 'test', size: 40, data: 'abcd')
+ end
+ end
end
shared_examples '.find' do
@@ -589,6 +613,40 @@ describe Gitlab::Git::Blob, :seed_helper do
end
end
+ describe '#truncated?' do
+ context 'when blob.size is nil' do
+ let(:nil_size_blob) { Gitlab::Git::Blob.new(name: 'test', data: 'abcd') }
+
+ it 'returns false' do
+ expect(nil_size_blob.truncated?).to be_falsey
+ end
+ end
+
+ context 'when blob.data is missing' do
+ let(:nil_data_blob) { Gitlab::Git::Blob.new(name: 'test', size: 4) }
+
+ it 'returns false' do
+ expect(nil_data_blob.truncated?).to be_falsey
+ end
+ end
+
+ context 'when the blob is truncated' do
+ let(:truncated_blob) { Gitlab::Git::Blob.new(name: 'test', size: 40, data: 'abcd') }
+
+ it 'returns true' do
+ expect(truncated_blob.truncated?).to be_truthy
+ end
+ end
+
+ context 'when the blob is untruncated' do
+ let(:untruncated_blob) { Gitlab::Git::Blob.new(name: 'test', size: 4, data: 'abcd') }
+
+ it 'returns false' do
+ expect(untruncated_blob.truncated?).to be_falsey
+ end
+ end
+ end
+
describe 'metrics' do
it 'defines :gitlab_blob_truncated_true counter' do
expect(described_class).to respond_to(:gitlab_blob_truncated_true)
@@ -597,5 +655,9 @@ describe Gitlab::Git::Blob, :seed_helper do
it 'defines :gitlab_blob_truncated_false counter' do
expect(described_class).to respond_to(:gitlab_blob_truncated_false)
end
+
+ it 'defines :gitlab_blob_size histogram' do
+ expect(described_class).to respond_to(:gitlab_blob_size)
+ end
end
end
diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb
index a99e8c4f60c..adc63401b89 100644
--- a/spec/lib/gitlab/git/lfs_changes_spec.rb
+++ b/spec/lib/gitlab/git/lfs_changes_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::Git::LfsChanges do
- set(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
let(:blob_object_id) { '0c304a93cb8430108629bbbcaa27db3343299bc0' }
diff --git a/spec/lib/gitlab/git/merge_base_spec.rb b/spec/lib/gitlab/git/merge_base_spec.rb
index fa95a1664ea..d92b13c5023 100644
--- a/spec/lib/gitlab/git/merge_base_spec.rb
+++ b/spec/lib/gitlab/git/merge_base_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::Git::MergeBase do
- set(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
subject(:merge_base) { described_class.new(repository, refs) }
diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb
index 32c4c1c82d4..c09e8a085df 100644
--- a/spec/lib/gitlab/git/push_spec.rb
+++ b/spec/lib/gitlab/git/push_spec.rb
@@ -3,8 +3,7 @@
require 'spec_helper'
describe Gitlab::Git::Push do
- set(:project) { create(:project, :repository) }
-
+ let_it_be(:project) { create(:project, :repository) }
let(:oldrev) { project.commit('HEAD~2').id }
let(:newrev) { project.commit.id }
let(:ref) { 'refs/heads/some-branch' }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index a9d7beb0fea..b706cad612a 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -492,50 +492,6 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- describe '#fetch_repository_as_mirror' do
- let(:new_repository) do
- Gitlab::Git::Repository.new('default', 'my_project.git', '', 'group/project')
- end
-
- subject { new_repository.fetch_repository_as_mirror(repository) }
-
- before do
- Gitlab::Shell.new.create_repository('default', 'my_project', 'group/project')
- end
-
- after do
- Gitlab::Shell.new.remove_repository('default', 'my_project')
- end
-
- it 'fetches a repository as a mirror remote' do
- subject
-
- expect(refs(new_repository_path)).to eq(refs(repository_path))
- end
-
- context 'with keep-around refs' do
- let(:sha) { SeedRepo::Commit::ID }
- let(:keep_around_ref) { "refs/keep-around/#{sha}" }
- let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
-
- before do
- repository_rugged.references.create(keep_around_ref, sha, force: true)
- repository_rugged.references.create(tmp_ref, sha, force: true)
- end
-
- it 'includes the temporary and keep-around refs' do
- subject
-
- expect(refs(new_repository_path)).to include(keep_around_ref)
- expect(refs(new_repository_path)).to include(tmp_ref)
- end
- end
-
- def new_repository_path
- File.join(TestEnv.repos_path, new_repository.relative_path)
- end
- end
-
describe '#fetch_remote' do
it 'delegates to the gitaly RepositoryService' do
ssh_auth = double(:ssh_auth)
@@ -2181,4 +2137,76 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
end
+
+ describe '#import_repository' do
+ let_it_be(:project) { create(:project) }
+
+ let(:repository) { project.repository }
+ let(:url) { 'http://invalid.invalid' }
+
+ it 'raises an error if a relative path is provided' do
+ expect { repository.import_repository('/foo') }.to raise_error(ArgumentError, /disk path/)
+ end
+
+ it 'raises an error if an absolute path is provided' do
+ expect { repository.import_repository('./foo') }.to raise_error(ArgumentError, /disk path/)
+ end
+
+ it 'delegates to Gitaly' do
+ expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |svc|
+ expect(svc).to receive(:import_repository).with(url).and_return(nil)
+ end
+
+ repository.import_repository(url)
+ end
+
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :import_repository do
+ subject { repository.import_repository('http://invalid.invalid') }
+ end
+ end
+
+ describe '#replicate' do
+ let(:new_repository) do
+ Gitlab::Git::Repository.new('test_second_storage', TEST_REPO_PATH, '', 'group/project')
+ end
+ let(:new_repository_path) { File.join(TestEnv::SECOND_STORAGE_PATH, new_repository.relative_path) }
+
+ subject { new_repository.replicate(repository) }
+
+ before do
+ stub_storage_settings('test_second_storage' => {
+ 'gitaly_address' => Gitlab.config.repositories.storages.default.gitaly_address,
+ 'path' => TestEnv::SECOND_STORAGE_PATH
+ })
+ new_repository.create_repository
+ end
+
+ after do
+ new_repository.remove
+ end
+
+ it 'mirrors the source repository' do
+ subject
+
+ expect(refs(new_repository_path)).to eq(refs(repository_path))
+ end
+
+ context 'with keep-around refs' do
+ let(:sha) { SeedRepo::Commit::ID }
+ let(:keep_around_ref) { "refs/keep-around/#{sha}" }
+ let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
+
+ before do
+ repository.write_ref(keep_around_ref, sha)
+ repository.write_ref(tmp_ref, sha)
+ end
+
+ it 'includes the temporary and keep-around refs' do
+ subject
+
+ expect(refs(new_repository_path)).to include(keep_around_ref)
+ expect(refs(new_repository_path)).to include(tmp_ref)
+ end
+ end
+ end
end
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 300d7bb14b6..b396e5d22c3 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -152,6 +152,43 @@ describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
end
end
+ describe '#rugged_enabled_through_feature_flag?' do
+ subject { wrapper.send(:rugged_enabled_through_feature_flag?) }
+
+ before do
+ allow(Feature).to receive(:enabled?).with(:feature_key_1).and_return(true)
+ allow(Feature).to receive(:enabled?).with(:feature_key_2).and_return(true)
+ allow(Feature).to receive(:enabled?).with(:feature_key_3).and_return(false)
+ allow(Feature).to receive(:enabled?).with(:feature_key_4).and_return(false)
+
+ stub_const('Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS', feature_keys)
+ end
+
+ context 'no feature keys given' do
+ let(:feature_keys) { [] }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'all features are enabled' do
+ let(:feature_keys) { [:feature_key_1, :feature_key_2] }
+
+ it { is_expected.to be_truthy}
+ end
+
+ context 'all features are not enabled' do
+ let(:feature_keys) { [:feature_key_3, :feature_key_4] }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'some feature is enabled' do
+ let(:feature_keys) { [:feature_key_4, :feature_key_2] }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
def create_temporary_gitaly_metadata_file
tmp = Tempfile.new('.gitaly-metadata')
gitaly_metadata = {
diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb
index ffb3d86408a..f52fe8ef612 100644
--- a/spec/lib/gitlab/git_access_snippet_spec.rb
+++ b/spec/lib/gitlab/git_access_snippet_spec.rb
@@ -3,83 +3,225 @@
require 'spec_helper'
describe Gitlab::GitAccessSnippet do
- include GitHelpers
+ include ProjectHelpers
+ include TermsHelper
+ include_context 'ProjectPolicyTable context'
+ using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
- let_it_be(:personal_snippet) { create(:personal_snippet, :private, :repository) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:snippet) { create(:project_snippet, :public, :repository, project: project) }
+ let(:actor) { user }
let(:protocol) { 'ssh' }
let(:changes) { Gitlab::GitAccess::ANY }
+ let(:authentication_abilities) { [:download_code, :push_code] }
+
let(:push_access_check) { access.check('git-receive-pack', changes) }
let(:pull_access_check) { access.check('git-upload-pack', changes) }
- let(:snippet) { personal_snippet }
- let(:actor) { personal_snippet.author }
- describe 'when feature flag :version_snippets is enabled' do
- it 'allows push and pull access' do
- aggregate_failures do
- expect { pull_access_check }.not_to raise_error
- expect { push_access_check }.not_to raise_error
- end
+ subject(:access) { Gitlab::GitAccessSnippet.new(actor, snippet, protocol, authentication_abilities: authentication_abilities) }
+
+ describe 'when actor is a DeployKey' do
+ let(:actor) { build(:deploy_key) }
+
+ it 'does not allow push and pull access' do
+ expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:authentication_mechanism])
end
end
describe 'when feature flag :version_snippets is disabled' do
+ let(:user) { snippet.author }
+
before do
stub_feature_flags(version_snippets: false)
end
- it 'does not allow push and pull access' do
- aggregate_failures do
- expect { push_access_check }.to raise_snippet_not_found
- expect { pull_access_check }.to raise_snippet_not_found
- end
+ it 'allows push and pull access' do
+ expect { pull_access_check }.not_to raise_error
+ expect { push_access_check }.not_to raise_error
end
end
describe '#check_snippet_accessibility!' do
context 'when the snippet exists' do
- it 'allows push and pull access' do
- aggregate_failures do
- expect { pull_access_check }.not_to raise_error
- expect { push_access_check }.not_to raise_error
- end
+ it 'allows access' do
+ project.add_developer(actor)
+
+ expect { pull_access_check }.not_to raise_error
end
end
context 'when the snippet is nil' do
let(:snippet) { nil }
- it 'blocks push and pull with "not found"' do
- aggregate_failures do
- expect { pull_access_check }.to raise_snippet_not_found
- expect { push_access_check }.to raise_snippet_not_found
- end
+ it 'blocks access with "not found"' do
+ expect { pull_access_check }.to raise_snippet_not_found
end
end
context 'when the snippet does not have a repository' do
let(:snippet) { build_stubbed(:personal_snippet) }
- it 'blocks push and pull with "not found"' do
- aggregate_failures do
- expect { pull_access_check }.to raise_snippet_not_found
- expect { push_access_check }.to raise_snippet_not_found
+ it 'blocks access with "not found"' do
+ expect { pull_access_check }.to raise_snippet_not_found
+ end
+ end
+ end
+
+ context 'terms are enforced', :aggregate_failures do
+ before do
+ enforce_terms
+ end
+
+ let(:user) { snippet.author }
+
+ it 'blocks access when the user did not accept terms' do
+ message = /must accept the Terms of Service in order to perform this action/
+
+ expect { push_access_check }.to raise_forbidden(message)
+ expect { pull_access_check }.to raise_forbidden(message)
+ end
+
+ it 'allows access when the user accepted the terms' do
+ accept_terms(user)
+
+ expect { push_access_check }.not_to raise_error
+ expect { pull_access_check }.not_to raise_error
+ end
+ end
+
+ context 'project snippet accessibility', :aggregate_failures do
+ let(:snippet) { create(:project_snippet, :private, :repository, project: project) }
+ let(:user) { membership == :author ? snippet.author : create_user_from_membership(project, membership) }
+
+ shared_examples_for 'checks accessibility' do
+ [:anonymous, :non_member, :guest, :reporter, :maintainer, :admin, :author].each do |membership|
+ context membership.to_s do
+ let(:membership) { membership }
+
+ it 'respects accessibility' do
+ if Ability.allowed?(user, :update_snippet, snippet)
+ expect { push_access_check }.not_to raise_error
+ else
+ expect { push_access_check }.to raise_error(described_class::ForbiddenError)
+ end
+
+ if Ability.allowed?(user, :read_snippet, snippet)
+ expect { pull_access_check }.not_to raise_error
+ else
+ expect { pull_access_check }.to raise_error(described_class::ForbiddenError)
+ end
+ end
+ end
+ end
+ end
+
+ context 'when project is public' do
+ it_behaves_like 'checks accessibility'
+ end
+
+ context 'when project is public but snippet feature is private' do
+ let(:project) { create(:project, :public) }
+
+ before do
+ update_feature_access_level(project, :private)
+ end
+
+ it_behaves_like 'checks accessibility'
+ end
+
+ context 'when project is not accessible' do
+ let(:project) { create(:project, :private) }
+
+ [:anonymous, :non_member].each do |membership|
+ context membership.to_s do
+ let(:membership) { membership }
+
+ it 'respects accessibility' do
+ expect { push_access_check }.to raise_error(described_class::NotFoundError)
+ expect { pull_access_check }.to raise_error(described_class::NotFoundError)
+ end
end
end
end
end
- private
+ context 'personal snippet accessibility', :aggregate_failures do
+ let(:snippet) { create(:personal_snippet, snippet_level, :repository) }
+ let(:user) { membership == :author ? snippet.author : create_user_from_membership(nil, membership) }
+
+ where(:snippet_level, :membership, :_expected_count) do
+ permission_table_for_personal_snippet_access
+ end
+
+ with_them do
+ it "respects accessibility" do
+ error_class = described_class::ForbiddenError
+
+ if Ability.allowed?(user, :update_snippet, snippet)
+ expect { push_access_check }.not_to raise_error
+ else
+ expect { push_access_check }.to raise_error(error_class)
+ end
+
+ if Ability.allowed?(user, :read_snippet, snippet)
+ expect { pull_access_check }.not_to raise_error
+ else
+ expect { pull_access_check }.to raise_error(error_class)
+ end
+ end
+ end
+ end
+
+ context 'when geo is enabled', if: Gitlab.ee? do
+ let(:user) { snippet.author }
+ let!(:primary_node) { FactoryBot.create(:geo_node, :primary) }
+
+ # Without override, push access would return Gitlab::GitAccessResult::CustomAction
+ it 'skips geo for snippet' do
+ allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
+ allow(::Gitlab::Geo).to receive(:secondary_with_primary?).and_return(true)
- def access
- described_class.new(actor, snippet, protocol,
- authentication_abilities: [],
- namespace_path: nil, project_path: nil,
- redirected_path: nil, auth_result_type: nil)
+ expect { push_access_check }.to raise_forbidden(/You can't push code to a read-only GitLab instance/)
+ end
end
+ context 'when changes are specific' do
+ let(:changes) { "2d1db523e11e777e49377cfb22d368deec3f0793 ddd0f15ae83993f5cb66a927a28673882e99100b master" }
+ let(:user) { snippet.author }
+
+ it 'does not raise error if SnippetCheck does not raise error' do
+ expect_next_instance_of(Gitlab::Checks::SnippetCheck) do |check|
+ expect(check).to receive(:validate!).and_call_original
+ end
+ expect_next_instance_of(Gitlab::Checks::PushFileCountCheck) do |check|
+ expect(check).to receive(:validate!)
+ end
+
+ expect { push_access_check }.not_to raise_error
+ end
+
+ it 'raises error if SnippetCheck raises error' do
+ expect_next_instance_of(Gitlab::Checks::SnippetCheck) do |check|
+ allow(check).to receive(:validate!).and_raise(Gitlab::GitAccess::ForbiddenError, 'foo')
+ end
+
+ expect { push_access_check }.to raise_forbidden('foo')
+ end
+ end
+
+ private
+
def raise_snippet_not_found
raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:snippet_not_found])
end
+
+ def raise_project_not_found
+ raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found])
+ end
+
+ def raise_forbidden(message)
+ raise_error(Gitlab::GitAccess::ForbiddenError, message)
+ end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 0831021b22b..a29c56c598f 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -32,8 +32,8 @@ describe Gitlab::GitAccess do
it 'blocks ssh git push and pull' do
aggregate_failures do
- expect { push_access_check }.to raise_unauthorized('Git access over SSH is not allowed')
- expect { pull_access_check }.to raise_unauthorized('Git access over SSH is not allowed')
+ expect { push_access_check }.to raise_forbidden('Git access over SSH is not allowed')
+ expect { pull_access_check }.to raise_forbidden('Git access over SSH is not allowed')
end
end
end
@@ -48,8 +48,8 @@ describe Gitlab::GitAccess do
it 'blocks http push and pull' do
aggregate_failures do
- expect { push_access_check }.to raise_unauthorized('Git access over HTTP is not allowed')
- expect { pull_access_check }.to raise_unauthorized('Git access over HTTP is not allowed')
+ expect { push_access_check }.to raise_forbidden('Git access over HTTP is not allowed')
+ expect { pull_access_check }.to raise_forbidden('Git access over HTTP is not allowed')
end
end
@@ -58,7 +58,7 @@ describe Gitlab::GitAccess do
it "doesn't block http pull" do
aggregate_failures do
- expect { pull_access_check }.not_to raise_unauthorized('Git access over HTTP is not allowed')
+ expect { pull_access_check }.not_to raise_forbidden('Git access over HTTP is not allowed')
end
end
@@ -67,7 +67,7 @@ describe Gitlab::GitAccess do
it "doesn't block http pull" do
aggregate_failures do
- expect { pull_access_check }.not_to raise_unauthorized('Git access over HTTP is not allowed')
+ expect { pull_access_check }.not_to raise_forbidden('Git access over HTTP is not allowed')
end
end
end
@@ -165,7 +165,7 @@ describe Gitlab::GitAccess do
end
it 'does not block pushes with "not found"' do
- expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload])
+ expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload])
end
end
@@ -178,7 +178,7 @@ describe Gitlab::GitAccess do
end
it 'blocks the push' do
- expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload])
+ expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:upload])
end
end
@@ -208,7 +208,7 @@ describe Gitlab::GitAccess do
end
it 'does not block pushes with "not found"' do
- expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:upload])
+ expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:upload])
end
end
@@ -240,7 +240,7 @@ describe Gitlab::GitAccess do
let(:access) do
described_class.new(actor, nil,
protocol, authentication_abilities: authentication_abilities,
- project_path: project_path, namespace_path: namespace_path,
+ repository_path: project_path, namespace_path: namespace_path,
redirected_path: redirected_path)
end
@@ -259,7 +259,7 @@ describe Gitlab::GitAccess do
let(:access) do
described_class.new(actor, nil,
protocol, authentication_abilities: authentication_abilities,
- project_path: project_path, namespace_path: namespace_path,
+ repository_path: project_path, namespace_path: namespace_path,
redirected_path: redirected_path)
end
@@ -285,8 +285,8 @@ describe Gitlab::GitAccess do
it 'does not allow keys which are too small', :aggregate_failures do
expect(actor).not_to be_valid
- expect { pull_access_check }.to raise_unauthorized('Your SSH key must be at least 4096 bits.')
- expect { push_access_check }.to raise_unauthorized('Your SSH key must be at least 4096 bits.')
+ 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.')
end
end
@@ -297,8 +297,8 @@ describe Gitlab::GitAccess do
it 'does not allow keys which are too small', :aggregate_failures do
expect(actor).not_to be_valid
- expect { pull_access_check }.to raise_unauthorized(/Your SSH key type is forbidden/)
- expect { push_access_check }.to raise_unauthorized(/Your SSH key type is forbidden/)
+ 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/)
end
end
end
@@ -363,7 +363,7 @@ describe Gitlab::GitAccess do
let(:authentication_abilities) { [] }
it 'raises unauthorized with download error' do
- expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_download])
+ expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_download])
end
context 'when authentication abilities include download code' do
@@ -387,7 +387,7 @@ describe Gitlab::GitAccess do
let(:authentication_abilities) { [] }
it 'raises unauthorized with push error' do
- expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload])
+ expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload])
end
context 'when authentication abilities include push code' do
@@ -414,7 +414,7 @@ describe Gitlab::GitAccess do
end
context 'when calling git-upload-pack' do
- it { expect { pull_access_check }.to raise_unauthorized('Pulling over HTTP is not allowed.') }
+ it { expect { pull_access_check }.to raise_forbidden('Pulling over HTTP is not allowed.') }
end
context 'when calling git-receive-pack' do
@@ -428,7 +428,7 @@ describe Gitlab::GitAccess do
end
context 'when calling git-receive-pack' do
- it { expect { push_access_check }.to raise_unauthorized('Pushing over HTTP is not allowed.') }
+ it { expect { push_access_check }.to raise_forbidden('Pushing over HTTP is not allowed.') }
end
context 'when calling git-upload-pack' do
@@ -445,7 +445,7 @@ describe Gitlab::GitAccess do
allow(Gitlab::Database).to receive(:read_only?) { true }
end
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:cannot_push_to_read_only]) }
+ it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:cannot_push_to_read_only]) }
end
end
@@ -453,7 +453,7 @@ describe Gitlab::GitAccess do
let(:access) do
described_class.new(actor, project,
protocol, authentication_abilities: authentication_abilities,
- project_path: project_path, namespace_path: namespace_path,
+ repository_path: project_path, namespace_path: namespace_path,
redirected_path: redirected_path)
end
@@ -559,21 +559,21 @@ describe Gitlab::GitAccess do
it 'disallows guests to pull' do
project.add_guest(user)
- expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:download])
+ expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:download])
end
it 'disallows blocked users to pull' do
project.add_maintainer(user)
user.block
- expect { pull_access_check }.to raise_unauthorized('Your account has been blocked.')
+ expect { pull_access_check }.to raise_forbidden('Your account has been blocked.')
end
it 'disallows deactivated users to pull' do
project.add_maintainer(user)
user.deactivate!
- expect { pull_access_check }.to raise_unauthorized("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
+ expect { pull_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
end
context 'when the project repository does not exist' do
@@ -598,7 +598,7 @@ describe Gitlab::GitAccess do
let(:public_project) { create(:project, :public, :repository) }
let(:project_path) { public_project.path }
let(:namespace_path) { public_project.namespace.path }
- let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: [:download_code], project_path: project_path, namespace_path: namespace_path) }
+ let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: [:download_code], repository_path: project_path, namespace_path: namespace_path) }
context 'when repository is enabled' do
it 'give access to download code' do
@@ -610,7 +610,7 @@ describe Gitlab::GitAccess do
it 'does not give access to download code' do
public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
- expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:download])
+ expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:download])
end
end
end
@@ -722,7 +722,7 @@ describe Gitlab::GitAccess do
context 'when is not member of the project' do
context 'pull code' do
- it { expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:download]) }
+ it { expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:download]) }
end
end
end
@@ -828,7 +828,7 @@ describe Gitlab::GitAccess do
expect(&check).not_to raise_error,
-> { "expected #{action} to be allowed" }
else
- expect(&check).to raise_error(Gitlab::GitAccess::UnauthorizedError),
+ expect(&check).to raise_error(Gitlab::GitAccess::ForbiddenError),
-> { "expected #{action} to be disallowed" }
end
end
@@ -965,7 +965,7 @@ describe Gitlab::GitAccess do
it 'does not allow deactivated users to push' do
user.deactivate!
- expect { push_access_check }.to raise_unauthorized("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
+ expect { push_access_check }.to raise_forbidden("Your account has been deactivated by your administrator. Please log back in from a web browser to reactivate your account at #{Gitlab.config.gitlab.url}")
end
it 'cleans up the files' do
@@ -1009,26 +1009,26 @@ describe Gitlab::GitAccess do
project.add_reporter(user)
end
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) }
+ it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload]) }
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public, :repository) }
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) }
+ it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload]) }
end
context 'to internal project' do
let(:project) { create(:project, :internal, :repository) }
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) }
+ it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload]) }
end
context 'to private project' do
let(:project) { create(:project, :private, :repository) }
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:auth_upload]) }
+ it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:auth_upload]) }
end
end
end
@@ -1039,7 +1039,7 @@ describe Gitlab::GitAccess do
it 'denies push access' do
project.add_maintainer(user)
- expect { push_access_check }.to raise_unauthorized('The repository is temporarily read-only. Please try again later.')
+ expect { push_access_check }.to raise_forbidden('The repository is temporarily read-only. Please try again later.')
end
end
@@ -1060,7 +1060,7 @@ describe Gitlab::GitAccess do
context 'to public project' do
let(:project) { create(:project, :public, :repository) }
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
+ it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
end
context 'to internal project' do
@@ -1083,14 +1083,14 @@ describe Gitlab::GitAccess do
key.deploy_keys_projects.create(project: project, can_push: false)
end
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
+ it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public, :repository) }
- it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
+ it { expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
end
context 'to internal project' do
@@ -1121,7 +1121,7 @@ describe Gitlab::GitAccess do
it 'blocks access when the user did not accept terms', :aggregate_failures do
actions.each do |action|
- expect { action.call }.to raise_unauthorized(/must accept the Terms of Service in order to perform this action/)
+ expect { action.call }.to raise_forbidden(/must accept the Terms of Service in order to perform this action/)
end
end
@@ -1203,7 +1203,7 @@ describe Gitlab::GitAccess do
def access
described_class.new(actor, project, protocol,
authentication_abilities: authentication_abilities,
- namespace_path: namespace_path, project_path: project_path,
+ namespace_path: namespace_path, repository_path: project_path,
redirected_path: redirected_path, auth_result_type: auth_result_type)
end
@@ -1211,8 +1211,8 @@ describe Gitlab::GitAccess do
access.check('git-receive-pack', changes)
end
- def raise_unauthorized(message)
- raise_error(Gitlab::GitAccess::UnauthorizedError, message)
+ def raise_forbidden(message)
+ raise_error(Gitlab::GitAccess::ForbiddenError, message)
end
def raise_not_found
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 99c9369a2b9..b5e673c9e79 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -33,7 +33,7 @@ describe Gitlab::GitAccessWiki do
end
it 'does not give access to upload wiki code' do
- expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "You can't push code to a read-only GitLab instance.")
+ expect { subject }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You can't push code to a read-only GitLab instance.")
end
end
end
@@ -70,7 +70,7 @@ describe Gitlab::GitAccessWiki do
it 'does not give access to download wiki code' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
- expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to download code from this project.')
+ expect { subject }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You are not allowed to download code from this project.')
end
end
end
diff --git a/spec/lib/gitlab/git_post_receive_spec.rb b/spec/lib/gitlab/git_post_receive_spec.rb
index f0df3794e29..0e25a616810 100644
--- a/spec/lib/gitlab/git_post_receive_spec.rb
+++ b/spec/lib/gitlab/git_post_receive_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe ::Gitlab::GitPostReceive do
- set(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
subject { described_class.new(project, "project-#{project.id}", changes.dup, {}) }
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 5c36d6d35af..00182983418 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -306,5 +306,19 @@ describe Gitlab::GitalyClient::CommitService do
client.find_commits(order: 'topo')
end
+
+ it 'sends an RPC request with an author' do
+ request = Gitaly::FindCommitsRequest.new(
+ repository: repository_message,
+ disable_walk: true,
+ order: 'NONE',
+ author: "Billy Baggins <bilbo@shire.com>"
+ )
+
+ expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
+ .with(request, kind_of(Hash)).and_return([])
+
+ client.find_commits(order: 'default', author: "Billy Baggins <bilbo@shire.com>")
+ end
end
end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index d4337c51279..45701b501bb 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -3,10 +3,10 @@
require 'spec_helper'
describe Gitlab::GitalyClient::OperationService do
- set(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository.raw }
let(:client) { described_class.new(repository) }
- set(:user) { create(:user) }
let(:gitaly_user) { Gitlab::Git::User.from_gitlab(user).to_gitaly }
describe '#user_create_branch' do
@@ -274,7 +274,6 @@ describe Gitlab::GitalyClient::OperationService do
end
describe '#user_squash' do
- let(:branch_name) { 'my-branch' }
let(:squash_id) { '1' }
let(:start_sha) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
let(:end_sha) { '54cec5282aa9f21856362fe321c800c236a61615' }
@@ -284,7 +283,6 @@ describe Gitlab::GitalyClient::OperationService do
repository: repository.gitaly_repository,
user: gitaly_user,
squash_id: squash_id.to_s,
- branch: branch_name,
start_sha: start_sha,
end_sha: end_sha,
author: gitaly_user,
@@ -295,7 +293,7 @@ describe Gitlab::GitalyClient::OperationService do
let(:response) { Gitaly::UserSquashResponse.new(squash_sha: squash_sha) }
subject do
- client.user_squash(user, squash_id, branch_name, start_sha, end_sha, user, commit_message)
+ client.user_squash(user, squash_id, start_sha, end_sha, user, commit_message)
end
it 'sends a user_squash message and returns the squash sha' do
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index 73ae4cd95ce..2658414d9b0 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -34,19 +34,6 @@ describe Gitlab::GitalyClient::RemoteService do
end
end
- describe '#fetch_internal_remote' do
- let(:remote_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project') }
-
- it 'sends an fetch_internal_remote message and returns the result value' do
- expect_any_instance_of(Gitaly::RemoteService::Stub)
- .to receive(:fetch_internal_remote)
- .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
- .and_return(double(result: true))
-
- expect(client.fetch_internal_remote(remote_repository)).to be(true)
- end
- end
-
describe '#find_remote_root_ref' do
it 'sends an find_remote_root_ref message and returns the root ref' do
expect_any_instance_of(Gitaly::RemoteService::Stub)
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 503ac57ade6..5f4147f6ff6 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -275,7 +275,18 @@ describe Gitlab::GitalyClient::RepositoryService do
end
end
- describe 'remove' do
+ describe '#rename' do
+ it 'sends a rename_repository message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:rename_repository)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return(double(value: true))
+
+ client.rename('some/new/path')
+ end
+ end
+
+ describe '#remove' do
it 'sends a remove_repository message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:remove_repository)
@@ -286,14 +297,15 @@ describe Gitlab::GitalyClient::RepositoryService do
end
end
- describe 'rename' do
- it 'sends a rename_repository message' do
+ describe '#replicate' do
+ let(:source_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project') }
+
+ it 'sends a replicate_repository message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
- .to receive(:rename_repository)
+ .to receive(:replicate_repository)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
- .and_return(double(value: true))
- client.rename('some/new/path')
+ client.replicate(source_repository)
end
end
end
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index c65b28fafbf..e26ac7bf81e 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -11,10 +11,15 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
double(
:wiki,
disk_path: 'foo.wiki',
- full_path: 'group/foo.wiki'
+ full_path: 'group/foo.wiki',
+ repository: wiki_repository
)
end
+ let(:wiki_repository) do
+ double(:wiki_repository)
+ end
+
let(:project) do
double(
:project,
@@ -221,17 +226,19 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
describe '#import_wiki_repository' do
it 'imports the wiki repository' do
- expect(importer.gitlab_shell)
+ expect(wiki_repository)
.to receive(:import_repository)
- .with('foo', 'foo.wiki', 'foo.wiki.git', 'group/foo.wiki')
+ .with(importer.wiki_url)
+ .and_return(true)
expect(importer.import_wiki_repository).to eq(true)
end
it 'marks the import as failed and creates an empty repo if an error was raised' do
- expect(importer.gitlab_shell)
+ expect(wiki_repository)
.to receive(:import_repository)
- .and_raise(Gitlab::Shell::Error)
+ .with(importer.wiki_url)
+ .and_raise(Gitlab::Git::CommandError)
expect(importer)
.to receive(:fail_import)
diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
index b8a6feb6c73..55add863d43 100644
--- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do
describe '#cache_database_id' do
it 'caches the ID of a database row' do
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:write)
.with('github-import/issuable-finder/4/MergeRequest/1', 10)
diff --git a/spec/lib/gitlab/github_import/label_finder_spec.rb b/spec/lib/gitlab/github_import/label_finder_spec.rb
index 039ae27ad57..bb946a15a2d 100644
--- a/spec/lib/gitlab/github_import/label_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/label_finder_spec.rb
@@ -21,7 +21,7 @@ describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
it 'returns nil for an empty cache key' do
key = finder.cache_key_for(bug.name)
- Gitlab::GithubImport::Caching.write(key, '')
+ Gitlab::Cache::Import::Caching.write(key, '')
expect(finder.id_for(bug.name)).to be_nil
end
@@ -40,7 +40,7 @@ describe Gitlab::GithubImport::LabelFinder, :clean_gitlab_redis_cache do
describe '#build_cache' do
it 'builds the cache of all project labels' do
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:write_multiple)
.with(
{
diff --git a/spec/lib/gitlab/github_import/milestone_finder_spec.rb b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
index 407e2e67ec9..ecb533b7e39 100644
--- a/spec/lib/gitlab/github_import/milestone_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/milestone_finder_spec.rb
@@ -22,7 +22,7 @@ describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
it 'returns nil for an empty cache key' do
key = finder.cache_key_for(milestone.iid)
- Gitlab::GithubImport::Caching.write(key, '')
+ Gitlab::Cache::Import::Caching.write(key, '')
expect(finder.id_for(issuable)).to be_nil
end
@@ -41,7 +41,7 @@ describe Gitlab::GithubImport::MilestoneFinder, :clean_gitlab_redis_cache do
describe '#build_cache' do
it 'builds the cache of all project milestones' do
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:write_multiple)
.with("github-import/milestone-finder/#{project.id}/1" => milestone.id)
.and_call_original
diff --git a/spec/lib/gitlab/github_import/page_counter_spec.rb b/spec/lib/gitlab/github_import/page_counter_spec.rb
index 87f3ce45fd3..95125c9c22f 100644
--- a/spec/lib/gitlab/github_import/page_counter_spec.rb
+++ b/spec/lib/gitlab/github_import/page_counter_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::GithubImport::PageCounter, :clean_gitlab_redis_cache do
end
it 'sets the initial page number to the cached value when one is present' do
- Gitlab::GithubImport::Caching.write(counter.cache_key, 2)
+ Gitlab::Cache::Import::Caching.write(counter.cache_key, 2)
expect(described_class.new(project, :issues).current).to eq(2)
end
diff --git a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
index f4d107e3dce..a6ae99b395c 100644
--- a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
+++ b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb
@@ -57,7 +57,7 @@ describe Gitlab::GithubImport::ParallelScheduling do
expect(importer).to receive(:parallel_import)
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:expire)
.with(importer.already_imported_cache_key, a_kind_of(Numeric))
@@ -287,7 +287,7 @@ describe Gitlab::GithubImport::ParallelScheduling do
.with(object)
.and_return(object.id)
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:set_add)
.with(importer.already_imported_cache_key, object.id)
.and_call_original
diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb
index 74b5c1c52cd..8764ebef32b 100644
--- a/spec/lib/gitlab/github_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/user_finder_spec.rb
@@ -162,7 +162,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
context 'when an Email address is cached' do
it 'reads the Email address from the cache' do
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:read)
.and_return(email)
@@ -182,7 +182,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
it 'caches the Email address when an Email address is available' do
expect(client).to receive(:user).with('kittens').and_return(user)
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:write)
.with(an_instance_of(String), email)
@@ -195,7 +195,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with('kittens')
.and_return(nil)
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.not_to receive(:write)
expect(finder.email_for_github_username('kittens')).to be_nil
@@ -207,7 +207,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
let(:id) { 4 }
it 'reads a user ID from the cache' do
- Gitlab::GithubImport::Caching
+ Gitlab::Cache::Import::Caching
.write(described_class::ID_CACHE_KEY % id, 4)
expect(finder.cached_id_for_github_id(id)).to eq([true, 4])
@@ -222,7 +222,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
let(:email) { 'kittens@example.com' }
it 'reads a user ID from the cache' do
- Gitlab::GithubImport::Caching
+ Gitlab::Cache::Import::Caching
.write(described_class::ID_FOR_EMAIL_CACHE_KEY % email, 4)
expect(finder.cached_id_for_github_email(email)).to eq([true, 4])
@@ -241,7 +241,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with(id)
.and_return(42)
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:write)
.with(described_class::ID_CACHE_KEY % id, 42)
@@ -253,7 +253,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with(id)
.and_return(nil)
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:write)
.with(described_class::ID_CACHE_KEY % id, nil)
@@ -269,7 +269,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with(email)
.and_return(42)
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:write)
.with(described_class::ID_FOR_EMAIL_CACHE_KEY % email, 42)
@@ -281,7 +281,7 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
.with(email)
.and_return(nil)
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:write)
.with(described_class::ID_FOR_EMAIL_CACHE_KEY % email, nil)
@@ -317,13 +317,13 @@ describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do
describe '#read_id_from_cache' do
it 'reads an ID from the cache' do
- Gitlab::GithubImport::Caching.write('foo', 10)
+ Gitlab::Cache::Import::Caching.write('foo', 10)
expect(finder.read_id_from_cache('foo')).to eq([true, 10])
end
it 'reads a cache key with an empty value' do
- Gitlab::GithubImport::Caching.write('foo', nil)
+ Gitlab::Cache::Import::Caching.write('foo', nil)
expect(finder.read_id_from_cache('foo')).to eq([true, nil])
end
diff --git a/spec/lib/gitlab/github_import_spec.rb b/spec/lib/gitlab/github_import_spec.rb
index c3ddac01c87..290d66243aa 100644
--- a/spec/lib/gitlab/github_import_spec.rb
+++ b/spec/lib/gitlab/github_import_spec.rb
@@ -35,7 +35,7 @@ describe Gitlab::GithubImport do
end
it 'caches the ghost user ID' do
- expect(Gitlab::GithubImport::Caching)
+ expect(Gitlab::Cache::Import::Caching)
.to receive(:write)
.once
.and_call_original
diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
index 7cf0442fbe1..6185b068d4c 100644
--- a/spec/lib/gitlab/gl_repository/repo_type_spec.rb
+++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
@@ -5,46 +5,62 @@ describe Gitlab::GlRepository::RepoType do
let_it_be(:project) { create(:project) }
let_it_be(:personal_snippet) { create(:personal_snippet, author: project.owner) }
let_it_be(:project_snippet) { create(:project_snippet, project: project, author: project.owner) }
+ let(:project_path) { project.repository.full_path }
+ let(:wiki_path) { project.wiki.repository.full_path }
+ let(:personal_snippet_path) { "snippets/#{personal_snippet.id}" }
+ let(:project_snippet_path) { "#{project.full_path}/snippets/#{project_snippet.id}" }
describe Gitlab::GlRepository::PROJECT do
it_behaves_like 'a repo type' do
- let(:expected_identifier) { "project-#{project.id}" }
let(:expected_id) { project.id.to_s }
+ let(:expected_identifier) { "project-#{expected_id}" }
let(:expected_suffix) { '' }
- let(:expected_repository) { project.repository }
let(:expected_container) { project }
+ let(:expected_repository) { expected_container.repository }
end
it 'knows its type' do
- expect(described_class).not_to be_wiki
- expect(described_class).to be_project
- expect(described_class).not_to be_snippet
+ aggregate_failures do
+ expect(described_class).not_to be_wiki
+ expect(described_class).to be_project
+ expect(described_class).not_to be_snippet
+ end
end
it 'checks if repository path is valid' do
- expect(described_class.valid?(project.repository.full_path)).to be_truthy
- expect(described_class.valid?(project.wiki.repository.full_path)).to be_truthy
+ aggregate_failures do
+ expect(described_class.valid?(project_path)).to be_truthy
+ expect(described_class.valid?(wiki_path)).to be_truthy
+ expect(described_class.valid?(personal_snippet_path)).to be_truthy
+ expect(described_class.valid?(project_snippet_path)).to be_truthy
+ end
end
end
describe Gitlab::GlRepository::WIKI do
it_behaves_like 'a repo type' do
- let(:expected_identifier) { "wiki-#{project.id}" }
let(:expected_id) { project.id.to_s }
+ let(:expected_identifier) { "wiki-#{expected_id}" }
let(:expected_suffix) { '.wiki' }
- let(:expected_repository) { project.wiki.repository }
let(:expected_container) { project }
+ let(:expected_repository) { expected_container.wiki.repository }
end
it 'knows its type' do
- expect(described_class).to be_wiki
- expect(described_class).not_to be_project
- expect(described_class).not_to be_snippet
+ aggregate_failures do
+ expect(described_class).to be_wiki
+ expect(described_class).not_to be_project
+ expect(described_class).not_to be_snippet
+ end
end
it 'checks if repository path is valid' do
- expect(described_class.valid?(project.repository.full_path)).to be_falsey
- expect(described_class.valid?(project.wiki.repository.full_path)).to be_truthy
+ aggregate_failures do
+ expect(described_class.valid?(project_path)).to be_falsey
+ expect(described_class.valid?(wiki_path)).to be_truthy
+ expect(described_class.valid?(personal_snippet_path)).to be_falsey
+ expect(described_class.valid?(project_snippet_path)).to be_falsey
+ end
end
end
@@ -59,9 +75,20 @@ describe Gitlab::GlRepository::RepoType do
end
it 'knows its type' do
- expect(described_class).to be_snippet
- expect(described_class).not_to be_wiki
- expect(described_class).not_to be_project
+ aggregate_failures do
+ expect(described_class).to be_snippet
+ expect(described_class).not_to be_wiki
+ expect(described_class).not_to be_project
+ end
+ end
+
+ it 'checks if repository path is valid' do
+ aggregate_failures do
+ expect(described_class.valid?(project_path)).to be_falsey
+ expect(described_class.valid?(wiki_path)).to be_falsey
+ expect(described_class.valid?(personal_snippet_path)).to be_truthy
+ expect(described_class.valid?(project_snippet_path)).to be_truthy
+ end
end
end
@@ -75,9 +102,20 @@ describe Gitlab::GlRepository::RepoType do
end
it 'knows its type' do
- expect(described_class).to be_snippet
- expect(described_class).not_to be_wiki
- expect(described_class).not_to be_project
+ aggregate_failures do
+ expect(described_class).to be_snippet
+ expect(described_class).not_to be_wiki
+ expect(described_class).not_to be_project
+ end
+ end
+
+ it 'checks if repository path is valid' do
+ aggregate_failures do
+ expect(described_class.valid?(project_path)).to be_falsey
+ expect(described_class.valid?(wiki_path)).to be_falsey
+ expect(described_class.valid?(personal_snippet_path)).to be_truthy
+ expect(described_class.valid?(project_snippet_path)).to be_truthy
+ end
end
end
end
diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb
index 3cfc4c2a132..858f436047e 100644
--- a/spec/lib/gitlab/gl_repository_spec.rb
+++ b/spec/lib/gitlab/gl_repository_spec.rb
@@ -5,13 +5,18 @@ require 'spec_helper'
describe ::Gitlab::GlRepository do
describe '.parse' do
let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:snippet) { create(:personal_snippet) }
it 'parses a project gl_repository' do
- expect(described_class.parse("project-#{project.id}")).to eq([project, Gitlab::GlRepository::PROJECT])
+ expect(described_class.parse("project-#{project.id}")).to eq([project, project, Gitlab::GlRepository::PROJECT])
end
it 'parses a wiki gl_repository' do
- expect(described_class.parse("wiki-#{project.id}")).to eq([project, Gitlab::GlRepository::WIKI])
+ expect(described_class.parse("wiki-#{project.id}")).to eq([project, project, Gitlab::GlRepository::WIKI])
+ end
+
+ it 'parses a snippet gl_repository' do
+ expect(described_class.parse("snippet-#{snippet.id}")).to eq([snippet, nil, Gitlab::GlRepository::SNIPPET])
end
it 'throws an argument error on an invalid gl_repository type' do
diff --git a/spec/lib/gitlab/global_id_spec.rb b/spec/lib/gitlab/global_id_spec.rb
index d35b5da0b75..719743ed5dc 100644
--- a/spec/lib/gitlab/global_id_spec.rb
+++ b/spec/lib/gitlab/global_id_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
describe Gitlab::GlobalId do
describe '.build' do
- set(:object) { create(:issue) }
+ let_it_be(:object) { create(:issue) }
it 'returns a standard GlobalId if only object is passed' do
expect(described_class.build(object).to_s).to eq(object.to_global_id.to_s)
diff --git a/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb b/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb
new file mode 100644
index 00000000000..d3b108f60ff
--- /dev/null
+++ b/spec/lib/gitlab/grape_logging/formatters/lograge_with_timestamp_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp do
+ let(:log_entry) do
+ {
+ status: 200,
+ time: {
+ total: 758.58,
+ db: 77.06,
+ view: 681.52
+ },
+ method: 'PUT',
+ path: '/api/v4/projects/1',
+ params: {
+ 'description': '[FILTERED]',
+ 'name': 'gitlab test'
+ },
+ host: 'localhost',
+ remote_ip: '127.0.0.1',
+ ua: 'curl/7.66.0',
+ route: '/api/:version/projects/:id',
+ user_id: 1,
+ username: 'root',
+ queue_duration: 1764.06,
+ gitaly_calls: 6,
+ gitaly_duration: 20.0,
+ correlation_id: 'WMefXn60429'
+ }
+ end
+ let(:time) { Time.now }
+ let(:result) { JSON.parse(subject) }
+
+ subject { described_class.new.call(:info, time, nil, log_entry) }
+
+ it 'turns the log entry to valid JSON' do
+ expect(result['status']).to eq(200)
+ end
+
+ it 're-formats the params hash' do
+ params = result['params']
+
+ expect(params).to eq([
+ { 'key' => 'description', 'value' => '[FILTERED]' },
+ { 'key' => 'name', 'value' => 'gitlab test' }
+ ])
+ end
+end
diff --git a/spec/lib/gitlab/graphql/docs/renderer_spec.rb b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
new file mode 100644
index 00000000000..5ba70bb8f0a
--- /dev/null
+++ b/spec/lib/gitlab/graphql/docs/renderer_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Docs::Renderer do
+ describe '#contents' do
+ # Returns a Schema that uses the given `type`
+ def mock_schema(type)
+ query_type = Class.new(GraphQL::Schema::Object) do
+ graphql_name 'QueryType'
+
+ field :foo, type, null: true
+ end
+
+ GraphQL::Schema.define(query: query_type)
+ end
+
+ let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/', 'default.md.haml') }
+
+ subject(:contents) do
+ described_class.new(
+ mock_schema(type).graphql_definition,
+ output_dir: nil,
+ template: template
+ ).contents
+ end
+
+ context 'A type with a field with a [Array] return type' do
+ let(:type) do
+ Class.new(GraphQL::Schema::Object) do
+ graphql_name 'ArrayTest'
+
+ field :foo, [GraphQL::STRING_TYPE], null: false, description: 'A description'
+ end
+ end
+
+ specify do
+ expectation = <<~DOC
+ ## ArrayTest
+
+ | Name | Type | Description |
+ | --- | ---- | ---------- |
+ | `foo` | String! => Array | A description |
+ DOC
+
+ is_expected.to include(expectation)
+ end
+ end
+
+ context 'A type with fields defined in reverse alphabetical order' do
+ let(:type) do
+ Class.new(GraphQL::Schema::Object) do
+ graphql_name 'OrderingTest'
+
+ field :foo, GraphQL::STRING_TYPE, null: false, description: 'A description of foo field'
+ field :bar, GraphQL::STRING_TYPE, null: false, description: 'A description of bar field'
+ end
+ end
+
+ specify do
+ expectation = <<~DOC
+ ## OrderingTest
+
+ | Name | Type | Description |
+ | --- | ---- | ---------- |
+ | `bar` | String! | A description of bar field |
+ | `foo` | String! | A description of foo field |
+ DOC
+
+ is_expected.to include(expectation)
+ end
+ end
+
+ context 'A type with a deprecated field' do
+ let(:type) do
+ Class.new(GraphQL::Schema::Object) do
+ graphql_name 'DeprecatedTest'
+
+ field :foo, GraphQL::STRING_TYPE, null: false, deprecation_reason: 'This is deprecated', description: 'A description'
+ end
+ end
+
+ specify do
+ expectation = <<~DOC
+ ## DeprecatedTest
+
+ | Name | Type | Description |
+ | --- | ---- | ---------- |
+ | `foo` **{warning-solid}** | String! | **Deprecated:** This is deprecated |
+ DOC
+
+ is_expected.to include(expectation)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
index 1e8de144b8d..b3d57c899d5 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
include GraphqlHelpers
- set(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'files/lfs/lfs_object.iso'), repository) }
let(:otherblob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.blob_at('master', 'README'), repository) }
diff --git a/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
new file mode 100644
index 00000000000..2269b4def82
--- /dev/null
+++ b/spec/lib/gitlab/graphql/pagination/offset_active_record_relation_connection_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection do
+ it 'subclasses from GraphQL::Relay::RelationConnection' do
+ expect(described_class.superclass).to eq GraphQL::Relay::RelationConnection
+ end
+end
diff --git a/spec/lib/gitlab/graphql/timeout_spec.rb b/spec/lib/gitlab/graphql/timeout_spec.rb
new file mode 100644
index 00000000000..8e04586d0ec
--- /dev/null
+++ b/spec/lib/gitlab/graphql/timeout_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Timeout do
+ it 'inherits from ' do
+ expect(described_class.superclass).to eq GraphQL::Schema::Timeout
+ end
+
+ it 'sends the error to our GraphQL logger' do
+ parent_type = double(graphql_name: 'parent_type')
+ field = double(graphql_name: 'field')
+ query = double(query_string: 'query_string', provided_variables: 'provided_variables')
+ error = GraphQL::Schema::Timeout::TimeoutError.new(parent_type, field)
+
+ expect(Gitlab::GraphqlLogger)
+ .to receive(:error)
+ .with(message: 'Timeout on parent_type.field', query: 'query_string', query_variables: 'provided_variables')
+
+ timeout = described_class.new(max_seconds: 30)
+ timeout.handle_timeout(error, query)
+ end
+end
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
index f3cbb811679..c59b152a982 100644
--- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb
+++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
@@ -186,7 +186,7 @@ describe Gitlab::HashedStorage::Migrator, :redis do
end
describe 'migration_pending?' do
- set(:project) { create(:project, :empty_repo) }
+ let_it_be(:project) { create(:project, :empty_repo) }
it 'returns true when there are MigratorWorker jobs scheduled' do
Sidekiq::Testing.disable! do
@@ -210,7 +210,7 @@ describe Gitlab::HashedStorage::Migrator, :redis do
end
describe 'rollback_pending?' do
- set(:project) { create(:project, :empty_repo) }
+ let_it_be(:project) { create(:project, :empty_repo) }
it 'returns true when there are RollbackerWorker jobs scheduled' do
Sidekiq::Testing.disable! do
diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
index 5135c84df19..cff489e0f3b 100644
--- a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::HookData::IssuableBuilder do
- set(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
# This shared example requires a `builder` and `user` variable
shared_examples 'issuable hook data' do |kind|
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index 8008f3d72b2..168603d6fcc 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
describe Gitlab::HookData::IssueBuilder do
- set(:label) { create(:label) }
- set(:issue) { create(:labeled_issue, labels: [label], project: label.project) }
+ let_it_be(:label) { create(:label) }
+ let_it_be(:issue) { create(:labeled_issue, labels: [label], project: label.project) }
let(:builder) { described_class.new(issue) }
describe '#build' do
diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
index 506354e370c..67fa0a7426a 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::HookData::MergeRequestBuilder do
- set(:merge_request) { create(:merge_request) }
+ let_it_be(:merge_request) { create(:merge_request) }
let(:builder) { described_class.new(merge_request) }
describe '#build' do
diff --git a/spec/lib/gitlab/import/merge_request_helpers_spec.rb b/spec/lib/gitlab/import/merge_request_helpers_spec.rb
index 2b165994152..d81251c4a43 100644
--- a/spec/lib/gitlab/import/merge_request_helpers_spec.rb
+++ b/spec/lib/gitlab/import/merge_request_helpers_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
describe Gitlab::Import::MergeRequestHelpers, type: :helper do
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
describe '.create_merge_request_without_hooks' do
let(:iid) { 42 }
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
index 86ceb97b250..1631de393b5 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
@@ -3,6 +3,12 @@
require 'spec_helper'
describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
+ before do
+ allow_next_instance_of(ProjectExportWorker) do |job|
+ allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
+ end
+ end
+
let!(:service) { described_class.new }
let!(:project) { create(:project, :with_export) }
let(:shared) { project.import_export_shared }
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 95c47d15f8f..7792daed99c 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
@@ -5,6 +5,12 @@ require 'spec_helper'
describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
include StubRequests
+ before do
+ allow_next_instance_of(ProjectExportWorker) do |job|
+ allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
+ end
+ end
+
let(:example_url) { 'http://www.example.com' }
let(:strategy) { subject.new(url: example_url, http_method: 'post') }
let!(:project) { create(:project, :with_export) }
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 4dadb310029..37b3e4a4a22 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -9,6 +9,8 @@ issues:
- notes
- resource_label_events
- resource_weight_events
+- resource_milestone_events
+- sent_notifications
- sentry_issue
- label_links
- labels
@@ -109,6 +111,7 @@ merge_requests:
- milestone
- notes
- resource_label_events
+- resource_milestone_events
- label_links
- labels
- last_edited_by
@@ -190,9 +193,11 @@ ci_pipelines:
- environments
- chat_data
- source_pipeline
+- ref_status
- source_bridge
- source_job
- sourced_pipelines
+- source_project
- triggered_by_pipeline
- triggered_pipelines
- child_pipelines
@@ -357,6 +362,7 @@ project:
- ci_pipelines
- all_pipelines
- stages
+- ci_refs
- builds
- runner_projects
- runners
@@ -461,6 +467,9 @@ project:
- container_expiration_policy
- resource_groups
- autoclose_referenced_issues
+- status_page_setting
+- requirements
+- export_jobs
award_emoji:
- awardable
- user
@@ -565,6 +574,8 @@ designs: *design
actions:
- design
- version
+- uploads
+- file_uploads
versions: &version
- author
- issue
@@ -602,3 +613,6 @@ epic:
- events
- resource_label_events
- user_mentions
+epic_issue:
+- epic
+- issue
diff --git a/spec/lib/gitlab/import_export/base_object_builder_spec.rb b/spec/lib/gitlab/import_export/base/object_builder_spec.rb
index fbb3b08cf56..e5242ae0bfc 100644
--- a/spec/lib/gitlab/import_export/base_object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/base/object_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::BaseObjectBuilder do
+describe Gitlab::ImportExport::Base::ObjectBuilder do
let(:project) do
create(:project, :repository,
:builds_disabled,
@@ -11,7 +11,7 @@ describe Gitlab::ImportExport::BaseObjectBuilder do
path: 'project')
end
let(:klass) { Milestone }
- let(:attributes) { { 'title' => 'Test BaseObjectBuilder Milestone', 'project' => project } }
+ let(:attributes) { { 'title' => 'Test Base::ObjectBuilder Milestone', 'project' => project } }
subject { described_class.build(klass, attributes) }
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 def3e43de9b..50d93763ad6 100644
--- a/spec/lib/gitlab/import_export/base_relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/base/relation_factory_spec.rb
@@ -2,21 +2,19 @@
require 'spec_helper'
-describe Gitlab::ImportExport::BaseRelationFactory do
+describe Gitlab::ImportExport::Base::RelationFactory do
let(:user) { create(:admin) }
let(:project) { create(:project) }
let(:members_mapper) { double('members_mapper').as_null_object }
let(:relation_sym) { :project_snippets }
- let(:merge_requests_mapping) { {} }
let(:relation_hash) { {} }
let(:excluded_keys) { [] }
subject do
described_class.create(relation_sym: relation_sym,
relation_hash: relation_hash,
- object_builder: Gitlab::ImportExport::GroupProjectObjectBuilder,
+ object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
members_mapper: members_mapper,
- merge_requests_mapping: merge_requests_mapping,
user: user,
importable: project,
excluded_keys: excluded_keys)
@@ -35,6 +33,15 @@ describe Gitlab::ImportExport::BaseRelationFactory do
end
end
+ context 'when the relation is predefined' do
+ let(:relation_sym) { :milestone }
+ let(:relation_hash) { { 'name' => '#upcoming', 'title' => 'Upcoming', 'id' => -2 } }
+
+ it 'returns without creating a new relation' do
+ expect(subject).to be_nil
+ end
+ end
+
context 'when #setup_models is not implemented' do
it 'raises NotImplementedError' do
expect { subject }.to raise_error(NotImplementedError)
diff --git a/spec/lib/gitlab/import_export/error_spec.rb b/spec/lib/gitlab/import_export/error_spec.rb
new file mode 100644
index 00000000000..067f7049097
--- /dev/null
+++ b/spec/lib/gitlab/import_export/error_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::Error do
+ describe '.permission_error' do
+ subject(:error) do
+ described_class.permission_error(user, importable)
+ end
+
+ let(:user) { build(:user, id: 1) }
+
+ context 'when supplied a project' do
+ let(:importable) { build(:project, id: 1, name: 'project1') }
+
+ it 'returns an error with the correct message' do
+ expect(error.message)
+ .to eq 'User with ID: 1 does not have required permissions for Project: project1 with ID: 1'
+ end
+ end
+
+ context 'when supplied a group' do
+ let(:importable) { build(:group, id: 1, name: 'group1') }
+
+ it 'returns an error with the correct message' do
+ expect(error.message)
+ .to eq 'User with ID: 1 does not have required permissions for Group: group1 with ID: 1'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
index 56ec6ec0f59..15058684229 100644
--- a/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
+++ b/spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb
@@ -215,6 +215,14 @@ describe Gitlab::ImportExport::FastHashSerializer do
expect(subject['boards'].first['lists']).not_to be_empty
end
+ context 'relation ordering' do
+ it 'orders exported pipelines by primary key' do
+ expected_order = project.ci_pipelines.reorder(:id).ids
+
+ expect(subject['ci_pipelines'].pluck('id')).to eq(expected_order)
+ end
+ end
+
def setup_project
release = create(:release)
group = create(:group)
@@ -246,6 +254,8 @@ describe Gitlab::ImportExport::FastHashSerializer do
ci_build.pipeline.update(project: project)
create(:commit_status, project: project, pipeline: ci_build.pipeline)
+ create_list(:ci_pipeline, 5, :success, project: project)
+
create(:milestone, project: project)
create(:discussion_note, noteable: issue, project: project)
create(:note, noteable: merge_request, project: project)
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index 09e4f62c686..8aa28353c04 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -24,11 +24,11 @@ describe 'forked project import' do
end
let(:saver) do
- Gitlab::ImportExport::ProjectTreeSaver.new(project: project_with_repo, current_user: user, shared: shared)
+ Gitlab::ImportExport::Project::TreeSaver.new(project: project_with_repo, current_user: user, shared: shared)
end
let(:restorer) do
- Gitlab::ImportExport::ProjectTreeRestorer.new(user: user, shared: shared, project: project)
+ Gitlab::ImportExport::Project::TreeRestorer.new(user: user, shared: shared, project: project)
end
before do
diff --git a/spec/lib/gitlab/import_export/group_object_builder_spec.rb b/spec/lib/gitlab/import_export/group/object_builder_spec.rb
index 08b2dae1147..781670b0aa5 100644
--- a/spec/lib/gitlab/import_export/group_object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/group/object_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::GroupObjectBuilder do
+describe Gitlab::ImportExport::Group::ObjectBuilder do
let(:group) { create(:group) }
let(:base_attributes) do
{
diff --git a/spec/lib/gitlab/import_export/group_relation_factory_spec.rb b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
index 9208b2ad203..332648d5c89 100644
--- a/spec/lib/gitlab/import_export/group_relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/group/relation_factory_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::GroupRelationFactory do
+describe Gitlab::ImportExport::Group::RelationFactory do
let(:group) { create(:group) }
let(:members_mapper) { double('members_mapper').as_null_object }
let(:user) { create(:admin) }
@@ -11,7 +11,7 @@ describe Gitlab::ImportExport::GroupRelationFactory do
described_class.create(relation_sym: relation_sym,
relation_hash: relation_hash,
members_mapper: members_mapper,
- object_builder: Gitlab::ImportExport::GroupObjectBuilder,
+ object_builder: Gitlab::ImportExport::Group::ObjectBuilder,
user: user,
importable: group,
excluded_keys: excluded_keys)
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 b2c8398d358..5584f1503f7 100644
--- a/spec/lib/gitlab/import_export/group_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::GroupTreeRestorer do
+describe Gitlab::ImportExport::Group::TreeRestorer do
include ImportExport::CommonUtil
let(:shared) { Gitlab::ImportExport::Shared.new(group) }
diff --git a/spec/lib/gitlab/import_export/group_tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
index 7f49c7af8fa..44fd49f0ac3 100644
--- a/spec/lib/gitlab/import_export/group_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/group/tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::GroupTreeSaver do
+describe Gitlab::ImportExport::Group::TreeSaver do
describe 'saves the group tree into a json object' do
let(:shared) { Gitlab::ImportExport::Shared.new(group) }
let(:group_tree_saver) { described_class.new(group: group, current_user: user, shared: shared) }
@@ -72,7 +72,7 @@ describe Gitlab::ImportExport::GroupTreeSaver do
# except:
# context 'with description override' do
# context 'group members' do
- # ^ These are specific for the groupTreeSaver
+ # ^ These are specific for the Group::TreeSaver
context 'JSON' do
let(:saved_group_json) do
group_tree_saver.save
@@ -189,7 +189,7 @@ describe Gitlab::ImportExport::GroupTreeSaver do
create(:group_badge, group: group)
group_label = create(:group_label, group: group)
create(:label_priority, label: group_label, priority: 1)
- board = create(:board, group: group)
+ board = create(:board, group: group, milestone_id: Milestone::Upcoming.id)
create(:list, board: board, label: group_label)
create(:group_badge, group: group)
@@ -197,6 +197,6 @@ describe Gitlab::ImportExport::GroupTreeSaver do
end
def group_json(filename)
- JSON.parse(IO.read(filename))
+ ::JSON.parse(IO.read(filename))
end
end
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
index 2ece0dd4b56..300ba66ee5b 100644
--- a/spec/lib/gitlab/import_export/import_export_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -21,4 +21,12 @@ describe Gitlab::ImportExport do
expect(described_class.export_filename(exportable: project).length).to be < 70
end
end
+
+ describe '#snippet_repo_bundle_filename_for' do
+ let(:snippet) { build(:snippet, id: 1) }
+
+ it 'generates the snippet bundle name' do
+ expect(described_class.snippet_repo_bundle_filename_for(snippet)).to eq "#{snippet.hexdigest}.bundle"
+ end
+ end
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 97d5ce07d47..335b0031147 100644
--- a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
+++ b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb
@@ -89,8 +89,6 @@ describe 'Test coverage of the Project Import' do
def relations_from_json(json_file)
json = ActiveSupport::JSON.decode(IO.read(json_file))
- Gitlab::ImportExport::RelationRenameService.rename(json)
-
[].tap {|res| gather_relations({ project: json }, res, [])}
.map {|relation_names| relation_names.join('.')}
end
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index 942af4084e5..e03c95525df 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -50,7 +50,8 @@ describe Gitlab::ImportExport::Importer do
Gitlab::ImportExport::WikiRestorer,
Gitlab::ImportExport::UploadsRestorer,
Gitlab::ImportExport::LfsRestorer,
- Gitlab::ImportExport::StatisticsRestorer
+ Gitlab::ImportExport::StatisticsRestorer,
+ Gitlab::ImportExport::SnippetsRepoRestorer
].each do |restorer|
it "calls the #{restorer}" do
fake_restorer = double(restorer.to_s)
@@ -63,7 +64,7 @@ describe Gitlab::ImportExport::Importer do
end
it 'restores the ProjectTree' do
- expect(Gitlab::ImportExport::ProjectTreeRestorer).to receive(:new).and_call_original
+ expect(Gitlab::ImportExport::Project::TreeRestorer).to receive(:new).and_call_original
importer.execute
end
diff --git a/spec/lib/gitlab/import_export/json/legacy_reader_spec.rb b/spec/lib/gitlab/import_export/json/legacy_reader_spec.rb
new file mode 100644
index 00000000000..0009a5f81de
--- /dev/null
+++ b/spec/lib/gitlab/import_export/json/legacy_reader_spec.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::JSON::LegacyReader::User do
+ let(:relation_names) { [] }
+ let(:legacy_reader) { described_class.new(tree_hash, relation_names) }
+
+ describe '#valid?' do
+ subject { legacy_reader.valid? }
+
+ context 'tree_hash not present' do
+ let(:tree_hash) { nil }
+
+ it { is_expected.to be false }
+ end
+
+ context 'tree_hash presents' do
+ let(:tree_hash) { { "issues": [] } }
+
+ it { is_expected.to be true }
+ end
+ end
+end
+
+describe Gitlab::ImportExport::JSON::LegacyReader::File do
+ let(:fixture) { 'spec/fixtures/lib/gitlab/import_export/light/project.json' }
+ let(:project_tree) { JSON.parse(File.read(fixture)) }
+ let(:relation_names) { [] }
+ let(:legacy_reader) { described_class.new(path, relation_names) }
+
+ describe '#valid?' do
+ subject { legacy_reader.valid? }
+
+ context 'given valid path' do
+ let(:path) { fixture }
+
+ it { is_expected.to be true }
+ end
+
+ context 'given invalid path' do
+ let(:path) { 'spec/non-existing-folder/do-not-create-this-file.json' }
+
+ it { is_expected.to be false }
+ end
+ end
+
+ describe '#root_attributes' do
+ let(:path) { fixture }
+
+ subject { legacy_reader.root_attributes(excluded_attributes) }
+
+ context 'No excluded attributes' do
+ let(:excluded_attributes) { [] }
+ let(:relation_names) { [] }
+
+ it 'returns the whole tree from parsed JSON' do
+ expect(subject).to eq(project_tree)
+ end
+ end
+
+ context 'Some attributes are excluded' do
+ let(:excluded_attributes) { %w[milestones labels issues services snippets] }
+ let(:relation_names) { %w[import_type archived] }
+
+ it 'returns hash without excluded attributes and relations' do
+ expect(subject).not_to include('milestones', 'labels', 'issues', 'services', 'snippets', 'import_type', 'archived')
+ end
+ end
+ end
+
+ describe '#consume_relation' do
+ let(:path) { fixture }
+ let(:key) { 'description' }
+
+ context 'block not given' do
+ it 'returns value of the key' do
+ expect(legacy_reader).to receive(:relations).and_return({ key => 'test value' })
+ expect(legacy_reader.consume_relation(key)).to eq('test value')
+ end
+ end
+
+ context 'key has been consumed' do
+ before do
+ legacy_reader.consume_relation(key)
+ end
+
+ it 'does not yield' do
+ expect do |blk|
+ legacy_reader.consume_relation(key, &blk)
+ end.not_to yield_control
+ end
+ end
+
+ context 'value is nil' do
+ before do
+ expect(legacy_reader).to receive(:relations).and_return({ key => nil })
+ end
+
+ it 'does not yield' do
+ expect do |blk|
+ legacy_reader.consume_relation(key, &blk)
+ end.not_to yield_control
+ end
+ end
+
+ context 'value is not array' do
+ before do
+ expect(legacy_reader).to receive(:relations).and_return({ key => 'value' })
+ end
+
+ it 'yield the value with index 0' do
+ expect do |blk|
+ legacy_reader.consume_relation(key, &blk)
+ end.to yield_with_args('value', 0)
+ end
+ end
+
+ context 'value is an array' do
+ before do
+ expect(legacy_reader).to receive(:relations).and_return({ key => %w[item1 item2 item3] })
+ end
+
+ it 'yield each array element with index' do
+ expect do |blk|
+ legacy_reader.consume_relation(key, &blk)
+ end.to yield_successive_args(['item1', 0], ['item2', 1], ['item3', 2])
+ end
+ end
+ end
+
+ describe '#tree_hash' do
+ let(:path) { fixture }
+
+ subject { legacy_reader.send(:tree_hash) }
+
+ it 'parses the JSON into the expected tree' do
+ expect(subject).to eq(project_tree)
+ end
+
+ context 'invalid JSON' do
+ let(:path) { 'spec/fixtures/lib/gitlab/import_export/invalid_json/project.json' }
+
+ it 'raise Exception' do
+ expect { subject }.to raise_exception(Gitlab::ImportExport::Error, 'Incorrect JSON format')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb b/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb
new file mode 100644
index 00000000000..b4cdfee3b22
--- /dev/null
+++ b/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::JSON::LegacyWriter do
+ let(:path) { "#{Dir.tmpdir}/legacy_writer_spec/test.json" }
+
+ subject { described_class.new(path) }
+
+ after do
+ FileUtils.rm_rf(path)
+ end
+
+ describe "#write" do
+ context "when key is already written" do
+ it "raises exception" do
+ key = "key"
+ value = "value"
+ subject.write(key, value)
+
+ expect { subject.write(key, "new value") }.to raise_exception("key '#{key}' already written")
+ end
+ end
+
+ context "when key is not already written" do
+ context "when multiple key value pairs are stored" do
+ it "writes correct json" do
+ expected_hash = { "key" => "value_1", "key_1" => "value_2" }
+ expected_hash.each do |key, value|
+ subject.write(key, value)
+ end
+ subject.close
+
+ expect(saved_json(path)).to eq(expected_hash)
+ end
+ end
+ end
+ end
+
+ describe "#append" do
+ context "when key is already written" do
+ it "appends values under a given key" do
+ key = "key"
+ values = %w(value_1 value_2)
+ expected_hash = { key => values }
+ values.each do |value|
+ subject.append(key, value)
+ end
+ subject.close
+
+ expect(saved_json(path)).to eq(expected_hash)
+ end
+ end
+
+ context "when key is not already written" do
+ it "writes correct json" do
+ expected_hash = { "key" => ["value"] }
+ subject.append("key", "value")
+ subject.close
+
+ expect(saved_json(path)).to eq(expected_hash)
+ end
+ end
+ end
+
+ describe "#set" do
+ it "writes correct json" do
+ expected_hash = { "key" => "value_1", "key_1" => "value_2" }
+ subject.set(expected_hash)
+ subject.close
+
+ expect(saved_json(path)).to eq(expected_hash)
+ end
+ end
+
+ def saved_json(filename)
+ ::JSON.parse(IO.read(filename))
+ end
+end
diff --git a/spec/lib/gitlab/import_export/relation_tree_saver_spec.rb b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb
index 2fc26c0e3d4..db77bd338e1 100644
--- a/spec/lib/gitlab/import_export/relation_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/legacy_relation_tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RelationTreeSaver do
+describe Gitlab::ImportExport::LegacyRelationTreeSaver do
let(:exportable) { create(:group) }
let(:relation_tree_saver) { described_class.new }
let(:tree) { {} }
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 01a7901062a..7e2b5ed534f 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -4,167 +4,191 @@ require 'spec_helper'
describe Gitlab::ImportExport::MembersMapper do
describe 'map members' do
- let(:user) { create(:admin) }
- let(:project) { create(:project, :public, name: 'searchable_project') }
- let(:user2) { create(:user) }
- let(:exported_user_id) { 99 }
- let(:exported_members) do
- [{
- "id" => 2,
- "access_level" => 40,
- "source_id" => 14,
- "source_type" => "Project",
- "notification_level" => 3,
- "created_at" => "2016-03-11T10:21:44.822Z",
- "updated_at" => "2016-03-11T10:21:44.822Z",
- "created_by_id" => nil,
- "invite_email" => nil,
- "invite_token" => nil,
- "invite_accepted_at" => nil,
- "user" =>
- {
- "id" => exported_user_id,
- "email" => user2.email,
- "username" => 'test'
- },
- "user_id" => 19
- },
- {
- "id" => 3,
- "access_level" => 40,
- "source_id" => 14,
- "source_type" => "Project",
- "user_id" => nil,
- "notification_level" => 3,
- "created_at" => "2016-03-11T10:21:44.822Z",
- "updated_at" => "2016-03-11T10:21:44.822Z",
- "created_by_id" => 1,
- "invite_email" => 'invite@test.com',
- "invite_token" => 'token',
- "invite_accepted_at" => nil
- }]
- end
-
- let(:members_mapper) do
- described_class.new(
- exported_members: exported_members, user: user, importable: project)
- end
-
- it 'includes the exported user ID in the map' do
- expect(members_mapper.map.keys).to include(exported_user_id)
- end
-
- it 'maps a project member' do
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
- end
-
- it 'defaults to importer project member if it does not exist' do
- expect(members_mapper.map[-1]).to eq(user.id)
- end
-
- it 'has invited members with no user' do
- members_mapper.map
-
- expect(ProjectMember.find_by_invite_email('invite@test.com')).not_to be_nil
- end
-
- it 'authorizes the users to the project' do
- members_mapper.map
-
- expect(user.authorized_project?(project)).to be true
- expect(user2.authorized_project?(project)).to be true
- end
-
- it 'maps an owner as a maintainer' do
- exported_members.first['access_level'] = ProjectMember::OWNER
-
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
- expect(ProjectMember.find_by_user_id(user2.id).access_level).to eq(ProjectMember::MAINTAINER)
- end
-
- it 'removes old user_id from member_hash to avoid conflict with user key' do
- expect(ProjectMember)
- .to receive(:create)
- .twice
- .with(hash_excluding('user_id'))
- .and_call_original
-
- members_mapper.map
- end
-
- context 'user is not an admin' do
- let(:user) { create(:user) }
-
- it 'does not map a project member' do
- expect(members_mapper.map[exported_user_id]).to eq(user.id)
+ shared_examples 'imports exported members' do
+ let(:user) { create(:admin) }
+ let(:user2) { create(:user) }
+ let(:exported_user_id) { 99 }
+ let(:exported_members) do
+ [{
+ "id" => 2,
+ "access_level" => 40,
+ "source_id" => 14,
+ "source_type" => source_type,
+ "notification_level" => 3,
+ "created_at" => "2016-03-11T10:21:44.822Z",
+ "updated_at" => "2016-03-11T10:21:44.822Z",
+ "created_by_id" => nil,
+ "invite_email" => nil,
+ "invite_token" => nil,
+ "invite_accepted_at" => nil,
+ "user" =>
+ {
+ "id" => exported_user_id,
+ "email" => user2.email,
+ "username" => 'test'
+ },
+ "user_id" => 19
+ },
+ {
+ "id" => 3,
+ "access_level" => 40,
+ "source_id" => 14,
+ "source_type" => source_type,
+ "user_id" => nil,
+ "notification_level" => 3,
+ "created_at" => "2016-03-11T10:21:44.822Z",
+ "updated_at" => "2016-03-11T10:21:44.822Z",
+ "created_by_id" => 1,
+ "invite_email" => 'invite@test.com',
+ "invite_token" => 'token',
+ "invite_accepted_at" => nil
+ }]
end
- it 'defaults to importer project member if it does not exist' do
- expect(members_mapper.map[-1]).to eq(user.id)
+ let(:members_mapper) do
+ described_class.new(
+ exported_members: exported_members, user: user, importable: importable)
end
- end
- context 'chooses the one with an email first' do
- let(:user3) { create(:user, username: 'test') }
+ it 'includes the exported user ID in the map' do
+ expect(members_mapper.map.keys).to include(exported_user_id)
+ end
- it 'maps the project member that has a matching email first' do
+ it 'maps a member' do
expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end
- end
- context 'importer same as group member' do
- let(:user2) { create(:admin) }
- let(:group) { create(:group) }
- let(:project) { create(:project, :public, name: 'searchable_project', namespace: group) }
- let(:members_mapper) do
- described_class.new(
- exported_members: exported_members, user: user2, importable: project)
+ it 'defaults to importer member if it does not exist' do
+ expect(members_mapper.map[-1]).to eq(user.id)
end
- before do
- group.add_users([user, user2], GroupMember::DEVELOPER)
- end
+ it 'has invited members with no user' do
+ members_mapper.map
- it 'maps the project member' do
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ expect(member_class.find_by_invite_email('invite@test.com')).not_to be_nil
end
- it 'maps the project member if it already exists' do
- project.add_maintainer(user2)
+ it 'removes old user_id from member_hash to avoid conflict with user key' do
+ expect(member_class)
+ .to receive(:create)
+ .twice
+ .with(hash_excluding('user_id'))
+ .and_call_original
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ members_mapper.map
end
- end
- context 'importing group members' do
- let(:group) { create(:group) }
- let(:project) { create(:project, namespace: group) }
- let(:members_mapper) do
- described_class.new(
- exported_members: exported_members, user: user, importable: project)
- end
+ context 'user is not an admin' do
+ let(:user) { create(:user) }
- before do
- group.add_users([user, user2], GroupMember::DEVELOPER)
- user.update(email: 'invite@test.com')
+ it 'does not map a member' do
+ expect(members_mapper.map[exported_user_id]).to eq(user.id)
+ end
+
+ it 'defaults to importer member if it does not exist' do
+ expect(members_mapper.map[-1]).to eq(user.id)
+ end
end
- it 'maps the importer' do
- expect(members_mapper.map[-1]).to eq(user.id)
+ context 'chooses the one with an email' do
+ let(:user3) { create(:user, username: 'test') }
+
+ it 'maps the member that has a matching email' do
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
end
+ end
- it 'maps the group member' do
- expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ context 'when importable is Project' do
+ include_examples 'imports exported members' do
+ let(:source_type) { 'Project' }
+ let(:member_class) { ProjectMember }
+ let(:importable) { create(:project, :public, name: 'searchable_project') }
+
+ it 'authorizes the users to the project' do
+ members_mapper.map
+
+ expect(user.authorized_project?(importable)).to be true
+ expect(user2.authorized_project?(importable)).to be true
+ end
+
+ it 'maps an owner as a maintainer' do
+ exported_members.first['access_level'] = ProjectMember::OWNER
+
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ expect(member_class.find_by_user_id(user2.id).access_level).to eq(ProjectMember::MAINTAINER)
+ end
+
+ context 'importer same as group member' do
+ let(:user2) { create(:admin) }
+ let(:group) { create(:group) }
+ let(:importable) { create(:project, :public, name: 'searchable_project', namespace: group) }
+ let(:members_mapper) do
+ described_class.new(
+ exported_members: exported_members, user: user2, importable: importable)
+ end
+
+ before do
+ group.add_users([user, user2], GroupMember::DEVELOPER)
+ end
+
+ it 'maps the project member' do
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+
+ it 'maps the project member if it already exists' do
+ importable.add_maintainer(user2)
+
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+ end
+
+ context 'importing group members' do
+ let(:group) { create(:group) }
+ let(:importable) { create(:project, namespace: group) }
+ let(:members_mapper) do
+ described_class.new(
+ exported_members: exported_members, user: user, importable: importable)
+ end
+
+ before do
+ group.add_users([user, user2], GroupMember::DEVELOPER)
+ user.update(email: 'invite@test.com')
+ end
+
+ it 'maps the importer' do
+ expect(members_mapper.map[-1]).to eq(user.id)
+ end
+
+ it 'maps the group member' do
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+ end
+
+ context 'when importer mapping fails' do
+ let(:exception_message) { 'Something went wrong' }
+
+ it 'includes importer specific error message' do
+ expect(member_class).to receive(:create!).and_raise(StandardError.new(exception_message))
+
+ expect { members_mapper.map }.to raise_error(StandardError, "Error adding importer user to Project members. #{exception_message}")
+ end
+ end
end
end
- context 'when importer mapping fails' do
- let(:exception_message) { 'Something went wrong' }
+ context 'when importable is Group' do
+ include_examples 'imports exported members' do
+ let(:source_type) { 'Namespace' }
+ let(:member_class) { GroupMember }
+ let(:importable) { create(:group) }
- it 'includes importer specific error message' do
- expect(ProjectMember).to receive(:create!).and_raise(StandardError.new(exception_message))
+ it 'does not lower owner access level' do
+ exported_members.first['access_level'] = member_class::OWNER
- expect { members_mapper.map }.to raise_error(StandardError, "Error adding importer user to Project members. #{exception_message}")
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ expect(member_class.find_by_user_id(user2.id).access_level).to eq(member_class::OWNER)
+ end
end
end
end
diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
index ab834ac3fa8..0cdb3c43992 100644
--- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
+++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
@@ -52,10 +52,10 @@ describe Gitlab::ImportExport::MergeRequestParser do
context 'when the diff is invalid' do
let(:merge_request_diff) { build(:merge_request_diff, merge_request: merge_request, base_commit_sha: 'foobar') }
- it 'sets the diff to nil' do
+ it 'sets the diff to empty diff' do
expect(merge_request_diff).to be_invalid
expect(merge_request_diff.merge_request).to eq merge_request
- expect(parsed_merge_request.merge_request_diff).to be_nil
+ expect(parsed_merge_request.merge_request_diff).to be_empty
end
end
end
diff --git a/spec/lib/gitlab/import_export/project/export_task_spec.rb b/spec/lib/gitlab/import_export/project/export_task_spec.rb
new file mode 100644
index 00000000000..cf11a1df33c
--- /dev/null
+++ b/spec/lib/gitlab/import_export/project/export_task_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+describe Gitlab::ImportExport::Project::ExportTask do
+ let(:username) { 'root' }
+ let(:namespace_path) { username }
+ let!(:user) { create(:user, username: username) }
+ let(:measurement_enabled) { false }
+ let(:file_path) { 'spec/fixtures/gitlab/import_export/test_project_export.tar.gz' }
+ let(:project) { create(:project, creator: user, namespace: user.namespace) }
+ let(:project_name) { project.name }
+
+ let(:task_params) do
+ {
+ username: username,
+ namespace_path: namespace_path,
+ project_path: project_name,
+ file_path: file_path,
+ measurement_enabled: measurement_enabled
+ }
+ end
+
+ subject { described_class.new(task_params).export }
+
+ context 'when project is found' do
+ let(:project) { create(:project, creator: user, namespace: user.namespace) }
+
+ around do |example|
+ example.run
+ ensure
+ File.delete(file_path)
+ end
+
+ it 'performs project export successfully' do
+ expect { subject }.to output(/Done!/).to_stdout
+
+ expect(subject).to eq(true)
+
+ expect(File).to exist(file_path)
+ end
+
+ it_behaves_like 'measurable'
+ end
+
+ context 'when project is not found' do
+ let(:project_name) { 'invalid project name' }
+
+ it 'logs an error' do
+ expect { subject }.to output(/Project with path: #{project_name} was not found. Please provide correct project path/).to_stdout
+ end
+
+ it 'returns false' do
+ expect(subject).to eq(false)
+ end
+ end
+
+ context 'when file path is invalid' do
+ let(:file_path) { '/invalid_file_path/test_project_export.tar.gz' }
+
+ it 'logs an error' do
+ expect { subject }.to output(/Invalid file path: #{file_path}. Please provide correct file path/ ).to_stdout
+ end
+
+ it 'returns false' do
+ expect(subject).to eq(false)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/project/import_task_spec.rb b/spec/lib/gitlab/import_export/project/import_task_spec.rb
new file mode 100644
index 00000000000..f7b9cbaa095
--- /dev/null
+++ b/spec/lib/gitlab/import_export/project/import_task_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+describe Gitlab::ImportExport::Project::ImportTask do
+ let(:username) { 'root' }
+ let(:namespace_path) { username }
+ let!(:user) { create(:user, username: username) }
+ let(:measurement_enabled) { false }
+ let(:project) { Project.find_by_full_path("#{namespace_path}/#{project_name}") }
+ let(:import_task) { described_class.new(task_params) }
+ let(:task_params) do
+ {
+ username: username,
+ namespace_path: namespace_path,
+ project_path: project_name,
+ file_path: file_path,
+ measurement_enabled: measurement_enabled
+ }
+ end
+
+ before do
+ allow(Settings.uploads.object_store).to receive(:[]=).and_call_original
+ end
+
+ around do |example|
+ old_direct_upload_setting = Settings.uploads.object_store['direct_upload']
+ old_background_upload_setting = Settings.uploads.object_store['background_upload']
+
+ Settings.uploads.object_store['direct_upload'] = true
+ Settings.uploads.object_store['background_upload'] = true
+
+ example.run
+
+ Settings.uploads.object_store['direct_upload'] = old_direct_upload_setting
+ Settings.uploads.object_store['background_upload'] = old_background_upload_setting
+ end
+
+ subject { import_task.import }
+
+ context 'when project import is valid' do
+ let(:project_name) { 'import_rake_test_project' }
+ let(:file_path) { 'spec/fixtures/gitlab/import_export/lightweight_project_export.tar.gz' }
+
+ it 'performs project import successfully' do
+ expect { subject }.to output(/Done!/).to_stdout
+ expect { subject }.not_to raise_error
+ expect(subject).to eq(true)
+
+ expect(project.merge_requests.count).to be > 0
+ expect(project.issues.count).to be > 0
+ expect(project.milestones.count).to be > 0
+ expect(project.import_state.status).to eq('finished')
+ end
+
+ it 'disables direct & background upload only during project creation' do
+ expect_next_instance_of(Projects::GitlabProjectsImportService) do |service|
+ expect(service).to receive(:execute).and_wrap_original do |m|
+ expect(Settings.uploads.object_store['background_upload']).to eq(false)
+ expect(Settings.uploads.object_store['direct_upload']).to eq(false)
+
+ m.call
+ end
+ end
+
+ expect(import_task).to receive(:execute_sidekiq_job).and_wrap_original do |m|
+ expect(Settings.uploads.object_store['background_upload']).to eq(true)
+ expect(Settings.uploads.object_store['direct_upload']).to eq(true)
+ expect(Settings.uploads.object_store).not_to receive(:[]=).with('backgroud_upload', false)
+ expect(Settings.uploads.object_store).not_to receive(:[]=).with('direct_upload', false)
+
+ m.call
+ end
+
+ subject
+ end
+
+ it_behaves_like 'measurable'
+ end
+
+ context 'when project import is invalid' do
+ let(:project_name) { 'import_rake_invalid_test_project' }
+ let(:file_path) { 'spec/fixtures/gitlab/import_export/corrupted_project_export.tar.gz' }
+ let(:not_imported_message) { /Total number of not imported relations: 1/ }
+
+ it 'performs project import successfully' do
+ expect { subject }.to output(not_imported_message).to_stdout
+ expect { subject }.not_to raise_error
+ expect(subject).to eq(true)
+
+ expect(project.merge_requests).to be_empty
+ expect(project.import_state.last_error).to be_nil
+ expect(project.import_state.status).to eq('finished')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/legacy_tree_saver_spec.rb
index 126ac289a56..d4406dbc60b 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/legacy_tree_saver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::ProjectTreeSaver do
+describe Gitlab::ImportExport::Project::LegacyTreeSaver do
describe 'saves the project tree into a json object' do
let(:shared) { project.import_export_shared }
let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) }
@@ -75,7 +75,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
# except:
# context 'with description override' do
# context 'group members' do
- # ^ These are specific for the ProjectTreeSaver
+ # ^ These are specific for the Project::TreeSaver
context 'JSON' do
let(:saved_project_json) do
project_tree_saver.save
@@ -392,6 +392,6 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
end
def project_json(filename)
- JSON.parse(IO.read(filename))
+ ::JSON.parse(IO.read(filename))
end
end
diff --git a/spec/lib/gitlab/import_export/group_project_object_builder_spec.rb b/spec/lib/gitlab/import_export/project/object_builder_spec.rb
index 34049cbf570..c9d1410400a 100644
--- a/spec/lib/gitlab/import_export/group_project_object_builder_spec.rb
+++ b/spec/lib/gitlab/import_export/project/object_builder_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::ImportExport::GroupProjectObjectBuilder do
+describe Gitlab::ImportExport::Project::ObjectBuilder do
let!(:group) { create(:group, :private) }
let!(:subgroup) { create(:group, :private, parent: group) }
let!(:project) do
diff --git a/spec/lib/gitlab/import_export/project_relation_factory_spec.rb b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
index 0ade7ac4fc7..73ae6810706 100644
--- a/spec/lib/gitlab/import_export/project_relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/project/relation_factory_spec.rb
@@ -2,19 +2,17 @@
require 'spec_helper'
-describe Gitlab::ImportExport::ProjectRelationFactory do
+describe Gitlab::ImportExport::Project::RelationFactory do
let(:group) { create(:group) }
let(:project) { create(:project, :repository, group: group) }
let(:members_mapper) { double('members_mapper').as_null_object }
- let(:merge_requests_mapping) { {} }
let(:user) { create(:admin) }
let(:excluded_keys) { [] }
let(:created_object) do
described_class.create(relation_sym: relation_sym,
relation_hash: relation_hash,
- object_builder: Gitlab::ImportExport::GroupProjectObjectBuilder,
+ object_builder: Gitlab::ImportExport::Project::ObjectBuilder,
members_mapper: members_mapper,
- merge_requests_mapping: merge_requests_mapping,
user: user,
importable: project,
excluded_keys: excluded_keys)
@@ -245,11 +243,11 @@ describe Gitlab::ImportExport::ProjectRelationFactory do
context 'Project references' do
let(:relation_sym) { :project_foo_model }
let(:relation_hash) do
- Gitlab::ImportExport::ProjectRelationFactory::PROJECT_REFERENCES.map { |ref| { ref => 99 } }.inject(:merge)
+ Gitlab::ImportExport::Project::RelationFactory::PROJECT_REFERENCES.map { |ref| { ref => 99 } }.inject(:merge)
end
class ProjectFooModel < FooModel
- attr_accessor(*Gitlab::ImportExport::ProjectRelationFactory::PROJECT_REFERENCES)
+ attr_accessor(*Gitlab::ImportExport::Project::RelationFactory::PROJECT_REFERENCES)
end
before do
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 c899217d164..e38ef75d085 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
-include ImportExport::CommonUtil
-describe Gitlab::ImportExport::ProjectTreeRestorer do
+describe Gitlab::ImportExport::Project::TreeRestorer do
include ImportExport::CommonUtil
let(:shared) { project.import_export_shared }
@@ -105,6 +104,24 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(pipeline.merge_request.source_branch).to eq('feature_conflict')
end
+ it 'restores pipelines based on ascending id order' do
+ expected_ordered_shas = %w[
+ 2ea1f3dec713d940208fb5ce4a38765ecb5d3f73
+ ce84140e8b878ce6e7c4d298c7202ff38170e3ac
+ 048721d90c449b244b7b4c53a9186b04330174ec
+ sha-notes
+ 5f923865dde3436854e9ceb9cdb7815618d4e849
+ d2d430676773caa88cdaf7c55944073b2fd5561a
+ 2ea1f3dec713d940208fb5ce4a38765ecb5d3f73
+ ]
+
+ project = Project.find_by_path('project')
+
+ project.ci_pipelines.order(:id).each_with_index do |pipeline, i|
+ expect(pipeline['sha']).to eq expected_ordered_shas[i]
+ end
+ end
+
it 'preserves updated_at on issues' do
issue = Issue.where(description: 'Aliquam enim illo et possimus.').first
@@ -386,7 +403,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it 'has the correct number of pipelines and statuses' do
expect(@project.ci_pipelines.size).to eq(7)
- @project.ci_pipelines.order(:id).zip([2, 2, 2, 2, 2, 0, 0])
+ @project.ci_pipelines.order(:id).zip([2, 0, 2, 2, 2, 2, 0])
.each do |(pipeline, expected_status_size)|
expect(pipeline.statuses.size).to eq(expected_status_size)
end
@@ -423,10 +440,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
it 'restores external pull request for the restored pipeline' do
- pipeline_with_external_pr = @project.ci_pipelines.order(:id).last
+ pipeline_with_external_pr = @project.ci_pipelines.where(source: 'external_pull_request_event').first
expect(pipeline_with_external_pr.external_pull_request).to be_persisted
end
+
+ it 'has no import failures' do
+ expect(@project.import_failures.size).to eq 0
+ end
end
end
end
@@ -500,6 +521,30 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
end
+ context 'multiple pipelines reference the same external pull request' do
+ before do
+ setup_import_export_config('multi_pipeline_ref_one_external_pr')
+ expect(restored_project_json).to eq(true)
+ end
+
+ it_behaves_like 'restores project successfully',
+ issues: 0,
+ labels: 0,
+ milestones: 0,
+ ci_pipelines: 2,
+ external_pull_requests: 1,
+ import_failures: 0
+
+ it 'restores external pull request for the restored pipelines' do
+ external_pr = project.external_pull_requests.first
+
+ project.ci_pipelines.each do |pipeline_with_external_pr|
+ expect(pipeline_with_external_pr.external_pull_request).to be_persisted
+ expect(pipeline_with_external_pr.external_pull_request).to eq(external_pr)
+ end
+ end
+ end
+
context 'when post import action throw non-retriable exception' do
let(:exception) { StandardError.new('post_import_error') }
@@ -658,6 +703,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(project.services.where(template: true).count).to eq(0)
end
+ it 'does not import any instance services' do
+ expect(restored_project_json).to eq(true)
+
+ expect(project.services.where(instance: true).count).to eq(0)
+ end
+
it 'imports labels' do
create(:group_label, name: 'Another label', group: project.group)
@@ -732,7 +783,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
before do
- expect(restorer).to receive(:read_tree_hash) { tree_hash }
+ allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:valid?).and_return(true)
+ allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:tree_hash) { tree_hash }
end
context 'no group visibility' do
diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
new file mode 100644
index 00000000000..23360b725b9
--- /dev/null
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -0,0 +1,346 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::Project::TreeSaver do
+ describe 'saves the project tree into a json object' do
+ let(:shared) { project.import_export_shared }
+ let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) }
+ let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
+ let(:user) { create(:user) }
+ let!(:project) { setup_project }
+
+ before do
+ project.add_maintainer(user)
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+ allow_any_instance_of(MergeRequest).to receive(:source_branch_sha).and_return('ABCD')
+ allow_any_instance_of(MergeRequest).to receive(:target_branch_sha).and_return('DCBA')
+ end
+
+ after do
+ FileUtils.rm_rf(export_path)
+ end
+
+ it 'saves project successfully' do
+ expect(project_tree_saver.save).to be true
+ end
+
+ context 'JSON' do
+ let(:saved_project_json) do
+ project_tree_saver.save
+ project_json(project_tree_saver.full_path)
+ end
+
+ # It is not duplicated in
+ # `spec/lib/gitlab/import_export/fast_hash_serializer_spec.rb`
+ context 'with description override' do
+ let(:params) { { description: 'Foo Bar' } }
+ let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared, params: params) }
+
+ it 'overrides the project description' do
+ expect(saved_project_json).to include({ 'description' => params[:description] })
+ end
+ end
+
+ it 'saves the correct json' do
+ expect(saved_project_json).to include({ 'description' => 'description', 'visibility_level' => 20 })
+ end
+
+ it 'has approvals_before_merge set' do
+ expect(saved_project_json['approvals_before_merge']).to eq(1)
+ end
+
+ it 'has milestones' do
+ expect(saved_project_json['milestones']).not_to be_empty
+ end
+
+ it 'has merge requests' do
+ expect(saved_project_json['merge_requests']).not_to be_empty
+ end
+
+ it 'has merge request\'s milestones' do
+ expect(saved_project_json['merge_requests'].first['milestone']).not_to be_empty
+ end
+
+ it 'has merge request\'s source branch SHA' do
+ expect(saved_project_json['merge_requests'].first['source_branch_sha']).to eq('ABCD')
+ end
+
+ it 'has merge request\'s target branch SHA' do
+ expect(saved_project_json['merge_requests'].first['target_branch_sha']).to eq('DCBA')
+ end
+
+ it 'has events' do
+ expect(saved_project_json['merge_requests'].first['milestone']['events']).not_to be_empty
+ end
+
+ it 'has snippets' do
+ expect(saved_project_json['snippets']).not_to be_empty
+ end
+
+ it 'has snippet notes' do
+ expect(saved_project_json['snippets'].first['notes']).not_to be_empty
+ end
+
+ it 'has releases' do
+ expect(saved_project_json['releases']).not_to be_empty
+ end
+
+ it 'has no author on releases' do
+ expect(saved_project_json['releases'].first['author']).to be_nil
+ end
+
+ it 'has the author ID on releases' do
+ expect(saved_project_json['releases'].first['author_id']).not_to be_nil
+ end
+
+ it 'has issues' do
+ expect(saved_project_json['issues']).not_to be_empty
+ end
+
+ it 'has issue comments' do
+ notes = saved_project_json['issues'].first['notes']
+
+ expect(notes).not_to be_empty
+ expect(notes.first['type']).to eq('DiscussionNote')
+ end
+
+ it 'has issue assignees' do
+ expect(saved_project_json['issues'].first['issue_assignees']).not_to be_empty
+ end
+
+ it 'has author on issue comments' do
+ expect(saved_project_json['issues'].first['notes'].first['author']).not_to be_empty
+ end
+
+ it 'has project members' do
+ expect(saved_project_json['project_members']).not_to be_empty
+ end
+
+ it 'has merge requests diffs' do
+ expect(saved_project_json['merge_requests'].first['merge_request_diff']).not_to be_empty
+ end
+
+ it 'has merge request diff files' do
+ expect(saved_project_json['merge_requests'].first['merge_request_diff']['merge_request_diff_files']).not_to be_empty
+ end
+
+ it 'has merge request diff commits' do
+ expect(saved_project_json['merge_requests'].first['merge_request_diff']['merge_request_diff_commits']).not_to be_empty
+ end
+
+ it 'has merge requests comments' do
+ expect(saved_project_json['merge_requests'].first['notes']).not_to be_empty
+ end
+
+ it 'has author on merge requests comments' do
+ expect(saved_project_json['merge_requests'].first['notes'].first['author']).not_to be_empty
+ end
+
+ it 'has pipeline stages' do
+ expect(saved_project_json.dig('ci_pipelines', 0, 'stages')).not_to be_empty
+ end
+
+ it 'has pipeline statuses' do
+ expect(saved_project_json.dig('ci_pipelines', 0, 'stages', 0, 'statuses')).not_to be_empty
+ end
+
+ it 'has pipeline builds' do
+ builds_count = saved_project_json
+ .dig('ci_pipelines', 0, 'stages', 0, 'statuses')
+ .count { |hash| hash['type'] == 'Ci::Build' }
+
+ expect(builds_count).to eq(1)
+ end
+
+ it 'has no when YML attributes but only the DB column' do
+ expect_any_instance_of(Gitlab::Ci::YamlProcessor).not_to receive(:build_attributes)
+
+ saved_project_json
+ end
+
+ it 'has pipeline commits' do
+ expect(saved_project_json['ci_pipelines']).not_to be_empty
+ end
+
+ it 'has ci pipeline notes' do
+ expect(saved_project_json['ci_pipelines'].first['notes']).not_to be_empty
+ end
+
+ it 'has labels with no associations' do
+ expect(saved_project_json['labels']).not_to be_empty
+ end
+
+ it 'has labels associated to records' do
+ expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
+ end
+
+ it 'has project and group labels' do
+ label_types = saved_project_json['issues'].first['label_links'].map { |link| link['label']['type'] }
+
+ expect(label_types).to match_array(%w(ProjectLabel GroupLabel))
+ end
+
+ it 'has priorities associated to labels' do
+ priorities = saved_project_json['issues'].first['label_links'].flat_map { |link| link['label']['priorities'] }
+
+ expect(priorities).not_to be_empty
+ end
+
+ it 'has issue resource label events' do
+ expect(saved_project_json['issues'].first['resource_label_events']).not_to be_empty
+ end
+
+ it 'has merge request resource label events' do
+ expect(saved_project_json['merge_requests'].first['resource_label_events']).not_to be_empty
+ end
+
+ it 'saves the correct service type' do
+ expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService')
+ end
+
+ it 'saves the properties for a service' do
+ expect(saved_project_json['services'].first['properties']).to eq('one' => 'value')
+ end
+
+ it 'has project feature' do
+ project_feature = saved_project_json['project_feature']
+ expect(project_feature).not_to be_empty
+ expect(project_feature["issues_access_level"]).to eq(ProjectFeature::DISABLED)
+ expect(project_feature["wiki_access_level"]).to eq(ProjectFeature::ENABLED)
+ expect(project_feature["builds_access_level"]).to eq(ProjectFeature::PRIVATE)
+ end
+
+ it 'has custom attributes' do
+ expect(saved_project_json['custom_attributes'].count).to eq(2)
+ end
+
+ it 'has badges' do
+ expect(saved_project_json['project_badges'].count).to eq(2)
+ end
+
+ it 'does not complain about non UTF-8 characters in MR diff files' do
+ ActiveRecord::Base.connection.execute("UPDATE merge_request_diff_files SET diff = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'")
+
+ expect(project_tree_saver.save).to be true
+ end
+
+ context 'group members' do
+ let(:user2) { create(:user, email: 'group@member.com') }
+ let(:member_emails) do
+ saved_project_json['project_members'].map do |pm|
+ pm['user']['email']
+ end
+ end
+
+ before do
+ Group.first.add_developer(user2)
+ end
+
+ it 'does not export group members if it has no permission' do
+ Group.first.add_developer(user)
+
+ expect(member_emails).not_to include('group@member.com')
+ end
+
+ it 'does not export group members as maintainer' do
+ Group.first.add_maintainer(user)
+
+ expect(member_emails).not_to include('group@member.com')
+ end
+
+ it 'exports group members as group owner' do
+ Group.first.add_owner(user)
+
+ expect(member_emails).to include('group@member.com')
+ end
+
+ context 'as admin' do
+ let(:user) { create(:admin) }
+
+ it 'exports group members as admin' do
+ expect(member_emails).to include('group@member.com')
+ end
+
+ it 'exports group members as project members' do
+ member_types = saved_project_json['project_members'].map { |pm| pm['source_type'] }
+
+ expect(member_types).to all(eq('Project'))
+ end
+ end
+ end
+
+ context 'project attributes' do
+ it 'does not contain the runners token' do
+ expect(saved_project_json).not_to include("runners_token" => 'token')
+ end
+ end
+
+ it 'has a board and a list' do
+ expect(saved_project_json['boards'].first['lists']).not_to be_empty
+ end
+ end
+ end
+
+ def setup_project
+ release = create(:release)
+ group = create(:group)
+
+ project = create(:project,
+ :public,
+ :repository,
+ :issues_disabled,
+ :wiki_enabled,
+ :builds_private,
+ description: 'description',
+ releases: [release],
+ group: group,
+ approvals_before_merge: 1
+ )
+ allow(project).to receive(:commit).and_return(Commit.new(RepoHelpers.sample_commit, project))
+
+ issue = create(:issue, assignees: [user], project: project)
+ snippet = create(:project_snippet, project: project)
+ project_label = create(:label, project: project)
+ group_label = create(:group_label, group: group)
+ create(:label_link, label: project_label, target: issue)
+ 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)
+
+ ci_build = create(:ci_build, project: project, when: nil)
+ ci_build.pipeline.update(project: project)
+ create(:commit_status, project: project, pipeline: ci_build.pipeline)
+
+ create(:milestone, project: project)
+ create(:discussion_note, noteable: issue, project: project)
+ create(:note, noteable: merge_request, project: project)
+ create(:note, noteable: snippet, project: project)
+ create(:note_on_commit,
+ author: user,
+ project: project,
+ commit_id: ci_build.pipeline.sha)
+
+ create(:resource_label_event, label: project_label, issue: issue)
+ create(:resource_label_event, label: group_label, merge_request: merge_request)
+
+ create(:event, :created, target: milestone, project: project, author: user)
+ create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker', properties: { one: 'value' })
+
+ create(:project_custom_attribute, project: project)
+ create(:project_custom_attribute, project: project)
+
+ create(:project_badge, project: project)
+ create(:project_badge, project: project)
+
+ board = create(:board, project: project, name: 'TestBoard')
+ create(:list, board: board, position: 0, label: project_label)
+
+ project
+ end
+
+ def project_json(filename)
+ ::JSON.parse(IO.read(filename))
+ end
+end
diff --git a/spec/lib/gitlab/import_export/project_tree_loader_spec.rb b/spec/lib/gitlab/import_export/project_tree_loader_spec.rb
deleted file mode 100644
index b22de5a3f7b..00000000000
--- a/spec/lib/gitlab/import_export/project_tree_loader_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::ImportExport::ProjectTreeLoader do
- let(:fixture) { 'spec/fixtures/lib/gitlab/import_export/with_duplicates.json' }
- let(:project_tree) { JSON.parse(File.read(fixture)) }
-
- context 'without de-duplicating entries' do
- let(:parsed_tree) do
- subject.load(fixture)
- end
-
- it 'parses the JSON into the expected tree' do
- expect(parsed_tree).to eq(project_tree)
- end
-
- it 'does not de-duplicate entries' do
- expect(parsed_tree['duped_hash_with_id']).not_to be(parsed_tree['array'][0]['duped_hash_with_id'])
- end
- end
-
- context 'with de-duplicating entries' do
- let(:parsed_tree) do
- subject.load(fixture, dedup_entries: true)
- end
-
- it 'parses the JSON into the expected tree' do
- expect(parsed_tree).to eq(project_tree)
- end
-
- it 'de-duplicates equal values' do
- expect(parsed_tree['duped_hash_with_id']).to be(parsed_tree['array'][0]['duped_hash_with_id'])
- expect(parsed_tree['duped_hash_with_id']).to be(parsed_tree['nested']['duped_hash_with_id'])
- expect(parsed_tree['duped_array']).to be(parsed_tree['array'][1]['duped_array'])
- expect(parsed_tree['duped_array']).to be(parsed_tree['nested']['duped_array'])
- end
-
- it 'does not de-duplicate hashes without IDs' do
- expect(parsed_tree['duped_hash_no_id']).to eq(parsed_tree['array'][2]['duped_hash_no_id'])
- expect(parsed_tree['duped_hash_no_id']).not_to be(parsed_tree['array'][2]['duped_hash_no_id'])
- end
-
- it 'keeps single entries intact' do
- expect(parsed_tree['simple']).to eq(42)
- expect(parsed_tree['nested']['array']).to eq(["don't touch"])
- end
- end
-end
diff --git a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
deleted file mode 100644
index d62f5725f9e..00000000000
--- a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::ImportExport::RelationRenameService do
- include ImportExport::CommonUtil
-
- let(:renames) do
- {
- 'example_relation1' => 'new_example_relation1',
- 'example_relation2' => 'new_example_relation2'
- }
- end
-
- let(:user) { create(:admin) }
- let(:group) { create(:group, :nested) }
- let!(:project) { create(:project, :builds_disabled, :issues_disabled, group: group, name: 'project', path: 'project') }
- let(:shared) { project.import_export_shared }
-
- before do
- stub_const("#{described_class}::RENAMES", renames)
- end
-
- context 'when importing' do
- let(:project_tree_restorer) { Gitlab::ImportExport::ProjectTreeRestorer.new(user: user, shared: shared, project: project) }
- let(:file_content) { IO.read(File.join(shared.export_path, 'project.json')) }
- let(:json_file) { ActiveSupport::JSON.decode(file_content) }
-
- before do
- setup_import_export_config('complex')
-
- allow(ActiveSupport::JSON).to receive(:decode).and_call_original
- allow(ActiveSupport::JSON).to receive(:decode).with(file_content).and_return(json_file)
- end
-
- context 'when the file has only old relationship names' do
- # Configuring the json as an old version exported file, with only
- # the previous association with the old name
- before do
- renames.each do |old_name, _|
- json_file[old_name.to_s] = []
- end
- end
-
- it 'renames old relationships to the new name' do
- expect(json_file.keys).to include(*renames.keys)
-
- project_tree_restorer.restore
-
- expect(json_file.keys).to include(*renames.values)
- expect(json_file.keys).not_to include(*renames.keys)
- end
- end
-
- context 'when the file has both the old and new relationships' do
- # Configuring the json as the new version exported file, with both
- # the old association name and the new one
- before do
- renames.each do |old_name, new_name|
- json_file[old_name.to_s] = [1]
- json_file[new_name.to_s] = [2]
- end
- end
-
- it 'uses the new relationships and removes the old ones from the hash' do
- expect(json_file.keys).to include(*renames.keys)
-
- project_tree_restorer.restore
-
- expect(json_file.keys).to include(*renames.values)
- expect(json_file.values_at(*renames.values).flatten.uniq.first).to eq 2
- expect(json_file.keys).not_to include(*renames.keys)
- end
- end
-
- context 'when the file has only new relationship names' do
- # Configuring the json as the future version exported file, with only
- # the new association name
- before do
- renames.each do |_, new_name|
- json_file[new_name.to_s] = []
- end
- end
-
- it 'uses the new relationships' do
- expect(json_file.keys).not_to include(*renames.keys)
-
- project_tree_restorer.restore
-
- expect(json_file.keys).to include(*renames.values)
- end
- end
- end
-
- context 'when exporting' do
- let(:export_content_path) { project_tree_saver.full_path }
- let(:export_content_hash) { ActiveSupport::JSON.decode(File.read(export_content_path)) }
- let(:injected_hash) { renames.values.product([{}]).to_h }
- let(:relation_tree_saver) { Gitlab::ImportExport::RelationTreeSaver.new }
-
- let(:project_tree_saver) do
- Gitlab::ImportExport::ProjectTreeSaver.new(
- project: project, current_user: user, shared: shared)
- end
-
- before do
- allow(project_tree_saver).to receive(:tree_saver).and_return(relation_tree_saver)
- end
-
- it 'adds old relationships to the exported file' do
- # we inject relations with new names that should be rewritten
- expect(relation_tree_saver).to receive(:serialize).and_wrap_original do |method, *args|
- method.call(*args).merge(injected_hash)
- end
-
- expect(project_tree_saver.save).to eq(true)
-
- expect(export_content_hash.keys).to include(*renames.keys)
- expect(export_content_hash.keys).to include(*renames.values)
- end
- end
-end
diff --git a/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
index edb2c0a131a..578418998c0 100644
--- a/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_tree_restorer_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# This spec is a lightweight version of:
-# * project_tree_restorer_spec.rb
+# * project/tree_restorer_spec.rb
#
# In depth testing is being done in the above specs.
# This spec tests that restore project works
@@ -25,7 +25,7 @@ describe Gitlab::ImportExport::RelationTreeRestorer do
described_class.new(
user: user,
shared: shared,
- tree_hash: tree_hash,
+ relation_reader: relation_reader,
importable: importable,
object_builder: object_builder,
members_mapper: members_mapper,
@@ -36,14 +36,7 @@ describe Gitlab::ImportExport::RelationTreeRestorer do
subject { relation_tree_restorer.restore }
- context 'when restoring a project' do
- let(:path) { 'spec/fixtures/lib/gitlab/import_export/complex/project.json' }
- let(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') }
- let(:object_builder) { Gitlab::ImportExport::GroupProjectObjectBuilder }
- let(:relation_factory) { Gitlab::ImportExport::ProjectRelationFactory }
- let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) }
- let(:tree_hash) { importable_hash }
-
+ shared_examples 'import project successfully' do
it 'restores project tree' do
expect(subject).to eq(true)
end
@@ -66,4 +59,18 @@ describe Gitlab::ImportExport::RelationTreeRestorer do
end
end
end
+
+ context 'when restoring a project' do
+ let(:path) { 'spec/fixtures/lib/gitlab/import_export/complex/project.json' }
+ let(:importable) { create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project') }
+ let(:object_builder) { Gitlab::ImportExport::Project::ObjectBuilder }
+ let(:relation_factory) { Gitlab::ImportExport::Project::RelationFactory }
+ let(:reader) { Gitlab::ImportExport::Reader.new(shared: shared) }
+
+ context 'using legacy reader' do
+ let(:relation_reader) { Gitlab::ImportExport::JSON::LegacyReader::File.new(path, reader.project_relation_names) }
+
+ it_behaves_like 'import project successfully'
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index fc1f782bfdd..a95d661ec3c 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::RepoSaver do
describe 'bundle a project Git repo' do
- set(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 807b017a67c..91b88349ee0 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -72,6 +72,7 @@ Note:
- resolved_by_push
- discussion_id
- original_discussion_id
+- confidential
LabelLink:
- id
- target_type
@@ -133,6 +134,7 @@ Releases::Link:
- id
- url
- name
+- filepath
- created_at
- updated_at
ProjectMember:
@@ -270,6 +272,8 @@ MergeRequest::Metrics:
- diff_size
- modified_paths_size
- commits_count
+- first_approved_at
+- first_reassigned_at
Ci::Pipeline:
- id
- project_id
@@ -455,6 +459,7 @@ Service:
- active
- properties
- template
+- instance
- push_events
- issues_events
- commit_events
@@ -769,7 +774,9 @@ DesignManagement::Design:
- project_id
- filename
DesignManagement::Action:
+- id
- event
+- image_v432x230
DesignManagement::Version:
- id
- created_at
@@ -827,3 +834,6 @@ Epic:
- start_date_sourcing_epic_id
- due_date_sourcing_epic_id
- health_status
+EpicIssue:
+ - id
+ - relative_position
diff --git a/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
new file mode 100644
index 00000000000..d72d41ddf38
--- /dev/null
+++ b/spec/lib/gitlab/import_export/snippet_repo_restorer_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::SnippetRepoRestorer do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, namespace: user.namespace) }
+ let(:snippet) { create(:project_snippet, project: project, author: user) }
+
+ let(:shared) { project.import_export_shared }
+ let(:exporter) { Gitlab::ImportExport::SnippetsRepoSaver.new(project: project, shared: shared, current_user: user) }
+ let(:restorer) do
+ described_class.new(user: user,
+ shared: shared,
+ snippet: snippet,
+ path_to_bundle: snippet_bundle_path)
+ end
+
+ after do
+ FileUtils.rm_rf(shared.export_path)
+ end
+
+ shared_examples 'no bundle file present' do
+ it 'creates the repository from the database content' do
+ expect(snippet.repository_exists?).to be_falsey
+
+ aggregate_failures do
+ expect(restorer.restore).to be_truthy
+
+ expect(snippet.repository_exists?).to be_truthy
+ expect(snippet.snippet_repository).not_to be_nil
+
+ blob = snippet.repository.blob_at('HEAD', snippet.file_name)
+ expect(blob).not_to be_nil
+ expect(blob.data).to eq(snippet.content)
+ end
+ end
+ end
+
+ context 'when the snippet does not have a bundle file path' do
+ let(:snippet_bundle_path) { nil }
+
+ it_behaves_like 'no bundle file present'
+ end
+
+ context 'when the snippet bundle path is not present' do
+ let(:snippet_bundle_path) { 'foo' }
+
+ it_behaves_like 'no bundle file present'
+ end
+
+ context 'when the snippet bundle exists' do
+ let!(:snippet_with_repo) { create(:project_snippet, :repository, project: project) }
+ let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
+ let(:snippet_bundle_path) { File.join(bundle_path, "#{snippet_with_repo.hexdigest}.bundle") }
+ let(:result) { exporter.save }
+
+ it 'creates the repository from the bundle' do
+ expect(exporter.save).to be_truthy
+
+ expect(snippet.repository_exists?).to be_falsey
+ expect(snippet.snippet_repository).to be_nil
+ expect(snippet.repository).to receive(:create_from_bundle).and_call_original
+
+ expect(restorer.restore).to be_truthy
+ expect(snippet.repository_exists?).to be_truthy
+ expect(snippet.snippet_repository).not_to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
new file mode 100644
index 00000000000..7ad1ff213a1
--- /dev/null
+++ b/spec/lib/gitlab/import_export/snippet_repo_saver_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::SnippetRepoSaver do
+ describe 'bundle a project Git repo' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, namespace: user.namespace) }
+ let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
+ let(:shared) { project.import_export_shared }
+ let(:bundler) { described_class.new(project: project, shared: shared, repository: snippet.repository) }
+ let(:bundle_path) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
+
+ around do |example|
+ FileUtils.mkdir_p(bundle_path)
+ example.run
+ ensure
+ FileUtils.rm_rf(bundle_path)
+ end
+
+ context 'with project snippet' do
+ it 'bundles the repo successfully' do
+ aggregate_failures do
+ expect(bundler.save).to be_truthy
+ expect(Dir.empty?(bundle_path)).to be_falsey
+ end
+ end
+
+ context 'when snippet does not have a repository' do
+ let(:snippet) { build(:personal_snippet) }
+
+ it 'returns true' do
+ expect(bundler.save).to be_truthy
+ end
+
+ it 'does not create any file' do
+ aggregate_failures do
+ expect(snippet.repository).not_to receive(:bundle_to_disk)
+
+ bundler.save
+
+ expect(Dir.empty?(bundle_path)).to be_truthy
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
new file mode 100644
index 00000000000..242f6f6b58c
--- /dev/null
+++ b/spec/lib/gitlab/import_export/snippets_repo_restorer_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::SnippetsRepoRestorer do
+ include GitHelpers
+
+ describe 'bundle a snippet Git repo' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, namespace: user.namespace) }
+ let_it_be(:snippet_with_repo) { create(:project_snippet, :repository, project: project, author: user) }
+ let_it_be(:snippet_without_repo) { create(:project_snippet, project: project, author: user) }
+
+ let(:shared) { project.import_export_shared }
+ let(:exporter) { Gitlab::ImportExport::SnippetsRepoSaver.new(current_user: user, project: project, shared: shared) }
+ let(:bundle_dir) { ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path) }
+ let(:restorer) do
+ described_class.new(user: user,
+ shared: shared,
+ project: project)
+ end
+ let(:service) { instance_double(Gitlab::ImportExport::SnippetRepoRestorer) }
+
+ before do
+ exporter.save
+ end
+
+ after do
+ FileUtils.rm_rf(shared.export_path)
+ end
+
+ it 'calls SnippetRepoRestorer per each snippet with the bundle path' do
+ allow(service).to receive(:restore).and_return(true)
+
+ expect(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet_with_repo, path_to_bundle: bundle_path(snippet_with_repo))).and_return(service)
+ expect(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet_without_repo, path_to_bundle: bundle_path(snippet_without_repo))).and_return(service)
+
+ expect(restorer.restore).to be_truthy
+ end
+
+ context 'when one snippet cannot be saved' do
+ it 'returns false and do not process other snippets' do
+ allow(Gitlab::ImportExport::SnippetRepoRestorer).to receive(:new).with(hash_including(snippet: snippet_with_repo)).and_return(service)
+ allow(service).to receive(:restore).and_return(false)
+
+ expect(Gitlab::ImportExport::SnippetRepoRestorer).not_to receive(:new).with(hash_including(snippet: snippet_without_repo))
+ expect(restorer.restore).to be_falsey
+ end
+ end
+
+ def bundle_path(snippet)
+ File.join(bundle_dir, ::Gitlab::ImportExport.snippet_repo_bundle_filename_for(snippet))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
new file mode 100644
index 00000000000..5332990a975
--- /dev/null
+++ b/spec/lib/gitlab/import_export/snippets_repo_saver_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::SnippetsRepoSaver do
+ describe 'bundle a project Git repo' do
+ let_it_be(:user) { create(:user) }
+ let!(:project) { create(:project) }
+ let(:shared) { project.import_export_shared }
+ let(:bundler) { described_class.new(current_user: user, project: project, shared: shared) }
+
+ after do
+ FileUtils.rm_rf(shared.export_path)
+ end
+
+ it 'creates the snippet bundles dir if not exists' do
+ snippets_dir = ::Gitlab::ImportExport.snippets_repo_bundle_path(shared.export_path)
+ expect(Dir.exist?(snippets_dir)).to be_falsey
+
+ bundler.save
+
+ expect(Dir.exist?(snippets_dir)).to be_truthy
+ end
+
+ context 'when project does not have any snippet' do
+ it 'does not perform any action' do
+ expect(Gitlab::ImportExport::SnippetRepoSaver).not_to receive(:new)
+
+ bundler.save
+ end
+ end
+
+ context 'when project has snippets' do
+ let!(:snippet1) { create(:project_snippet, :repository, project: project, author: user) }
+ let!(:snippet2) { create(:project_snippet, project: project, author: user) }
+ let(:service) { instance_double(Gitlab::ImportExport::SnippetRepoSaver) }
+
+ it 'calls the SnippetRepoSaver for each snippet' do
+ allow(Gitlab::ImportExport::SnippetRepoSaver).to receive(:new).and_return(service)
+ expect(service).to receive(:save).and_return(true).twice
+
+ bundler.save
+ end
+
+ context 'when one snippet cannot be saved' do
+ it 'returns false and do not process other snippets' do
+ allow(Gitlab::ImportExport::SnippetRepoSaver).to receive(:new).with(hash_including(repository: snippet1.repository)).and_return(service)
+ allow(service).to receive(:save).and_return(false)
+
+ expect(Gitlab::ImportExport::SnippetRepoSaver).not_to receive(:new).with(hash_including(repository: snippet2.repository))
+ expect(bundler.save).to be_falsey
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 59a59223d8d..264272d2026 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -4,8 +4,8 @@ require 'spec_helper'
describe Gitlab::ImportExport::WikiRepoSaver do
describe 'bundle a wiki Git repo' do
- set(:user) { create(:user) }
- let!(:project) { create(:project, :wiki_repo) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :wiki_repo) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { project.import_export_shared }
let(:wiki_bundler) { described_class.new(project: project, shared: shared) }
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index f5a6ea4d5b0..2dd45d18ee9 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -89,6 +89,17 @@ describe Gitlab::IncomingEmail do
it 'does not match emails with extra bits' do
expect(described_class.key_from_address('somereplies+somekey@example.com.someotherdomain.com')).to be nil
end
+
+ context 'when a custom wildcard address is used' do
+ let(:wildcard_address) { 'custom.address+%{key}@example.com' }
+
+ it 'finds key if email matches address pattern' do
+ key = described_class.key_from_address(
+ 'custom.address+foo@example.com', wildcard_address: wildcard_address
+ )
+ expect(key).to eq('foo')
+ end
+ end
end
context 'self.key_from_fallback_message_id' do
diff --git a/spec/lib/gitlab/job_waiter_spec.rb b/spec/lib/gitlab/job_waiter_spec.rb
index efa7fd4b975..da6a6a9149b 100644
--- a/spec/lib/gitlab/job_waiter_spec.rb
+++ b/spec/lib/gitlab/job_waiter_spec.rb
@@ -37,5 +37,40 @@ describe Gitlab::JobWaiter do
expect(result).to contain_exactly('a')
end
+
+ context 'when a label is provided' do
+ let(:waiter) { described_class.new(2, worker_label: 'Foo') }
+ let(:started_total) { double(:started_total) }
+ let(:timeouts_total) { double(:timeouts_total) }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter)
+ .with(described_class::STARTED_METRIC, anything)
+ .and_return(started_total)
+
+ allow(Gitlab::Metrics).to receive(:counter)
+ .with(described_class::TIMEOUTS_METRIC, anything)
+ .and_return(timeouts_total)
+ end
+
+ it 'increments just job_waiter_started_total when all jobs complete' do
+ expect(started_total).to receive(:increment).with(worker: 'Foo')
+ expect(timeouts_total).not_to receive(:increment)
+
+ described_class.notify(waiter.key, 'a')
+ described_class.notify(waiter.key, 'b')
+
+ result = nil
+ expect { Timeout.timeout(1) { result = waiter.wait(2) } }.not_to raise_error
+ end
+
+ it 'increments job_waiter_started_total and job_waiter_timeouts_total when it times out' do
+ expect(started_total).to receive(:increment).with(worker: 'Foo')
+ expect(timeouts_total).to receive(:increment).with(worker: 'Foo')
+
+ result = nil
+ expect { Timeout.timeout(2) { result = waiter.wait(1) } }.not_to raise_error
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb
index 9d986abb8dd..9379499f602 100644
--- a/spec/lib/gitlab/json_cache_spec.rb
+++ b/spec/lib/gitlab/json_cache_spec.rb
@@ -3,13 +3,12 @@
require 'spec_helper'
describe Gitlab::JsonCache do
+ let_it_be(:broadcast_message) { create(:broadcast_message) }
let(:backend) { double('backend').as_null_object }
let(:namespace) { 'geo' }
let(:key) { 'foo' }
let(:expanded_key) { "#{namespace}:#{key}:#{Gitlab::VERSION}:#{Rails.version}" }
- set(:broadcast_message) { create(:broadcast_message) }
-
subject(:cache) { described_class.new(namespace: namespace, backend: backend) }
describe '#active?' do
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index e493acd7bad..8147990ecc3 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Gitlab::Kubernetes::Helm::Api do
+describe Gitlab::Kubernetes::Helm::API do
let(:client) { double('kubernetes client') }
let(:helm) { described_class.new(client) }
let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 24a734a2915..3c62219a9a5 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -32,7 +32,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
- expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.1-kube-1.13.12')
+ expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.16.3-kube-1.13.12')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb
index d44a803410f..467b10e21b1 100644
--- a/spec/lib/gitlab/kubernetes/namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb
@@ -92,12 +92,14 @@ describe Gitlab::Kubernetes::Namespace do
it 'logs the error' do
expect(subject.send(:logger)).to receive(:error).with(
hash_including(
- exception: 'Kubeclient::HttpError',
+ exception: {
+ class: 'Kubeclient::HttpError',
+ message: 'system failure'
+ },
status_code: 500,
namespace: 'a_namespace',
class_name: 'Gitlab::Kubernetes::Namespace',
- event: :failed_to_create_namespace,
- message: 'system failure'
+ event: :failed_to_create_namespace
)
)
diff --git a/spec/lib/gitlab/language_detection_spec.rb b/spec/lib/gitlab/language_detection_spec.rb
index f558ce0d527..770fe2f80db 100644
--- a/spec/lib/gitlab/language_detection_spec.rb
+++ b/spec/lib/gitlab/language_detection_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
describe Gitlab::LanguageDetection do
- set(:project) { create(:project, :repository) }
- set(:ruby) { create(:programming_language, name: 'Ruby') }
- set(:haskell) { create(:programming_language, name: 'Haskell') }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:ruby) { create(:programming_language, name: 'Ruby') }
+ let_it_be(:haskell) { create(:programming_language, name: 'Haskell') }
let(:repository) { project.repository }
let(:detection) do
[{ value: 66.63, label: "Ruby", color: "#701516", highlight: "#701516" },
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index 7fef763f64d..af0bffa91a5 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -45,7 +45,7 @@ describe Gitlab::LegacyGithubImport::Importer do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
- allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
+ allow(project.wiki.repository).to receive(:import_repository).and_raise(Gitlab::Git::CommandError)
allow_any_instance_of(Octokit::Client).to receive(:user).and_return(octocat)
allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
@@ -169,14 +169,10 @@ describe Gitlab::LegacyGithubImport::Importer do
errors: [
{ type: :label, url: "#{api_root}/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
{ type: :issue, url: "#{api_root}/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank" },
- { type: :wiki, errors: "Gitlab::Shell::Error" }
+ { type: :wiki, errors: "Gitlab::Git::CommandError" }
]
}
- unless project.gitea_import?
- error[:errors] << { type: :release, url: "#{api_root}/repos/octocat/Hello-World/releases/2", errors: "Validation failed: Description can't be blank" }
- end
-
described_class.new(project).execute
expect(project.import_state.last_error).to eq error.to_json
diff --git a/spec/lib/gitlab/lograge/custom_options_spec.rb b/spec/lib/gitlab/lograge/custom_options_spec.rb
new file mode 100644
index 00000000000..48d06283b7a
--- /dev/null
+++ b/spec/lib/gitlab/lograge/custom_options_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Lograge::CustomOptions do
+ describe '.call' do
+ let(:params) do
+ {
+ 'controller' => 'ApplicationController',
+ 'action' => 'show',
+ 'format' => 'html',
+ 'a' => 'b'
+ }
+ end
+
+ let(:event) do
+ ActiveSupport::Notifications::Event.new(
+ 'test',
+ 1,
+ 2,
+ 'transaction_id',
+ { params: params, user_id: 'test' }
+ )
+ end
+
+ subject { described_class.call(event) }
+
+ it 'ignores some parameters' do
+ param_keys = subject[:params].map { |param| param[:key] }
+
+ expect(param_keys).not_to include(*described_class::IGNORE_PARAMS)
+ end
+
+ it 'formats the parameters' do
+ expect(subject[:params]).to eq([{ key: 'a', value: 'b' }])
+ end
+
+ it 'adds the current time' do
+ travel_to(5.days.ago) do
+ expected_time = Time.now.utc.iso8601(3)
+
+ expect(subject[:time]).to eq(expected_time)
+ end
+ end
+
+ it 'adds the user id' do
+ expect(subject[:user_id]).to eq('test')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
index 2d3b61e61ce..d772b0c7a5f 100644
--- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
- set(:project) { create(:project) }
- set(:user) { create(:user) }
- set(:environment) { create(:environment, project: project) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:environment) { create(:environment, project: project) }
before do
project.add_maintainer(user)
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
index 5c2ec6dae6b..e41004bb57e 100644
--- a/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/stages/grafana_formatter_spec.rb
@@ -11,8 +11,9 @@ describe Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter do
describe '#transform!' do
let(:grafana_dashboard) { JSON.parse(fixture_file('grafana/simplified_dashboard_response.json'), symbolize_names: true) }
let(:datasource) { JSON.parse(fixture_file('grafana/datasource_response.json'), symbolize_names: true) }
+ let(:expected_dashboard) { JSON.parse(fixture_file('grafana/expected_grafana_embed.json'), symbolize_names: true) }
- let(:dashboard) { described_class.new(project, {}, params).transform! }
+ subject(:dashboard) { described_class.new(project, {}, params).transform! }
let(:params) do
{
@@ -23,83 +24,34 @@ describe Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter do
end
context 'when the query and resources are configured correctly' do
- let(:expected_dashboard) { JSON.parse(fixture_file('grafana/expected_grafana_embed.json'), symbolize_names: true) }
-
- it 'generates a gitlab-yml formatted dashboard' do
- expect(dashboard).to eq(expected_dashboard)
- end
+ it { is_expected.to eq expected_dashboard }
end
- context 'when the inputs are invalid' do
- shared_examples_for 'processing error' do
- it 'raises a processing error' do
- expect { dashboard }
- .to raise_error(Gitlab::Metrics::Dashboard::Stages::InputFormatValidator::DashboardProcessingError)
- end
- end
-
- context 'when the datasource is not proxyable' do
- before do
- params[:datasource][:access] = 'not-proxy'
- end
-
- it_behaves_like 'processing error'
+ context 'when a panelId is not included in the grafana_url' do
+ before do
+ params[:grafana_url].gsub('&panelId=8', '')
end
- context 'when query param "panelId" is not specified' do
- before do
- params[:grafana_url].gsub!('panelId=8', '')
- end
-
- it_behaves_like 'processing error'
- end
-
- context 'when query param "from" is not specified' do
- before do
- params[:grafana_url].gsub!('from=1570397739557', '')
- end
-
- it_behaves_like 'processing error'
- end
+ it { is_expected.to eq expected_dashboard }
- context 'when query param "to" is not specified' do
+ context 'when there is also no valid panel in the dashboard' do
before do
- params[:grafana_url].gsub!('to=1570484139557', '')
+ params[:grafana_dashboard][:dashboard][:panels] = []
end
- it_behaves_like 'processing error'
- end
-
- context 'when the panel is not a graph' do
- before do
- params[:grafana_dashboard][:dashboard][:panels][0][:type] = 'singlestat'
+ it 'raises a processing error' do
+ expect { dashboard }.to raise_error(::Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError)
end
-
- it_behaves_like 'processing error'
end
+ end
- context 'when the panel is not a line graph' do
- before do
- params[:grafana_dashboard][:dashboard][:panels][0][:lines] = false
- end
-
- it_behaves_like 'processing error'
- end
-
- context 'when the query dashboard includes undefined variables' do
- before do
- params[:grafana_url].gsub!('&var-instance=localhost:9121', '')
- end
-
- it_behaves_like 'processing error'
+ context 'when an input is invalid' do
+ before do
+ params[:datasource][:access] = 'not-proxy'
end
- context 'when the expression contains unsupported global variables' do
- before do
- params[:grafana_dashboard][:dashboard][:panels][0][:targets][0][:expr] = 'sum(important_metric[$__interval_ms])'
- end
-
- it_behaves_like 'processing error'
+ it 'raises a processing error' do
+ expect { dashboard }.to raise_error(::Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError)
end
end
end
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index 99c2a364dfc..43a489f6df0 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -89,6 +89,13 @@ describe Gitlab::Middleware::Go do
it 'returns the full project path' do
expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
end
+
+ context 'with an empty ssh_user' do
+ it 'returns the full project path' do
+ allow(Gitlab.config.gitlab_shell).to receive(:ssh_user).and_return('')
+ expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
+ end
+ end
end
context 'without access to the project' do
@@ -234,7 +241,9 @@ describe Gitlab::Middleware::Go do
def expect_response_with_path(response, protocol, path, branch)
repository_url = case protocol
when :ssh
- "ssh://#{Gitlab.config.gitlab.user}@#{Gitlab.config.gitlab.host}/#{path}.git"
+ shell = Gitlab.config.gitlab_shell
+ user = "#{shell.ssh_user}@" unless shell.ssh_user.empty?
+ "ssh://#{user}#{shell.ssh_host}/#{path}.git"
when :http, nil
"http://#{Gitlab.config.gitlab.host}/#{path}.git"
end
diff --git a/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb b/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb
new file mode 100644
index 00000000000..36405daed5a
--- /dev/null
+++ b/spec/lib/gitlab/omniauth_logging/json_formatter_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::OmniauthLogging::JSONFormatter do
+ it "generates log in json format" do
+ Timecop.freeze(Time.utc(2019, 12, 04, 9, 10, 11, 123456)) do
+ expect(subject.call(:info, Time.now, 'omniauth', 'log message'))
+ .to eq %Q({"severity":"info","timestamp":"2019-12-04T09:10:11.123Z","pid":#{Process.pid},"progname":"omniauth","message":"log message"}\n)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 3cbcae4cdeb..8dabe5a756b 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -411,4 +411,37 @@ describe Gitlab::PathRegex do
it { is_expected.not_to match('git lab') }
it { is_expected.not_to match('gitlab.git') }
end
+
+ shared_examples 'invalid snippet routes' do
+ it { is_expected.not_to match('gitlab-org/gitlab/snippets/1.git') }
+ it { is_expected.not_to match('snippets/1.git') }
+ it { is_expected.not_to match('gitlab-org/gitlab/snippets/') }
+ it { is_expected.not_to match('/gitlab-org/gitlab/snippets/1') }
+ it { is_expected.not_to match('gitlab-org/gitlab/snippets/foo') }
+ it { is_expected.not_to match('root/snippets/1') }
+ it { is_expected.not_to match('/snippets/1') }
+ it { is_expected.not_to match('snippets/') }
+ it { is_expected.not_to match('snippets/foo') }
+ end
+
+ describe '.full_snippets_repository_path_regex' do
+ subject { described_class.full_snippets_repository_path_regex }
+
+ it { is_expected.to match('gitlab-org/gitlab/snippets/1') }
+ it { is_expected.to match('snippets/1') }
+
+ it_behaves_like 'invalid snippet routes'
+ end
+
+ describe '.personal_and_project_snippets_path_regex' do
+ subject { %r{\A#{described_class.personal_and_project_snippets_path_regex}\z} }
+
+ it { is_expected.to match('gitlab-org/gitlab/snippets') }
+ it { is_expected.to match('snippets') }
+
+ it { is_expected.not_to match('gitlab-org/gitlab/snippets/1') }
+ it { is_expected.not_to match('snippets/1') }
+
+ it_behaves_like 'invalid snippet routes'
+ end
end
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
index 14a5d40d445..b2a63e4f026 100644
--- a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
- set(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
let(:redis) { Gitlab::Redis::Cache }
subject(:map) { described_class.new(project) }
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
index 06ed264e781..79f11d7fae6 100644
--- a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::PhabricatorImport::Issues::TaskImporter do
- set(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
let(:task) do
Gitlab::PhabricatorImport::Representation::Task.new(
{
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 0186d48fd1b..6440f74a49a 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -195,6 +195,7 @@ describe Gitlab::Profiler do
describe '.print_by_total_time' do
let(:stdout) { StringIO.new }
+ let(:regexp) { /^\s+\d+\.\d+\s+(\d+\.\d+)/ }
let(:output) do
stdout.rewind
@@ -202,6 +203,8 @@ describe Gitlab::Profiler do
end
let_it_be(:result) do
+ Thread.new { sleep 1 }
+
RubyProf.profile do
sleep 0.1
1.to_s
@@ -215,14 +218,19 @@ describe Gitlab::Profiler do
it 'prints a profile result sorted by total time' do
described_class.print_by_total_time(result)
- total_times =
- output
- .scan(/^\s+\d+\.\d+\s+(\d+\.\d+)/)
- .map { |(total)| total.to_f }
-
expect(output).to include('Kernel#sleep')
- expect(total_times).to eq(total_times.sort.reverse)
- expect(total_times).not_to eq(total_times.uniq)
+
+ thread_profiles = output.split('Sort by: total_time').select { |x| x =~ regexp }
+
+ thread_profiles.each do |profile|
+ total_times =
+ profile
+ .scan(regexp)
+ .map { |(total)| total.to_f }
+
+ expect(total_times).to eq(total_times.sort.reverse)
+ expect(total_times).not_to eq(total_times.uniq)
+ end
end
it 'accepts a max_percent option' do
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index 7b282433061..d1c441c8605 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -43,6 +43,78 @@ describe Gitlab::ProjectAuthorizations do
end
end
+ context 'unapproved access request' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+
+ subject(:mapping) { map_access_levels(authorizations) }
+
+ context 'group membership' do
+ let!(:group_project) { create(:project, namespace: group) }
+
+ before do
+ create(:group_member, :developer, :access_request, user: user, group: group)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[group_project.id]).to be_nil
+ end
+ end
+
+ context 'inherited group membership' do
+ let!(:sub_group) { create(:group, parent: group) }
+ let!(:sub_group_project) { create(:project, namespace: sub_group) }
+
+ before do
+ create(:group_member, :developer, :access_request, user: user, group: group)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[sub_group_project.id]).to be_nil
+ end
+ end
+
+ context 'project membership' do
+ let!(:group_project) { create(:project, namespace: group) }
+
+ before do
+ create(:project_member, :developer, :access_request, user: user, project: group_project)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[group_project.id]).to be_nil
+ end
+ end
+
+ context 'shared group' do
+ let!(:shared_group) { create(:group) }
+ let!(:shared_group_project) { create(:project, namespace: shared_group) }
+
+ before do
+ create(:group_group_link, shared_group: shared_group, shared_with_group: group)
+ create(:group_member, :developer, :access_request, user: user, group: group)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[shared_group_project.id]).to be_nil
+ end
+ end
+
+ context 'shared project' do
+ let!(:another_group) { create(:group) }
+ let!(:shared_project) { create(:project, namespace: another_group) }
+
+ before do
+ create(:project_group_link, group: group, project: shared_project)
+ create(:group_member, :developer, :access_request, user: user, group: group)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[shared_project.id]).to be_nil
+ end
+ end
+ end
+
context 'with nested groups' do
let(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index a2e3e2146f3..3948e53bc17 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -13,6 +13,7 @@ describe Gitlab::ProjectTemplate do
described_class.new('dotnetcore', '.NET Core', 'A .NET Core console application template, customizable for any .NET Core project', 'https://gitlab.com/gitlab-org/project-templates/dotnetcore'),
described_class.new('android', 'Android', 'A ready-to-go template for use with Android apps.', 'https://gitlab.com/gitlab-org/project-templates/android'),
described_class.new('gomicro', 'Go Micro', 'Go Micro is a framework for micro service development.', 'https://gitlab.com/gitlab-org/project-templates/go-micro'),
+ described_class.new('gatsby', 'Pages/Gatsby', 'Everything you need to get started using a Gatsby site.', 'https://gitlab.com/pages/gatsby'),
described_class.new('hugo', 'Pages/Hugo', 'Everything you need to get started using a Hugo Pages site.', 'https://gitlab.com/pages/hugo'),
described_class.new('jekyll', 'Pages/Jekyll', 'Everything you need to get started using a Jekyll Pages site.', 'https://gitlab.com/pages/jekyll'),
described_class.new('plainhtml', 'Pages/Plain HTML', 'Everything you need to get started using a plain HTML Pages site.', 'https://gitlab.com/pages/plain-html'),
@@ -73,7 +74,7 @@ describe Gitlab::ProjectTemplate do
end
describe 'validate all templates' do
- set(:admin) { create(:admin) }
+ let_it_be(:admin) { create(:admin) }
described_class.all.each do |template|
it "#{template.name} has a valid archive" do
diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb
index 849265de513..d8f8a2b7e7c 100644
--- a/spec/lib/gitlab/prometheus/query_variables_spec.rb
+++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb
@@ -11,6 +11,10 @@ describe Gitlab::Prometheus::QueryVariables do
subject { described_class.call(environment) }
it { is_expected.to include(ci_environment_slug: slug) }
+ it { is_expected.to include(ci_project_name: project.name) }
+ it { is_expected.to include(ci_project_namespace: project.namespace.name) }
+ it { is_expected.to include(ci_project_path: project.full_path) }
+ it { is_expected.to include(ci_environment_name: environment.name) }
it do
is_expected.to include(environment_filter:
diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
index 2536e4a372b..6ea597bf01e 100644
--- a/spec/lib/gitlab/quick_actions/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -216,6 +216,22 @@ describe Gitlab::QuickActions::Extractor do
expect(msg).to eq "hello\nworld\nthis is great? SHRUG"
end
+ it 'extracts and performs multiple substitution commands' do
+ msg = %(hello\nworld\n/reopen\n/shrug this is great?\n/shrug meh)
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to eq [['reopen'], ['shrug', 'this is great?'], %w(shrug meh)]
+ expect(msg).to eq "hello\nworld\nthis is great? SHRUG\nmeh SHRUG"
+ end
+
+ it 'does not extract substitution command in inline code' do
+ msg = %(hello\nworld\n/reopen\n`/tableflip this is great`?)
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to eq [['reopen']]
+ expect(msg).to eq "hello\nworld\n`/tableflip this is great`?"
+ end
+
it 'extracts and performs substitution commands case insensitive' do
msg = %(hello\nworld\n/reOpen\n/sHRuG this is great?)
msg, commands = extractor.extract_commands(msg)
@@ -275,6 +291,33 @@ describe Gitlab::QuickActions::Extractor do
expect(msg).to eq expected
end
+ it 'does not extract commands in multiline inline code on seperated rows' do
+ msg = "Hello\r\n`\r\nThis is some text\r\n/close\r\n/assign @user\r\n`\r\n\r\nWorld"
+ expected = msg.delete("\r")
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq expected
+ end
+
+ it 'does not extract commands in multiline inline code starting from text' do
+ msg = "Hello `This is some text\r\n/close\r\n/assign @user\r\n`\r\n\r\nWorld"
+ expected = msg.delete("\r")
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq expected
+ end
+
+ it 'does not extract commands in inline code' do
+ msg = "`This is some text\r\n/close\r\n/assign @user\r\n`\r\n\r\nWorld"
+ expected = msg.delete("\r")
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq expected
+ end
+
it 'limits to passed commands when they are passed' do
msg = <<~MSG.strip
Hello, we should only extract the commands passed
diff --git a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
index a09aca31cdc..d1a44e2feeb 100644
--- a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
@@ -7,6 +7,7 @@ describe Gitlab::QuickActions::SubstitutionDefinition do
<<EOF
Hello! Let's do this!
/sub_name I like this stuff
+/sub_name second substitution
EOF
end
@@ -24,6 +25,7 @@ EOF
expect(subject.perform_substitution(self, content)).to eq <<EOF
Hello! Let's do this!
I like this stuff foo
+/sub_name second substitution
EOF
end
end
diff --git a/spec/lib/gitlab/rate_limit_helpers_spec.rb b/spec/lib/gitlab/rate_limit_helpers_spec.rb
new file mode 100644
index 00000000000..5ab79a2bbfe
--- /dev/null
+++ b/spec/lib/gitlab/rate_limit_helpers_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::RateLimitHelpers, :clean_gitlab_redis_shared_state do
+ let(:limiter_class) do
+ Class.new do
+ include ::Gitlab::RateLimitHelpers
+
+ attr_reader :request
+
+ def initialize(request)
+ @request = request
+ end
+ end
+ end
+
+ let(:request) { instance_double(ActionDispatch::Request, request_method: 'GET', ip: '127.0.0.1', fullpath: '/') }
+ let(:class_instance) { limiter_class.new(request) }
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ describe '#archive_rate_limit_reached?' do
+ context 'with a user' do
+ it 'rate limits the user properly' do
+ 5.times do
+ expect(class_instance.archive_rate_limit_reached?(user, project)).to be_falsey
+ end
+
+ expect(class_instance.archive_rate_limit_reached?(user, project)).to be_truthy
+ end
+ end
+
+ context 'with an anonymous user' do
+ before do
+ stub_const('Gitlab::RateLimitHelpers::ARCHIVE_RATE_ANONYMOUS_THRESHOLD', 2)
+ end
+
+ it 'rate limits with higher limits' do
+ 2.times do
+ expect(class_instance.archive_rate_limit_reached?(nil, project)).to be_falsey
+ end
+
+ expect(class_instance.archive_rate_limit_reached?(nil, project)).to be_truthy
+ expect(class_instance.archive_rate_limit_reached?(user, project)).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
new file mode 100644
index 00000000000..a0d8f15ba1b
--- /dev/null
+++ b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ReactiveCacheSetCache, :clean_gitlab_redis_cache do
+ let_it_be(:project) { create(:project) }
+ let(:cache_prefix) { 'cache_prefix' }
+ let(:expires_in) { 10.minutes }
+ let(:cache) { described_class.new(expires_in: expires_in) }
+
+ describe '#cache_key' do
+ subject { cache.cache_key(cache_prefix) }
+
+ it 'includes the suffix' do
+ expect(subject).to eq "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:#{cache_prefix}:set"
+ end
+ end
+
+ describe '#read' do
+ subject { cache.read(cache_prefix) }
+
+ it { is_expected.to be_empty }
+
+ context 'after item added' do
+ before do
+ cache.write(cache_prefix, 'test_item')
+ end
+
+ it { is_expected.to contain_exactly('test_item') }
+ end
+ end
+
+ describe '#write' do
+ it 'writes the value to the cache' do
+ cache.write(cache_prefix, 'test_item')
+
+ expect(cache.read(cache_prefix)).to contain_exactly('test_item')
+ end
+
+ it 'sets the expiry of the set' do
+ cache.write(cache_prefix, 'test_item')
+
+ expect(cache.ttl(cache_prefix)).to be_within(1).of(expires_in.seconds)
+ end
+ end
+
+ describe '#clear_cache!', :use_clean_rails_redis_caching do
+ it 'deletes the cached items' do
+ # Cached key and value
+ Rails.cache.write('test_item', 'test_value')
+ # Add key to set
+ cache.write(cache_prefix, 'test_item')
+
+ expect(cache.read(cache_prefix)).to contain_exactly('test_item')
+ cache.clear_cache!(cache_prefix)
+
+ expect(cache.read(cache_prefix)).to be_empty
+ end
+ end
+
+ describe '#include?' do
+ subject { cache.include?(cache_prefix, 'test_item') }
+
+ it { is_expected.to be(false) }
+
+ context 'item added' do
+ before do
+ cache.write(cache_prefix, 'test_item')
+ end
+
+ it { is_expected.to be(true) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/reference_counter_spec.rb b/spec/lib/gitlab/reference_counter_spec.rb
index f9361d08faf..ae7b18ca007 100644
--- a/spec/lib/gitlab/reference_counter_spec.rb
+++ b/spec/lib/gitlab/reference_counter_spec.rb
@@ -2,38 +2,54 @@
require 'spec_helper'
-describe Gitlab::ReferenceCounter do
- let(:redis) { double('redis') }
- let(:reference_counter_key) { "git-receive-pack-reference-counter:project-1" }
+describe Gitlab::ReferenceCounter, :clean_gitlab_redis_shared_state do
let(:reference_counter) { described_class.new('project-1') }
- before do
- allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
+ describe '#increase' do
+ it 'increases and sets the expire time of a reference count for a path' do
+ expect { reference_counter.increase }.to change { reference_counter.value }.by(1)
+ expect(reference_counter.expires_in).to be_positive
+ expect(reference_counter.increase).to be(true)
+ end
end
- it 'increases and set the expire time of a reference count for a path' do
- expect(redis).to receive(:incr).with(reference_counter_key)
- expect(redis).to receive(:expire).with(reference_counter_key,
- described_class::REFERENCE_EXPIRE_TIME)
- expect(reference_counter.increase).to be(true)
+ describe '#decrease' do
+ it 'decreases the reference count for a path' do
+ reference_counter.increase
+
+ expect { reference_counter.decrease }.to change { reference_counter.value }.by(-1)
+ end
+
+ it 'warns if attempting to decrease a counter with a value of zero or less, and resets the counter' do
+ expect(Rails.logger).to receive(:warn).with("Reference counter for project-1" \
+ " decreased when its value was less than 1. Resetting the counter.")
+ expect { reference_counter.decrease }.not_to change { reference_counter.value }
+ end
end
- it 'decreases the reference count for a path' do
- allow(redis).to receive(:decr).and_return(0)
- expect(redis).to receive(:decr).with(reference_counter_key)
- expect(reference_counter.decrease).to be(true)
+ describe '#value' do
+ it 'get the reference count for a path' do
+ expect(reference_counter.value).to eq(0)
+
+ reference_counter.increase
+
+ expect(reference_counter.value).to eq(1)
+ end
end
- it 'warns if attempting to decrease a counter with a value of one or less, and resets the counter' do
- expect(redis).to receive(:decr).and_return(-1)
- expect(redis).to receive(:del)
- expect(Rails.logger).to receive(:warn).with("Reference counter for project-1" \
- " decreased when its value was less than 1. Reseting the counter.")
- expect(reference_counter.decrease).to be(true)
+ describe '#reset!' 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)
+ end
end
- it 'get the reference count for a path' do
- allow(redis).to receive(:get).and_return(1)
- expect(reference_counter.value).to be(1)
+ describe '#expires_in' do
+ it 'displays the expiration time in seconds' do
+ reference_counter.increase
+
+ expect(reference_counter.expires_in).to be_between(500, 600)
+ end
end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 0faaaa50621..a7961190ff1 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -225,6 +225,24 @@ describe Gitlab::ReferenceExtractor do
end
end
+ context 'with an inactive external issue tracker' do
+ let(:project) { create(:project) }
+ let!(:jira_service) { create(:jira_service, project: project, active: false) }
+ let(:issue) { create(:issue, project: project) }
+
+ context 'when GitLab issues are enabled' do
+ it 'returns only internal issue' do
+ subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}")
+ expect(subject.issues).to eq([issue])
+ end
+
+ it 'does not return any issue if the internal one does not exists' do
+ subject.analyze("JIRA-123 and FOOBAR-4567 and #999")
+ expect(subject.issues).to be_empty
+ end
+ end
+ end
+
context 'with a project with an underscore' do
let(:other_project) { create(:project, path: 'test_project') }
let(:issue) { create(:issue, project: other_project) }
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index cffd7cc89e7..68571b9de20 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -3,64 +3,75 @@
require 'spec_helper'
describe ::Gitlab::RepoPath do
- describe '.parse' do
- set(:project) { create(:project, :repository) }
+ include Gitlab::Routing
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:personal_snippet) { create(:personal_snippet) }
+ let_it_be(:project_snippet) { create(:project_snippet, project: project) }
+ let_it_be(:redirect_route) { 'foo/bar/baz' }
+ let_it_be(:redirect) { project.route.create_redirect(redirect_route) }
+ describe '.parse' do
context 'a repository storage path' do
- it 'parses a full repository path' do
- expect(described_class.parse(project.repository.full_path)).to eq([project, Gitlab::GlRepository::PROJECT, nil])
+ it 'parses a full repository project path' do
+ expect(described_class.parse(project.repository.full_path)).to eq([project, project, Gitlab::GlRepository::PROJECT, nil])
+ end
+
+ it 'parses a full wiki project path' do
+ expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, project, Gitlab::GlRepository::WIKI, nil])
+ end
+
+ it 'parses a personal snippet repository path' do
+ expect(described_class.parse("snippets/#{personal_snippet.id}")).to eq([personal_snippet, nil, Gitlab::GlRepository::SNIPPET, nil])
end
- it 'parses a full wiki path' do
- expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, Gitlab::GlRepository::WIKI, nil])
+ it 'parses a project snippet repository path' do
+ expect(described_class.parse("#{project.full_path}/snippets/#{project_snippet.id}")).to eq([project_snippet, project, Gitlab::GlRepository::SNIPPET, nil])
end
end
context 'a relative path' do
it 'parses a relative repository path' do
- expect(described_class.parse(project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil])
+ expect(described_class.parse(project.full_path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, nil])
end
it 'parses a relative wiki path' do
- expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, nil])
+ expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, project, Gitlab::GlRepository::WIKI, nil])
end
it 'parses a relative path starting with /' do
- expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil])
+ expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, nil])
end
context 'of a redirected project' do
- let(:redirect) { project.route.create_redirect('foo/bar') }
-
it 'parses a relative repository path' do
- expect(described_class.parse(redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar'])
+ expect(described_class.parse(redirect.path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, redirect_route])
end
it 'parses a relative wiki path' do
- expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, 'foo/bar.wiki'])
+ expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, project, Gitlab::GlRepository::WIKI, redirect_route])
end
it 'parses a relative path starting with /' do
- expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar'])
+ expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, redirect_route])
+ end
+
+ it 'parses a redirected project snippet repository path' do
+ expect(described_class.parse(redirect.path + "/snippets/#{project_snippet.id}.git")).to eq([project_snippet, project, Gitlab::GlRepository::SNIPPET, redirect_route])
end
end
end
- it "returns the default type for non existent paths" do
- _project, type, _redirected = described_class.parse("path/non-existent.git")
-
- expect(type).to eq(Gitlab::GlRepository.default_type)
+ it 'returns the default type for non existent paths' do
+ expect(described_class.parse('path/non-existent.git')).to eq([nil, nil, Gitlab::GlRepository.default_type, nil])
end
end
describe '.find_project' do
- let(:project) { create(:project) }
- let(:redirect) { project.route.create_redirect('foo/bar/baz') }
-
context 'when finding a project by its canonical path' do
context 'when the cases match' do
- it 'returns the project and false' do
- expect(described_class.find_project(project.full_path)).to eq([project, false])
+ it 'returns the project and nil' do
+ expect(described_class.find_project(project.full_path)).to eq([project, nil])
end
end
@@ -69,15 +80,45 @@ describe ::Gitlab::RepoPath do
# easy and safe to redirect someone to the correctly-cased URL. For git
# requests, we should accept wrongly-cased URLs because it is a pain to
# block people's git operations and force them to update remote URLs.
- it 'returns the project and false' do
- expect(described_class.find_project(project.full_path.upcase)).to eq([project, false])
+ it 'returns the project and nil' do
+ expect(described_class.find_project(project.full_path.upcase)).to eq([project, nil])
end
end
end
context 'when finding a project via a redirect' do
+ it 'returns the project and nil' do
+ expect(described_class.find_project(redirect.path)).to eq([project, redirect.path])
+ end
+ end
+ end
+
+ describe '.find_snippet' do
+ it 'extracts path and id from personal snippet route' do
+ expect(described_class.find_snippet("snippets/#{personal_snippet.id}")).to eq([personal_snippet, nil])
+ end
+
+ it 'extracts path and id from project snippet route' do
+ expect(described_class.find_snippet("#{project.full_path}/snippets/#{project_snippet.id}")).to eq([project_snippet, nil])
+ end
+
+ it 'returns nil for invalid snippet paths' do
+ aggregate_failures do
+ expect(described_class.find_snippet("snippets/#{project_snippet.id}")).to eq([nil, nil])
+ expect(described_class.find_snippet("#{project.full_path}/snippets/#{personal_snippet.id}")).to eq([nil, nil])
+ expect(described_class.find_snippet('')).to eq([nil, nil])
+ end
+ end
+
+ it 'returns nil for snippets not associated with the project' do
+ snippet = create(:project_snippet)
+
+ expect(described_class.find_snippet("#{project.full_path}/snippets/#{snippet.id}")).to eq([nil, nil])
+ end
+
+ context 'when finding a project snippet via a redirect' do
it 'returns the project and true' do
- expect(described_class.find_project(redirect.path)).to eq([project, true])
+ expect(described_class.find_snippet("#{redirect.path}/snippets/#{project_snippet.id}")).to eq([project_snippet, redirect.path])
end
end
end
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index b4fc504ea60..dba5ffc84c5 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -211,8 +211,7 @@ describe Gitlab::RepositoryCacheAdapter do
it 'expires the caches of the given methods' do
expect(cache).to receive(:expire).with(:rendered_readme)
expect(cache).to receive(:expire).with(:branch_names)
- expect(redis_set_cache).to receive(:expire).with(:rendered_readme)
- expect(redis_set_cache).to receive(:expire).with(:branch_names)
+ expect(redis_set_cache).to receive(:expire).with(:rendered_readme, :branch_names)
expect(redis_hash_cache).to receive(:delete).with(:rendered_readme)
expect(redis_hash_cache).to receive(:delete).with(:branch_names)
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
index e787288fc51..be31be761ad 100644
--- a/spec/lib/gitlab/repository_cache_spec.rb
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -12,19 +12,44 @@ describe Gitlab::RepositoryCache do
describe '#cache_key' do
subject { cache.cache_key(:foo) }
- it 'includes the namespace' do
- expect(subject).to eq "foo:#{namespace}"
+ shared_examples 'cache_key examples' do
+ it 'includes the namespace' do
+ expect(subject).to eq "foo:#{namespace}"
+ end
+
+ context 'with a given namespace' do
+ let(:extra_namespace) { 'my:data' }
+ let(:cache) do
+ described_class.new(repository, extra_namespace: extra_namespace,
+ backend: backend)
+ end
+
+ it 'includes the full namespace' do
+ expect(subject).to eq "foo:#{namespace}:#{extra_namespace}"
+ end
+ end
end
- context 'with a given namespace' do
- let(:extra_namespace) { 'my:data' }
- let(:cache) do
- described_class.new(repository, extra_namespace: extra_namespace,
- backend: backend)
+ describe 'project repository' do
+ it_behaves_like 'cache_key examples' do
+ let(:repository) { project.repository }
end
+ end
+
+ describe 'personal snippet repository' do
+ let_it_be(:personal_snippet) { create(:personal_snippet) }
+ let(:namespace) { repository.full_path }
+
+ it_behaves_like 'cache_key examples' do
+ let(:repository) { personal_snippet.repository }
+ end
+ end
+
+ describe 'project snippet repository' do
+ let_it_be(:project_snippet) { create(:project_snippet, project: project) }
- it 'includes the full namespace' do
- expect(subject).to eq "foo:#{namespace}:#{extra_namespace}"
+ it_behaves_like 'cache_key examples' do
+ let(:repository) { project_snippet.repository }
end
end
end
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
index de0f3602346..6221d6fb45f 100644
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -11,27 +11,98 @@ describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
describe '#cache_key' do
subject { cache.cache_key(:foo) }
- it 'includes the namespace' do
- is_expected.to eq("foo:#{namespace}:set")
+ shared_examples 'cache_key examples' do
+ it 'includes the namespace' do
+ is_expected.to eq("foo:#{namespace}:set")
+ end
+
+ context 'with a given namespace' do
+ let(:extra_namespace) { 'my:data' }
+ let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) }
+
+ it 'includes the full namespace' do
+ is_expected.to eq("foo:#{namespace}:#{extra_namespace}:set")
+ end
+ end
end
- context 'with a given namespace' do
- let(:extra_namespace) { 'my:data' }
- let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) }
+ describe 'project repository' do
+ it_behaves_like 'cache_key examples' do
+ let(:repository) { project.repository }
+ end
+ end
+
+ describe 'personal snippet repository' do
+ let_it_be(:personal_snippet) { create(:personal_snippet) }
+ let(:namespace) { repository.full_path }
- it 'includes the full namespace' do
- is_expected.to eq("foo:#{namespace}:#{extra_namespace}:set")
+ it_behaves_like 'cache_key examples' do
+ let(:repository) { personal_snippet.repository }
+ end
+ end
+
+ describe 'project snippet repository' do
+ let_it_be(:project_snippet) { create(:project_snippet, project: project) }
+
+ it_behaves_like 'cache_key examples' do
+ let(:repository) { project_snippet.repository }
end
end
end
describe '#expire' do
- it 'expires the given key from the cache' do
+ subject { cache.expire(*keys) }
+
+ before do
cache.write(:foo, ['value'])
+ cache.write(:bar, ['value2'])
+ end
+ it 'actually wrote the values' do
expect(cache.read(:foo)).to contain_exactly('value')
- expect(cache.expire(:foo)).to eq(1)
- expect(cache.read(:foo)).to be_empty
+ expect(cache.read(:bar)).to contain_exactly('value2')
+ end
+
+ context 'single key' do
+ let(:keys) { %w(foo) }
+
+ it { is_expected.to eq(1) }
+
+ it 'deletes the given key from the cache' do
+ subject
+
+ expect(cache.read(:foo)).to be_empty
+ end
+ end
+
+ context 'multiple keys' do
+ let(:keys) { %w(foo bar) }
+
+ it { is_expected.to eq(2) }
+
+ it 'deletes the given keys from the cache' do
+ subject
+
+ expect(cache.read(:foo)).to be_empty
+ expect(cache.read(:bar)).to be_empty
+ end
+ end
+
+ context 'no keys' do
+ let(:keys) { [] }
+
+ it { is_expected.to eq(0) }
+ end
+
+ context "unlink isn't supported" do
+ before do
+ allow_any_instance_of(Redis).to receive(:unlink) { raise ::Redis::CommandError }
+ end
+
+ it 'still deletes the given key' do
+ expect(cache.expire(:foo)).to eq(1)
+ expect(cache.read(:foo)).to be_empty
+ end
end
end
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index f0b733817b3..58fba673f8e 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -7,9 +7,9 @@ describe Gitlab::Sanitizers::Exif do
describe '#batch_clean' do
context 'with image uploads' do
- set(:upload1) { create(:upload, :with_file, :issuable_upload) }
- set(:upload2) { create(:upload, :with_file, :personal_snippet_upload) }
- set(:upload3) { create(:upload, :with_file, created_at: 3.days.ago) }
+ let_it_be(:upload1) { create(:upload, :with_file, :issuable_upload) }
+ let_it_be(:upload2) { create(:upload, :with_file, :personal_snippet_upload) }
+ let_it_be(:upload3) { create(:upload, :with_file, created_at: 3.days.ago) }
it 'processes all uploads if range ID is not set' do
expect(sanitizer).to receive(:clean).exactly(3).times
diff --git a/spec/lib/gitlab/serverless/domain_spec.rb b/spec/lib/gitlab/serverless/domain_spec.rb
deleted file mode 100644
index ae5551977d4..00000000000
--- a/spec/lib/gitlab/serverless/domain_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Serverless::Domain do
- describe '.generate_uuid' do
- it 'has 14 characters' do
- expect(described_class.generate_uuid.length).to eq(described_class::UUID_LENGTH)
- end
-
- it 'consists of only hexadecimal characters' do
- expect(described_class.generate_uuid).to match(/\A\h+\z/)
- end
-
- it 'uses random characters' do
- uuid = 'abcd1234567890'
-
- expect(SecureRandom).to receive(:hex).with(described_class::UUID_LENGTH / 2).and_return(uuid)
- expect(described_class.generate_uuid).to eq(uuid)
- end
- end
-end
diff --git a/spec/lib/gitlab/serverless/function_uri_spec.rb b/spec/lib/gitlab/serverless/function_uri_spec.rb
deleted file mode 100644
index cd4abeb89f5..00000000000
--- a/spec/lib/gitlab/serverless/function_uri_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Serverless::FunctionURI do
- let(:function) { 'test-function' }
- let(:domain) { 'serverless.gitlab.io' }
- let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: domain) }
- let!(:cluster) { create(:serverless_domain_cluster, uuid: 'abcdef12345678', pages_domain: pages_domain) }
- let(:valid_cluster) { 'aba1cdef123456f278' }
- let(:invalid_cluster) { 'aba1cdef123456f178' }
- let!(:environment) { create(:environment, name: 'test') }
-
- let(:valid_uri) { "https://#{function}-#{valid_cluster}#{"%x" % environment.id}-#{environment.slug}.#{domain}" }
- let(:valid_fqdn) { "#{function}-#{valid_cluster}#{"%x" % environment.id}-#{environment.slug}.#{domain}" }
- let(:invalid_uri) { "https://#{function}-#{invalid_cluster}#{"%x" % environment.id}-#{environment.slug}.#{domain}" }
-
- shared_examples 'a valid FunctionURI class' do
- describe '#to_s' do
- it 'matches valid URI' do
- expect(subject.to_s).to eq valid_uri
- end
- end
-
- describe '#function' do
- it 'returns function' do
- expect(subject.function).to eq function
- end
- end
-
- describe '#cluster' do
- it 'returns cluster' do
- expect(subject.cluster).to eq cluster
- end
- end
-
- describe '#environment' do
- it 'returns environment' do
- expect(subject.environment).to eq environment
- end
- end
- end
-
- describe '.new' do
- context 'with valid arguments' do
- subject { described_class.new(function: function, cluster: cluster, environment: environment) }
-
- it_behaves_like 'a valid FunctionURI class'
- end
-
- context 'with invalid arguments' do
- subject { described_class.new(function: function, environment: environment) }
-
- it 'raises an exception' do
- expect { subject }.to raise_error(ArgumentError)
- end
- end
- end
-
- describe '.parse' do
- context 'with valid URI' do
- subject { described_class.parse(valid_uri) }
-
- it_behaves_like 'a valid FunctionURI class'
- end
-
- context 'with valid FQDN' do
- subject { described_class.parse(valid_fqdn) }
-
- it_behaves_like 'a valid FunctionURI class'
- end
-
- context 'with invalid URI' do
- subject { described_class.parse(invalid_uri) }
-
- it 'returns nil' do
- expect(subject).to be_nil
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/serverless/service_spec.rb b/spec/lib/gitlab/serverless/service_spec.rb
index f618dd02cdb..6db8b9cd0ba 100644
--- a/spec/lib/gitlab/serverless/service_spec.rb
+++ b/spec/lib/gitlab/serverless/service_spec.rb
@@ -94,17 +94,19 @@ describe Gitlab::Serverless::Service do
end
describe '#url' do
+ let(:serverless_domain) { instance_double(::Serverless::Domain, uri: URI('https://proxy.example.com')) }
+
it 'returns proxy URL if cluster has serverless domain' do
# cluster = create(:cluster)
knative = create(:clusters_applications_knative, :installed, cluster: cluster)
create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
service = Gitlab::Serverless::Service.new(attributes.merge('cluster' => cluster))
- expect(Gitlab::Serverless::FunctionURI).to receive(:new).with(
- function: service.name,
- cluster: service.cluster.serverless_domain,
+ expect(::Serverless::Domain).to receive(:new).with(
+ function_name: service.name,
+ serverless_domain_cluster: service.cluster.serverless_domain,
environment: service.environment
- ).and_return('https://proxy.example.com')
+ ).and_return(serverless_domain)
expect(service.url).to eq('https://proxy.example.com')
end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 7b8d1b6cd9b..e4c33863ac2 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -4,25 +4,19 @@ require 'spec_helper'
require 'stringio'
describe Gitlab::Shell do
- set(:project) { create(:project, :repository) }
-
+ let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:gitlab_shell) { described_class.new }
- let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
- let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
- let(:gitlab_authorized_keys) { double }
-
- before do
- allow(Project).to receive(:find).and_return(project)
- end
- it { is_expected.to respond_to :add_key }
- it { is_expected.to respond_to :remove_key }
- it { is_expected.to respond_to :create_repository }
it { is_expected.to respond_to :remove_repository }
- it { is_expected.to respond_to :fork_repository }
- it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") }
+ describe '.url_to_repo' do
+ let(:full_path) { 'diaspora/disaspora-rails' }
+
+ subject { described_class.url_to_repo(full_path) }
+
+ it { is_expected.to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + full_path + '.git') }
+ end
describe 'memoized secret_token' do
let(:secret_file) { 'tmp/tests/.secret_shell_test' }
@@ -50,261 +44,15 @@ describe Gitlab::Shell do
end
end
- describe '#add_key' do
- context 'when authorized_keys_enabled is true' do
- it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
-
- expect(gitlab_authorized_keys)
- .to receive(:add_key)
- .with('key-123', 'ssh-rsa foobar')
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
- end
- end
-
- context 'when authorized_keys_enabled is false' do
- before do
- stub_application_setting(authorized_keys_enabled: false)
- end
-
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
- context 'when authorized_keys_enabled is nil' do
- before do
- stub_application_setting(authorized_keys_enabled: nil)
- end
-
- it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
-
- expect(gitlab_authorized_keys)
- .to receive(:add_key)
- .with('key-123', 'ssh-rsa foobar')
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
- end
- end
- end
-
- describe '#batch_add_keys' do
- let(:keys) { [double(shell_id: 'key-123', key: 'ssh-rsa foobar')] }
-
- context 'when authorized_keys_enabled is true' do
- it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
-
- expect(gitlab_authorized_keys)
- .to receive(:batch_add_keys)
- .with(keys)
-
- gitlab_shell.batch_add_keys(keys)
- end
- end
-
- context 'when authorized_keys_enabled is false' do
- before do
- stub_application_setting(authorized_keys_enabled: false)
- end
-
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.batch_add_keys(keys)
- end
- end
-
- context 'when authorized_keys_enabled is nil' do
- before do
- stub_application_setting(authorized_keys_enabled: nil)
- end
-
- it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
-
- expect(gitlab_authorized_keys)
- .to receive(:batch_add_keys)
- .with(keys)
-
- gitlab_shell.batch_add_keys(keys)
- end
- end
- end
-
- describe '#remove_key' do
- context 'when authorized_keys_enabled is true' do
- it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
-
- gitlab_shell.remove_key('key-123')
- end
- end
-
- context 'when authorized_keys_enabled is false' do
- before do
- stub_application_setting(authorized_keys_enabled: false)
- end
-
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.remove_key('key-123')
- end
- end
-
- context 'when authorized_keys_enabled is nil' do
- before do
- stub_application_setting(authorized_keys_enabled: nil)
- end
-
- it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
-
- gitlab_shell.remove_key('key-123')
- end
- end
- end
-
- describe '#remove_all_keys' do
- context 'when authorized_keys_enabled is true' do
- it 'calls Gitlab::AuthorizedKeys#clear' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:clear)
-
- gitlab_shell.remove_all_keys
- end
- end
-
- context 'when authorized_keys_enabled is false' do
- before do
- stub_application_setting(authorized_keys_enabled: false)
- end
-
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.remove_all_keys
- end
- end
-
- context 'when authorized_keys_enabled is nil' do
- before do
- stub_application_setting(authorized_keys_enabled: nil)
- end
-
- it 'calls Gitlab::AuthorizedKeys#clear' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:clear)
-
- gitlab_shell.remove_all_keys
- end
- end
- end
-
- describe '#remove_keys_not_found_in_db' do
- context 'when keys are in the file that are not in the DB' do
- before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
- @another_key = create(:key) # this one IS in the DB
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- expect(gitlab_shell).to receive(:remove_key).with('key-9876')
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
-
- gitlab_shell.remove_keys_not_found_in_db
- end
- end
-
- context 'when keys there are duplicate keys in the file that are not in the DB' do
- before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
-
- gitlab_shell.remove_keys_not_found_in_db
- end
- end
-
- context 'when keys there are duplicate keys in the file that ARE in the DB' do
- before do
- gitlab_shell.remove_all_keys
- @key = create(:key)
- gitlab_shell.add_key(@key.shell_id, @key.key)
- end
-
- it 'does not remove the key' do
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
-
- gitlab_shell.remove_keys_not_found_in_db
- end
- end
-
- unless ENV['CI'] # Skip in CI, it takes 1 minute
- context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
- before do
- gitlab_shell.remove_all_keys
- 100.times { |i| create(:key) } # first batch is all in the DB
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys not in the DB' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
-
- gitlab_shell.remove_keys_not_found_in_db
- end
- end
- end
- end
-
describe 'projects commands' do
let(:gitlab_shell_path) { File.expand_path('tmp/tests/gitlab-shell') }
let(:projects_path) { File.join(gitlab_shell_path, 'bin/gitlab-projects') }
- let(:gitlab_shell_hooks_path) { File.join(gitlab_shell_path, 'hooks') }
before do
allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path)
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
- describe '#create_repository' do
- let(:repository_storage) { 'default' }
- let(:repository_storage_path) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Gitlab.config.repositories.storages[repository_storage].legacy_disk_path
- end
- end
- let(:repo_name) { 'project/path' }
- let(:created_path) { File.join(repository_storage_path, repo_name + '.git') }
-
- after do
- FileUtils.rm_rf(created_path)
- end
-
- it 'returns false when the command fails' do
- FileUtils.mkdir_p(File.dirname(created_path))
- # This file will block the creation of the repo's .git directory. That
- # should cause #create_repository to fail.
- FileUtils.touch(created_path)
-
- expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_falsy
- end
- end
-
describe '#remove_repository' do
let!(:project) { create(:project, :repository, :legacy_storage) }
let(:disk_path) { "#{project.disk_path}.git" }
@@ -346,52 +94,6 @@ describe Gitlab::Shell do
expect(TestEnv.storage_dir_exists?(project2.repository_storage, "#{project2.disk_path}.git")).to be(true)
end
end
-
- describe '#fork_repository' do
- let(:target_project) { create(:project) }
-
- subject do
- gitlab_shell.fork_repository(project, target_project)
- end
-
- it 'returns true when the command succeeds' do
- expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:fork_repository)
- .with(repository.raw_repository) { :gitaly_response_object }
-
- is_expected.to be_truthy
- end
-
- it 'return false when the command fails' do
- expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:fork_repository)
- .with(repository.raw_repository) { raise GRPC::BadStatus, 'bla' }
-
- is_expected.to be_falsy
- end
- end
-
- describe '#import_repository' do
- let(:import_url) { 'https://gitlab.com/gitlab-org/gitlab-foss.git' }
-
- context 'with gitaly' do
- it 'returns true when the command succeeds' do
- expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository).with(import_url)
-
- result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url, project.full_path)
-
- expect(result).to be_truthy
- end
-
- it 'raises an exception when the command fails' do
- expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository)
- .with(import_url) { raise GRPC::BadStatus, 'bla' }
- expect_any_instance_of(Gitlab::Shell::GitalyGitlabProjects).to receive(:output) { 'error'}
-
- expect do
- gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url, project.full_path)
- end.to raise_error(Gitlab::Shell::Error, "error")
- end
- end
- end
end
describe 'namespace actions' do
diff --git a/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
new file mode 100644
index 00000000000..5bda8ff8c72
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_cluster/cli_spec.rb
@@ -0,0 +1,282 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::SidekiqCluster::CLI do
+ let(:cli) { described_class.new('/dev/null') }
+ let(:default_options) do
+ { env: 'test', directory: Dir.pwd, max_concurrency: 50, min_concurrency: 0, dryrun: false }
+ end
+
+ before do
+ stub_env('RAILS_ENV', 'test')
+ end
+
+ describe '#run' do
+ context 'without any arguments' do
+ it 'raises CommandError' do
+ expect { cli.run([]) }.to raise_error(described_class::CommandError)
+ end
+ end
+
+ context 'with arguments' do
+ before do
+ allow(cli).to receive(:write_pid)
+ allow(cli).to receive(:trap_signals)
+ allow(cli).to receive(:start_loop)
+ end
+
+ it 'starts the Sidekiq workers' do
+ expect(Gitlab::SidekiqCluster).to receive(:start)
+ .with([['foo']], default_options)
+ .and_return([])
+
+ cli.run(%w(foo))
+ end
+
+ it 'allows the special * selector' do
+ worker_queues = %w(foo bar baz)
+
+ expect(Gitlab::SidekiqConfig::CliMethods)
+ .to receive(:worker_queues).and_return(worker_queues)
+
+ expect(Gitlab::SidekiqCluster)
+ .to receive(:start).with([worker_queues], default_options)
+
+ cli.run(%w(*))
+ end
+
+ context 'with --negate flag' do
+ it 'starts Sidekiq workers for all queues in all_queues.yml except the ones in argv' do
+ expect(Gitlab::SidekiqConfig::CliMethods).to receive(:worker_queues).and_return(['baz'])
+ expect(Gitlab::SidekiqCluster).to receive(:start)
+ .with([['baz']], default_options)
+ .and_return([])
+
+ cli.run(%w(foo -n))
+ end
+ end
+
+ context 'with --max-concurrency flag' do
+ it 'starts Sidekiq workers for specified queues with a max concurrency' do
+ expect(Gitlab::SidekiqConfig::CliMethods).to receive(:worker_queues).and_return(%w(foo bar baz))
+ expect(Gitlab::SidekiqCluster).to receive(:start)
+ .with([%w(foo bar baz), %w(solo)], default_options.merge(max_concurrency: 2))
+ .and_return([])
+
+ cli.run(%w(foo,bar,baz solo -m 2))
+ end
+ end
+
+ context 'with --min-concurrency flag' do
+ it 'starts Sidekiq workers for specified queues with a min concurrency' do
+ expect(Gitlab::SidekiqConfig::CliMethods).to receive(:worker_queues).and_return(%w(foo bar baz))
+ expect(Gitlab::SidekiqCluster).to receive(:start)
+ .with([%w(foo bar baz), %w(solo)], default_options.merge(min_concurrency: 2))
+ .and_return([])
+
+ cli.run(%w(foo,bar,baz solo --min-concurrency 2))
+ end
+ end
+
+ context 'queue namespace expansion' do
+ it 'starts Sidekiq workers for all queues in all_queues.yml with a namespace in argv' do
+ expect(Gitlab::SidekiqConfig::CliMethods).to receive(:worker_queues).and_return(['cronjob:foo', 'cronjob:bar'])
+ expect(Gitlab::SidekiqCluster).to receive(:start)
+ .with([['cronjob', 'cronjob:foo', 'cronjob:bar']], default_options)
+ .and_return([])
+
+ cli.run(%w(cronjob))
+ end
+ end
+
+ context 'with --experimental-queue-selector' do
+ where do
+ {
+ 'memory-bound queues' => {
+ query: 'resource_boundary=memory',
+ included_queues: %w(project_export),
+ excluded_queues: %w(merge)
+ },
+ 'memory- or CPU-bound queues' => {
+ query: 'resource_boundary=memory,cpu',
+ included_queues: %w(auto_merge:auto_merge_process project_export),
+ excluded_queues: %w(merge)
+ },
+ 'high urgency CI queues' => {
+ query: 'feature_category=continuous_integration&urgency=high',
+ included_queues: %w(pipeline_cache:expire_job_cache pipeline_cache:expire_pipeline_cache),
+ excluded_queues: %w(merge)
+ },
+ 'CPU-bound high urgency CI queues' => {
+ query: 'feature_category=continuous_integration&urgency=high&resource_boundary=cpu',
+ included_queues: %w(pipeline_cache:expire_pipeline_cache),
+ excluded_queues: %w(pipeline_cache:expire_job_cache merge)
+ },
+ 'CPU-bound high urgency non-CI queues' => {
+ query: 'feature_category!=continuous_integration&urgency=high&resource_boundary=cpu',
+ included_queues: %w(new_issue),
+ excluded_queues: %w(pipeline_cache:expire_pipeline_cache)
+ },
+ 'CI and SCM queues' => {
+ query: 'feature_category=continuous_integration|feature_category=source_code_management',
+ included_queues: %w(pipeline_cache:expire_job_cache merge),
+ excluded_queues: %w(mailers)
+ }
+ }
+ end
+
+ with_them do
+ it 'expands queues by attributes' do
+ expect(Gitlab::SidekiqCluster).to receive(:start) do |queues, opts|
+ expect(opts).to eq(default_options)
+ expect(queues.first).to include(*included_queues)
+ expect(queues.first).not_to include(*excluded_queues)
+
+ []
+ end
+
+ cli.run(%W(--experimental-queue-selector #{query}))
+ end
+
+ it 'works when negated' do
+ expect(Gitlab::SidekiqCluster).to receive(:start) do |queues, opts|
+ expect(opts).to eq(default_options)
+ expect(queues.first).not_to include(*included_queues)
+ expect(queues.first).to include(*excluded_queues)
+
+ []
+ end
+
+ cli.run(%W(--negate --experimental-queue-selector #{query}))
+ end
+ end
+
+ it 'expands multiple queue groups correctly' do
+ expect(Gitlab::SidekiqCluster)
+ .to receive(:start)
+ .with([['chat_notification'], ['project_export']], default_options)
+ .and_return([])
+
+ cli.run(%w(--experimental-queue-selector feature_category=chatops&has_external_dependencies=true resource_boundary=memory&feature_category=importers))
+ end
+
+ it 'allows the special * selector' do
+ worker_queues = %w(foo bar baz)
+
+ expect(Gitlab::SidekiqConfig::CliMethods)
+ .to receive(:worker_queues).and_return(worker_queues)
+
+ expect(Gitlab::SidekiqCluster)
+ .to receive(:start).with([worker_queues], default_options)
+
+ cli.run(%w(--experimental-queue-selector *))
+ end
+
+ it 'errors when the selector matches no queues' do
+ expect(Gitlab::SidekiqCluster).not_to receive(:start)
+
+ expect { cli.run(%w(--experimental-queue-selector has_external_dependencies=true&has_external_dependencies=false)) }
+ .to raise_error(described_class::CommandError)
+ end
+
+ it 'errors on an invalid query multiple queue groups correctly' do
+ expect(Gitlab::SidekiqCluster).not_to receive(:start)
+
+ expect { cli.run(%w(--experimental-queue-selector unknown_field=chatops)) }
+ .to raise_error(Gitlab::SidekiqConfig::CliMethods::QueryError)
+ end
+ end
+ end
+ end
+
+ describe '#write_pid' do
+ context 'when a PID is specified' do
+ it 'writes the PID to a file' do
+ expect(Gitlab::SidekiqCluster).to receive(:write_pid).with('/dev/null')
+
+ cli.option_parser.parse!(%w(-P /dev/null))
+ cli.write_pid
+ end
+ end
+
+ context 'when no PID is specified' do
+ it 'does not write a PID' do
+ expect(Gitlab::SidekiqCluster).not_to receive(:write_pid)
+
+ cli.write_pid
+ end
+ end
+ end
+
+ describe '#wait_for_termination' do
+ it 'waits for termination of all sub-processes and succeeds after 3 checks' do
+ expect(Gitlab::SidekiqCluster).to receive(:any_alive?)
+ .with(an_instance_of(Array)).and_return(true, true, true, false)
+
+ expect(Gitlab::SidekiqCluster).to receive(:pids_alive)
+ .with([]).and_return([])
+
+ expect(Gitlab::SidekiqCluster).to receive(:signal_processes)
+ .with([], :KILL)
+
+ stub_const("Gitlab::SidekiqCluster::CLI::CHECK_TERMINATE_INTERVAL_SECONDS", 0.1)
+ stub_const("Gitlab::SidekiqCluster::CLI::TERMINATE_TIMEOUT_SECONDS", 1)
+ cli.wait_for_termination
+ end
+
+ context 'with hanging workers' do
+ before do
+ expect(cli).to receive(:write_pid)
+ expect(cli).to receive(:trap_signals)
+ expect(cli).to receive(:start_loop)
+ end
+
+ it 'hard kills workers after timeout expires' do
+ worker_pids = [101, 102, 103]
+ expect(Gitlab::SidekiqCluster).to receive(:start)
+ .with([['foo']], default_options)
+ .and_return(worker_pids)
+
+ expect(Gitlab::SidekiqCluster).to receive(:any_alive?)
+ .with(worker_pids).and_return(true).at_least(10).times
+
+ expect(Gitlab::SidekiqCluster).to receive(:pids_alive)
+ .with(worker_pids).and_return([102])
+
+ expect(Gitlab::SidekiqCluster).to receive(:signal_processes)
+ .with([102], :KILL)
+
+ cli.run(%w(foo))
+
+ stub_const("Gitlab::SidekiqCluster::CLI::CHECK_TERMINATE_INTERVAL_SECONDS", 0.1)
+ stub_const("Gitlab::SidekiqCluster::CLI::TERMINATE_TIMEOUT_SECONDS", 1)
+ cli.wait_for_termination
+ end
+ end
+ end
+
+ describe '#trap_signals' do
+ it 'traps the termination and forwarding signals' do
+ expect(Gitlab::SidekiqCluster).to receive(:trap_terminate)
+ expect(Gitlab::SidekiqCluster).to receive(:trap_forward)
+
+ cli.trap_signals
+ end
+ end
+
+ describe '#start_loop' do
+ it 'runs until one of the processes has been terminated' do
+ allow(cli).to receive(:sleep).with(a_kind_of(Numeric))
+
+ expect(Gitlab::SidekiqCluster).to receive(:all_alive?)
+ .with(an_instance_of(Array)).and_return(false)
+
+ expect(Gitlab::SidekiqCluster).to receive(:signal_processes)
+ .with(an_instance_of(Array), :TERM)
+
+ cli.start_loop
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_cluster_spec.rb b/spec/lib/gitlab/sidekiq_cluster_spec.rb
new file mode 100644
index 00000000000..fa5de04f2f3
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_cluster_spec.rb
@@ -0,0 +1,196 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::SidekiqCluster do
+ describe '.trap_signals' do
+ it 'traps the given signals' do
+ expect(described_class).to receive(:trap).ordered.with(:INT)
+ expect(described_class).to receive(:trap).ordered.with(:HUP)
+
+ described_class.trap_signals(%i(INT HUP))
+ end
+ end
+
+ describe '.trap_terminate' do
+ it 'traps the termination signals' do
+ expect(described_class).to receive(:trap_signals)
+ .with(described_class::TERMINATE_SIGNALS)
+
+ described_class.trap_terminate { }
+ end
+ end
+
+ describe '.trap_forward' do
+ it 'traps the signals to forward' do
+ expect(described_class).to receive(:trap_signals)
+ .with(described_class::FORWARD_SIGNALS)
+
+ described_class.trap_forward { }
+ end
+ end
+
+ describe '.signal' do
+ it 'sends a signal to the given process' do
+ allow(Process).to receive(:kill).with(:INT, 4)
+ expect(described_class.signal(4, :INT)).to eq(true)
+ end
+
+ it 'returns false when the process does not exist' do
+ allow(Process).to receive(:kill).with(:INT, 4).and_raise(Errno::ESRCH)
+ expect(described_class.signal(4, :INT)).to eq(false)
+ end
+ end
+
+ describe '.signal_processes' do
+ it 'sends a signal to every thread' do
+ expect(described_class).to receive(:signal).with(1, :INT)
+
+ described_class.signal_processes([1], :INT)
+ end
+ end
+
+ describe '.start' do
+ it 'starts Sidekiq with the given queues, environment and options' do
+ expected_options = {
+ env: :production,
+ directory: 'foo/bar',
+ max_concurrency: 20,
+ min_concurrency: 10,
+ dryrun: true
+ }
+
+ expect(described_class).to receive(:start_sidekiq).ordered.with(%w(foo), expected_options.merge(worker_id: 0))
+ expect(described_class).to receive(:start_sidekiq).ordered.with(%w(bar baz), expected_options.merge(worker_id: 1))
+
+ described_class.start([%w(foo), %w(bar baz)], env: :production, directory: 'foo/bar', max_concurrency: 20, min_concurrency: 10, dryrun: true)
+ end
+
+ it 'starts Sidekiq with the given queues and sensible default options' do
+ expected_options = {
+ env: :development,
+ directory: an_instance_of(String),
+ max_concurrency: 50,
+ min_concurrency: 0,
+ worker_id: an_instance_of(Integer),
+ dryrun: false
+ }
+
+ expect(described_class).to receive(:start_sidekiq).ordered.with(%w(foo bar baz), expected_options)
+ expect(described_class).to receive(:start_sidekiq).ordered.with(%w(solo), expected_options)
+
+ described_class.start([%w(foo bar baz), %w(solo)])
+ end
+ end
+
+ describe '.start_sidekiq' do
+ let(:first_worker_id) { 0 }
+ let(:options) do
+ { env: :production, directory: 'foo/bar', max_concurrency: 20, min_concurrency: 0, worker_id: first_worker_id, dryrun: false }
+ end
+ let(:env) { { "ENABLE_SIDEKIQ_CLUSTER" => "1", "SIDEKIQ_WORKER_ID" => first_worker_id.to_s } }
+ let(:args) { ['bundle', 'exec', 'sidekiq', anything, '-eproduction', *([anything] * 5)] }
+
+ it 'starts a Sidekiq process' do
+ allow(Process).to receive(:spawn).and_return(1)
+
+ expect(described_class).to receive(:wait_async).with(1)
+ expect(described_class.start_sidekiq(%w(foo), options)).to eq(1)
+ end
+
+ it 'handles duplicate queue names' do
+ allow(Process)
+ .to receive(:spawn)
+ .with(env, *args, anything)
+ .and_return(1)
+
+ expect(described_class).to receive(:wait_async).with(1)
+ expect(described_class.start_sidekiq(%w(foo foo bar baz), options)).to eq(1)
+ end
+
+ it 'runs the sidekiq process in a new process group' do
+ expect(Process)
+ .to receive(:spawn)
+ .with(anything, *args, a_hash_including(pgroup: true))
+ .and_return(1)
+
+ allow(described_class).to receive(:wait_async)
+ expect(described_class.start_sidekiq(%w(foo bar baz), options)).to eq(1)
+ end
+ end
+
+ describe '.concurrency' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:queue_count, :min, :max, :expected) do
+ 2 | 0 | 0 | 3 # No min or max specified
+ 2 | 0 | 9 | 3 # No min specified, value < max
+ 2 | 1 | 4 | 3 # Value between min and max
+ 2 | 4 | 5 | 4 # Value below range
+ 5 | 2 | 3 | 3 # Value above range
+ 2 | 1 | 1 | 1 # Value above explicit setting (min == max)
+ 0 | 3 | 3 | 3 # Value below explicit setting (min == max)
+ 1 | 4 | 3 | 3 # Min greater than max
+ end
+
+ with_them do
+ let(:queues) { Array.new(queue_count) }
+
+ it { expect(described_class.concurrency(queues, min, max)).to eq(expected) }
+ end
+ end
+
+ describe '.wait_async' do
+ it 'waits for a process in a separate thread' do
+ thread = described_class.wait_async(Process.spawn('true'))
+
+ # Upon success Process.wait just returns the PID.
+ expect(thread.value).to be_a_kind_of(Numeric)
+ end
+ end
+
+ # In the X_alive? checks, we check negative PIDs sometimes as a simple way
+ # to be sure the pids are definitely for non-existent processes.
+ # Note that -1 is special, and sends the signal to every process we have permission
+ # for, so we use -2, -3 etc
+ describe '.all_alive?' do
+ it 'returns true if all processes are alive' do
+ processes = [Process.pid]
+
+ expect(described_class.all_alive?(processes)).to eq(true)
+ end
+
+ it 'returns false when a thread was not alive' do
+ processes = [-2]
+
+ expect(described_class.all_alive?(processes)).to eq(false)
+ end
+ end
+
+ describe '.any_alive?' do
+ it 'returns true if at least one process is alive' do
+ processes = [Process.pid, -2]
+
+ expect(described_class.any_alive?(processes)).to eq(true)
+ end
+
+ it 'returns false when all threads are dead' do
+ processes = [-2, -3]
+
+ expect(described_class.any_alive?(processes)).to eq(false)
+ end
+ end
+
+ describe '.write_pid' do
+ it 'writes the PID of the current process to the given file' do
+ handle = double(:handle)
+
+ allow(File).to receive(:open).with('/dev/null', 'w').and_yield(handle)
+
+ expect(handle).to receive(:write).with(Process.pid.to_s)
+
+ described_class.write_pid('/dev/null')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
index e6d0055df64..0aaff12f278 100644
--- a/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config/cli_methods_spec.rb
@@ -124,28 +124,28 @@ describe Gitlab::SidekiqConfig::CliMethods do
name: 'a',
feature_category: :category_a,
has_external_dependencies: false,
- latency_sensitive: false,
+ urgency: :low,
resource_boundary: :cpu
},
{
- name: 'a_2',
+ name: 'a:2',
feature_category: :category_a,
has_external_dependencies: false,
- latency_sensitive: true,
+ urgency: :high,
resource_boundary: :none
},
{
name: 'b',
feature_category: :category_b,
has_external_dependencies: true,
- latency_sensitive: true,
+ urgency: :high,
resource_boundary: :memory
},
{
name: 'c',
feature_category: :category_c,
has_external_dependencies: false,
- latency_sensitive: false,
+ urgency: :throttled,
resource_boundary: :memory
}
]
@@ -154,40 +154,40 @@ describe Gitlab::SidekiqConfig::CliMethods do
context 'with valid input' do
where(:query, :selected_queues) do
# feature_category
- 'feature_category=category_a' | %w(a a_2)
- 'feature_category=category_a,category_c' | %w(a a_2 c)
- 'feature_category=category_a|feature_category=category_c' | %w(a a_2 c)
+ 'feature_category=category_a' | %w(a a:2)
+ 'feature_category=category_a,category_c' | %w(a a:2 c)
+ 'feature_category=category_a|feature_category=category_c' | %w(a a:2 c)
'feature_category!=category_a' | %w(b c)
# has_external_dependencies
'has_external_dependencies=true' | %w(b)
- 'has_external_dependencies=false' | %w(a a_2 c)
- 'has_external_dependencies=true,false' | %w(a a_2 b c)
- 'has_external_dependencies=true|has_external_dependencies=false' | %w(a a_2 b c)
- 'has_external_dependencies!=true' | %w(a a_2 c)
-
- # latency_sensitive
- 'latency_sensitive=true' | %w(a_2 b)
- 'latency_sensitive=false' | %w(a c)
- 'latency_sensitive=true,false' | %w(a a_2 b c)
- 'latency_sensitive=true|latency_sensitive=false' | %w(a a_2 b c)
- 'latency_sensitive!=true' | %w(a c)
+ 'has_external_dependencies=false' | %w(a a:2 c)
+ 'has_external_dependencies=true,false' | %w(a a:2 b c)
+ 'has_external_dependencies=true|has_external_dependencies=false' | %w(a a:2 b c)
+ 'has_external_dependencies!=true' | %w(a a:2 c)
+
+ # urgency
+ 'urgency=high' | %w(a:2 b)
+ 'urgency=low' | %w(a)
+ 'urgency=high,low,throttled' | %w(a a:2 b c)
+ 'urgency=low|urgency=throttled' | %w(a c)
+ 'urgency!=high' | %w(a c)
# name
'name=a' | %w(a)
'name=a,b' | %w(a b)
- 'name=a,a_2|name=b' | %w(a a_2 b)
- 'name!=a,a_2' | %w(b c)
+ 'name=a,a:2|name=b' | %w(a a:2 b)
+ 'name!=a,a:2' | %w(b c)
# resource_boundary
'resource_boundary=memory' | %w(b c)
'resource_boundary=memory,cpu' | %w(a b c)
'resource_boundary=memory|resource_boundary=cpu' | %w(a b c)
- 'resource_boundary!=memory,cpu' | %w(a_2)
+ 'resource_boundary!=memory,cpu' | %w(a:2)
# combinations
- 'feature_category=category_a&latency_sensitive=true' | %w(a_2)
- 'feature_category=category_a&latency_sensitive=true|feature_category=category_c' | %w(a_2 c)
+ 'feature_category=category_a&urgency=high' | %w(a:2)
+ 'feature_category=category_a&urgency=high|feature_category=category_c' | %w(a:2 c)
end
with_them do
diff --git a/spec/lib/gitlab/sidekiq_config/worker_spec.rb b/spec/lib/gitlab/sidekiq_config/worker_spec.rb
index 38edd0f5eeb..817755e3507 100644
--- a/spec/lib/gitlab/sidekiq_config/worker_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config/worker_spec.rb
@@ -11,8 +11,9 @@ describe Gitlab::SidekiqConfig::Worker do
get_feature_category: attributes[:feature_category],
get_weight: attributes[:weight],
get_worker_resource_boundary: attributes[:resource_boundary],
- latency_sensitive_worker?: attributes[:latency_sensitive],
- worker_has_external_dependencies?: attributes[:has_external_dependencies]
+ get_urgency: attributes[:urgency],
+ worker_has_external_dependencies?: attributes[:has_external_dependencies],
+ idempotent?: attributes[:idempotent]
)
described_class.new(inner_worker, ee: false)
@@ -46,7 +47,7 @@ describe Gitlab::SidekiqConfig::Worker do
describe 'delegations' do
[
:feature_category_not_owned?, :get_feature_category, :get_weight,
- :get_worker_resource_boundary, :latency_sensitive_worker?, :queue,
+ :get_worker_resource_boundary, :get_urgency, :queue,
:queue_namespace, :worker_has_external_dependencies?
].each do |meth|
it "delegates #{meth} to the worker class" do
@@ -87,17 +88,19 @@ describe Gitlab::SidekiqConfig::Worker do
attributes_a = {
feature_category: :source_code_management,
has_external_dependencies: false,
- latency_sensitive: false,
+ urgency: :low,
resource_boundary: :memory,
- weight: 2
+ weight: 2,
+ idempotent: true
}
attributes_b = {
feature_category: :not_owned,
has_external_dependencies: true,
- latency_sensitive: true,
+ urgency: :high,
resource_boundary: :unknown,
- weight: 1
+ weight: 3,
+ idempotent: false
}
worker_a = create_worker(queue: 'a', **attributes_a)
diff --git a/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb
new file mode 100644
index 00000000000..3cc5c0bed1b
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_logging/deduplication_logger_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqLogging::DeduplicationLogger do
+ describe '#log_deduplication' do
+ let(:job) do
+ {
+ 'class' => 'TestWorker',
+ 'args' => [1234, 'hello', { 'key' => 'value' }],
+ 'jid' => 'da883554ee4fe414012f5f42',
+ 'correlation_id' => 'cid',
+ 'duplicate-of' => 'other_jid'
+ }
+ end
+
+ it 'logs a deduplication message to the sidekiq logger' do
+ expected_payload = {
+ 'job_status' => 'deduplicated',
+ 'message' => "#{job['class']} JID-#{job['jid']}: deduplicated: a fancy strategy",
+ 'deduplication_type' => 'a fancy strategy'
+ }
+ expect(Sidekiq.logger).to receive(:info).with(a_hash_including(expected_payload)).and_call_original
+
+ described_class.instance.log(job, "a fancy strategy")
+ end
+
+ it "does not modify the job" do
+ expect { described_class.instance.log(job, "a fancy strategy") }
+ .not_to change { job }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index f294d7f7fcd..bd04d30f85f 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
let(:job) do
{
"class" => "TestWorker",
- "args" => [1234, 'hello'],
+ "args" => [1234, 'hello', { 'key' => 'value' }],
"retry" => false,
"queue" => "cronjob:test_queue",
"queue_namespace" => "cronjob",
@@ -30,6 +30,7 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
let(:clock_thread_cputime_end) { 1.333333799 }
let(:start_payload) do
job.except('error_backtrace', 'error_class', 'error_message').merge(
+ 'args' => %w(1234 hello {"key"=>"value"}),
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start',
'job_status' => 'start',
'pid' => Process.pid,
@@ -99,13 +100,27 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
end
end
+ it 'does not modify the job' do
+ Timecop.freeze(timestamp) do
+ job_copy = job.deep_dup
+
+ allow(logger).to receive(:info)
+ allow(subject).to receive(:log_job_start).and_call_original
+ allow(subject).to receive(:log_job_done).and_call_original
+
+ subject.call(job, 'test_queue') do
+ expect(job).to eq(job_copy)
+ end
+ end
+ end
+
context 'when the job args are bigger than the maximum allowed' do
it 'keeps args from the front until they exceed the limit' do
Timecop.freeze(timestamp) do
half_limit = Gitlab::Utils::LogLimitedArray::MAXIMUM_ARRAY_LENGTH / 2
job['args'] = [1, 2, 'a' * half_limit, 'b' * half_limit, 3]
- expected_args = job['args'].take(3) + ['...']
+ expected_args = job['args'].take(3).map(&:to_s) + ['...']
expect(logger).to receive(:info).with(start_payload.merge('args' => expected_args)).ordered
expect(logger).to receive(:info).with(end_payload.merge('args' => expected_args)).ordered
diff --git a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
index daee2c0bbd0..59639409183 100644
--- a/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::SidekiqMiddleware::ClientMetrics do
let(:queue) { :test }
let(:worker_class) { worker.class }
let(:job) { {} }
- let(:default_labels) { { queue: queue.to_s, boundary: "", external_dependencies: "no", feature_category: "", latency_sensitive: "no" } }
+ let(:default_labels) { { queue: queue.to_s, boundary: "", external_dependencies: "no", feature_category: "", urgency: "low" } }
shared_examples "a metrics client middleware" do
context "with mocked prometheus" do
@@ -46,17 +46,17 @@ describe Gitlab::SidekiqMiddleware::ClientMetrics do
it_behaves_like "a metrics client middleware" do
let(:worker) { TestNonAttributedWorker.new }
- let(:labels) { default_labels }
+ let(:labels) { default_labels.merge(urgency: "") }
end
end
context "when workers are attributed" do
- def create_attributed_worker_class(latency_sensitive, external_dependencies, resource_boundary, category)
+ def create_attributed_worker_class(urgency, external_dependencies, resource_boundary, category)
klass = Class.new do
include Sidekiq::Worker
include WorkerAttributes
- latency_sensitive_worker! if latency_sensitive
+ urgency urgency if urgency
worker_has_external_dependencies! if external_dependencies
worker_resource_boundary resource_boundary unless resource_boundary == :unknown
feature_category category unless category.nil?
@@ -64,17 +64,24 @@ describe Gitlab::SidekiqMiddleware::ClientMetrics do
stub_const("TestAttributedWorker", klass)
end
- let(:latency_sensitive) { false }
+ let(:urgency) { nil }
let(:external_dependencies) { false }
let(:resource_boundary) { :unknown }
let(:feature_category) { nil }
- let(:worker_class) { create_attributed_worker_class(latency_sensitive, external_dependencies, resource_boundary, feature_category) }
+ let(:worker_class) { create_attributed_worker_class(urgency, external_dependencies, resource_boundary, feature_category) }
let(:worker) { worker_class.new }
- context "latency sensitive" do
+ context "high urgency" do
it_behaves_like "a metrics client middleware" do
- let(:latency_sensitive) { true }
- let(:labels) { default_labels.merge(latency_sensitive: "yes") }
+ let(:urgency) { :high }
+ let(:labels) { default_labels.merge(urgency: "high") }
+ end
+ end
+
+ context "no urgency" do
+ it_behaves_like "a metrics client middleware" do
+ let(:urgency) { :throttled }
+ let(:labels) { default_labels.merge(urgency: "throttled") }
end
end
@@ -108,11 +115,11 @@ describe Gitlab::SidekiqMiddleware::ClientMetrics do
context "combined" do
it_behaves_like "a metrics client middleware" do
- let(:latency_sensitive) { true }
+ let(:urgency) { :high }
let(:external_dependencies) { true }
let(:resource_boundary) { :cpu }
let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(latency_sensitive: "yes", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
+ let(:labels) { default_labels.merge(urgency: "high", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
new file mode 100644
index 00000000000..b6e47afc7e8
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::DuplicateJobs::Client, :clean_gitlab_redis_queues do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestDeduplicationWorker'
+ end
+
+ include ApplicationWorker
+
+ def perform(*args)
+ end
+ end
+ end
+
+ before do
+ stub_const('TestDeduplicationWorker', worker_class)
+ end
+
+ describe '#call' do
+ it 'adds a correct duplicate tag to the jobs', :aggregate_failures do
+ TestDeduplicationWorker.bulk_perform_async([['args1'], ['args2'], ['args1']])
+
+ job1, job2, job3 = TestDeduplicationWorker.jobs
+
+ expect(job1['duplicate-of']).to be_nil
+ expect(job2['duplicate-of']).to be_nil
+ expect(job3['duplicate-of']).to eq(job1['jid'])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
new file mode 100644
index 00000000000..058e0737a25
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb
@@ -0,0 +1,157 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do
+ using RSpec::Parameterized::TableSyntax
+
+ subject(:duplicate_job) do
+ described_class.new(job, queue)
+ end
+
+ let(:job) { { 'class' => 'AuthorizedProjectsWorker', 'args' => [1], 'jid' => '123' } }
+ let(:queue) { 'authorized_projects' }
+
+ let(:idempotency_key) do
+ hash = Digest::SHA256.hexdigest("#{job['class']}:#{job['args'].join('-')}")
+ "#{Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE}:duplicate:#{queue}:#{hash}"
+ end
+
+ describe '#schedule' do
+ it 'calls schedule on the strategy' do
+ expect do |block|
+ expect_next_instance_of(Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting) do |strategy|
+ expect(strategy).to receive(:schedule).with(job, &block)
+ end
+
+ duplicate_job.schedule(&block)
+ end.to yield_control
+ end
+ end
+
+ describe '#perform' do
+ it 'calls perform on the strategy' do
+ expect do |block|
+ expect_next_instance_of(Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting) do |strategy|
+ expect(strategy).to receive(:perform).with(job, &block)
+ end
+
+ duplicate_job.perform(&block)
+ end.to yield_control
+ end
+ end
+
+ describe '#check!' do
+ context 'when there was no job in the queue yet' do
+ it { expect(duplicate_job.check!).to eq('123') }
+
+ it "adds a key with ttl set to #{described_class::DUPLICATE_KEY_TTL}" do
+ expect { duplicate_job.check! }
+ .to change { read_idempotency_key_with_ttl(idempotency_key) }
+ .from([nil, -2])
+ .to(['123', be_within(1).of(described_class::DUPLICATE_KEY_TTL)])
+ end
+ end
+
+ context 'when there was already a job with same arguments in the same queue' do
+ before do
+ set_idempotency_key(idempotency_key, 'existing-key')
+ end
+
+ it { expect(duplicate_job.check!).to eq('existing-key') }
+
+ it "does not change the existing key's TTL" do
+ expect { duplicate_job.check! }
+ .not_to change { read_idempotency_key_with_ttl(idempotency_key) }
+ .from(['existing-key', -1])
+ end
+
+ it 'sets the existing jid' do
+ duplicate_job.check!
+
+ expect(duplicate_job.existing_jid).to eq('existing-key')
+ end
+ end
+ end
+
+ describe '#delete!' do
+ context "when we didn't track the definition" do
+ it { expect { duplicate_job.delete! }.not_to raise_error }
+ end
+
+ context 'when the key exists in redis' do
+ before do
+ set_idempotency_key(idempotency_key, 'existing-key')
+ end
+
+ it 'removes the key from redis' do
+ expect { duplicate_job.delete! }
+ .to change { read_idempotency_key_with_ttl(idempotency_key) }
+ .from(['existing-key', -1])
+ .to([nil, -2])
+ end
+ end
+ end
+
+ describe '#duplicate?' do
+ it "raises an error if the check wasn't performed" do
+ expect { duplicate_job.duplicate? }.to raise_error /Call `#check!` first/
+ end
+
+ it 'returns false if the existing jid equals the job jid' do
+ duplicate_job.check!
+
+ expect(duplicate_job.duplicate?).to be(false)
+ end
+
+ it 'returns false if the existing jid is different from the job jid' do
+ set_idempotency_key(idempotency_key, 'a different jid')
+ duplicate_job.check!
+
+ expect(duplicate_job.duplicate?).to be(true)
+ end
+ end
+
+ describe 'droppable?' do
+ where(:idempotent, :duplicate, :feature_enabled) do
+ # [true, false].repeated_permutation(3)
+ [[true, true, true],
+ [true, true, false],
+ [true, false, true],
+ [true, false, false],
+ [false, true, true],
+ [false, true, false],
+ [false, false, true],
+ [false, false, false]]
+ end
+
+ with_them do
+ before do
+ allow(AuthorizedProjectsWorker).to receive(:idempotent?).and_return(idempotent)
+ allow(duplicate_job).to receive(:duplicate?).and_return(duplicate)
+ stub_feature_flags(drop_duplicate_sidekiq_jobs: feature_enabled)
+ end
+
+ it 'is droppable when all conditions are met' do
+ if idempotent && duplicate && feature_enabled
+ expect(duplicate_job).to be_droppable
+ else
+ expect(duplicate_job).not_to be_droppable
+ end
+ end
+ end
+ end
+
+ def set_idempotency_key(key, value = '1')
+ Sidekiq.redis { |r| r.set(key, value) }
+ end
+
+ def read_idempotency_key_with_ttl(key)
+ Sidekiq.redis do |redis|
+ redis.pipelined do |p|
+ p.get(key)
+ p.ttl(key)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
new file mode 100644
index 00000000000..0ea248fbcf1
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::DuplicateJobs::Server, :clean_gitlab_redis_queues do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestDeduplicationWorker'
+ end
+
+ include ApplicationWorker
+
+ def perform(*args)
+ end
+ end
+ end
+
+ before do
+ stub_const('TestDeduplicationWorker', worker_class)
+ end
+
+ around do |example|
+ Sidekiq::Testing.inline! { example.run }
+ end
+
+ before(:context) do
+ Sidekiq::Testing.server_middleware do |chain|
+ chain.add described_class
+ end
+ end
+
+ after(:context) do
+ Sidekiq::Testing.server_middleware do |chain|
+ chain.remove described_class
+ end
+ end
+
+ describe '#call' do
+ it 'removes the stored job from redis' do
+ bare_job = { 'class' => 'TestDeduplicationWorker', 'args' => ['hello'] }
+ job_definition = Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob.new(bare_job.dup, 'test_deduplication')
+
+ expect(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob)
+ .to receive(:new).with(a_hash_including(bare_job), 'test_deduplication')
+ .and_return(job_definition).twice # once in client middleware
+ expect(job_definition).to receive(:delete!).and_call_original
+
+ TestDeduplicationWorker.perform_async('hello')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb
new file mode 100644
index 00000000000..31b51260ebd
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
+ let(:fake_duplicate_job) do
+ instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob)
+ end
+
+ subject(:strategy) { described_class.new(fake_duplicate_job) }
+
+ describe '#schedule' do
+ before do
+ allow(Gitlab::SidekiqLogging::DeduplicationLogger.instance).to receive(:log)
+ end
+
+ it 'checks for duplicates before yielding' do
+ expect(fake_duplicate_job).to receive(:check!).ordered.and_return('a jid')
+ expect(fake_duplicate_job).to receive(:duplicate?).ordered.and_return(false)
+ expect(fake_duplicate_job).to receive(:droppable?).ordered.and_return(false)
+
+ expect { |b| strategy.schedule({}, &b) }.to yield_control
+ end
+
+ it 'adds the jid of the existing job to the job hash' do
+ allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
+ allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ job_hash = {}
+
+ expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
+ expect(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
+
+ strategy.schedule(job_hash) {}
+
+ expect(job_hash).to include('duplicate-of' => 'the jid')
+ end
+
+ context "when the job is droppable" do
+ before do
+ allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
+ allow(fake_duplicate_job).to receive(:duplicate?).and_return(true)
+ allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
+ allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
+ end
+
+ it 'drops the job' do
+ schedule_result = nil
+
+ expect(fake_duplicate_job).to receive(:droppable?).and_return(true)
+
+ expect { |b| schedule_result = strategy.schedule({}, &b) }.not_to yield_control
+ expect(schedule_result).to be(false)
+ end
+
+ it 'logs that the job wass dropped' do
+ fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
+
+ expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
+ expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), 'dropped until executing')
+
+ strategy.schedule({ 'jid' => 'new jid' }) {}
+ end
+ end
+ end
+
+ describe '#perform' do
+ it 'deletes the lock before executing' do
+ expect(fake_duplicate_job).to receive(:delete!).ordered
+ expect { |b| strategy.perform({}, &b) }.to yield_control
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
new file mode 100644
index 00000000000..6ecc2a3a5f8
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies do
+ describe '.for' do
+ it 'returns the right class for `until_executing`' do
+ expect(described_class.for(:until_executing)).to eq(described_class::UntilExecuting)
+ end
+
+ it 'raises an UnknownStrategyError when passing an unknown key' do
+ expect { described_class.for(:unknown) }.to raise_error(described_class::UnknownStrategyError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
index 65a961b34f8..3343587beff 100644
--- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do
let(:job) { {} }
let(:job_status) { :done }
let(:labels_with_job_status) { labels.merge(job_status: job_status.to_s) }
- let(:default_labels) { { queue: queue.to_s, boundary: "", external_dependencies: "no", feature_category: "", latency_sensitive: "no" } }
+ let(:default_labels) { { queue: queue.to_s, boundary: "", external_dependencies: "no", feature_category: "", urgency: "low" } }
shared_examples "a metrics middleware" do
context "with mocked prometheus" do
@@ -20,6 +20,8 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do
let(:queue_duration_seconds) { double('queue duration seconds metric') }
let(:completion_seconds_metric) { double('completion seconds metric') }
let(:user_execution_seconds_metric) { double('user execution seconds metric') }
+ let(:db_seconds_metric) { double('db seconds metric') }
+ let(:gitaly_seconds_metric) { double('gitaly seconds metric') }
let(:failed_total_metric) { double('failed total metric') }
let(:retried_total_metric) { double('retried total metric') }
let(:running_jobs_metric) { double('running jobs metric') }
@@ -28,6 +30,8 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do
allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_queue_duration_seconds, anything, anything, anything).and_return(queue_duration_seconds)
allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything, anything).and_return(completion_seconds_metric)
allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_cpu_seconds, anything, anything, anything).and_return(user_execution_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_db_seconds, anything, anything, anything).and_return(db_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_gitaly_seconds, anything, anything, anything).and_return(gitaly_seconds_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric)
allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :all).and_return(running_jobs_metric)
@@ -55,16 +59,23 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do
let(:queue_duration_for_job) { 0.01 }
+ let(:db_duration) { 3 }
+ let(:gitaly_duration) { 4 }
+
before do
allow(subject).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after)
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after)
allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job)
+ allow(ActiveRecord::LogSubscriber).to receive(:runtime).and_return(db_duration * 1000)
+ allow(subject).to receive(:get_gitaly_time).and_return(gitaly_duration)
expect(running_jobs_metric).to receive(:increment).with(labels, 1)
expect(running_jobs_metric).to receive(:increment).with(labels, -1)
expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job
expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration)
+ expect(db_seconds_metric).to receive(:observe).with(labels_with_job_status, db_duration)
+ expect(gitaly_seconds_metric).to receive(:observe).with(labels_with_job_status, gitaly_duration)
expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration)
end
@@ -130,34 +141,34 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do
include Sidekiq::Worker
end
let(:worker) { TestNonAttributedWorker.new }
- let(:labels) { default_labels }
+ let(:labels) { default_labels.merge(urgency: "") }
it_behaves_like "a metrics middleware"
end
context "when workers are attributed" do
- def create_attributed_worker_class(latency_sensitive, external_dependencies, resource_boundary, category)
+ def create_attributed_worker_class(urgency, external_dependencies, resource_boundary, category)
Class.new do
include Sidekiq::Worker
include WorkerAttributes
- latency_sensitive_worker! if latency_sensitive
+ urgency urgency if urgency
worker_has_external_dependencies! if external_dependencies
worker_resource_boundary resource_boundary unless resource_boundary == :unknown
feature_category category unless category.nil?
end
end
- let(:latency_sensitive) { false }
+ let(:urgency) { nil }
let(:external_dependencies) { false }
let(:resource_boundary) { :unknown }
let(:feature_category) { nil }
- let(:worker_class) { create_attributed_worker_class(latency_sensitive, external_dependencies, resource_boundary, feature_category) }
+ let(:worker_class) { create_attributed_worker_class(urgency, external_dependencies, resource_boundary, feature_category) }
let(:worker) { worker_class.new }
- context "latency sensitive" do
- let(:latency_sensitive) { true }
- let(:labels) { default_labels.merge(latency_sensitive: "yes") }
+ context "high urgency" do
+ let(:urgency) { :high }
+ let(:labels) { default_labels.merge(urgency: "high") }
it_behaves_like "a metrics middleware"
end
@@ -191,11 +202,11 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do
end
context "combined" do
- let(:latency_sensitive) { true }
+ let(:urgency) { :throttled }
let(:external_dependencies) { true }
let(:resource_boundary) { :cpu }
let(:feature_category) { :authentication }
- let(:labels) { default_labels.merge(latency_sensitive: "yes", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
+ let(:labels) { default_labels.merge(urgency: "throttled", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
it_behaves_like "a metrics middleware"
end
diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb
index 19242d25e27..88f83ebc2ac 100644
--- a/spec/lib/gitlab/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb
@@ -8,6 +8,8 @@ describe Gitlab::SidekiqMiddleware do
include Sidekiq::Worker
def perform(_arg)
+ Gitlab::SafeRequestStore['gitaly_call_actual'] = 1
+ Gitlab::GitalyClient.query_time = 5
end
end
@@ -46,7 +48,8 @@ describe Gitlab::SidekiqMiddleware do
Gitlab::SidekiqMiddleware::MemoryKiller,
Gitlab::SidekiqMiddleware::RequestStoreMiddleware,
Gitlab::SidekiqMiddleware::WorkerContext::Server,
- Gitlab::SidekiqMiddleware::AdminMode::Server
+ Gitlab::SidekiqMiddleware::AdminMode::Server,
+ Gitlab::SidekiqMiddleware::DuplicateJobs::Server
]
end
let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares }
@@ -98,6 +101,24 @@ describe Gitlab::SidekiqMiddleware do
it "passes through server middlewares" do
worker_class.perform_async(*job_args)
end
+
+ context "server metrics" do
+ let(:gitaly_histogram) { double(:gitaly_histogram) }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:histogram).and_call_original
+
+ allow(Gitlab::Metrics).to receive(:histogram)
+ .with(:sidekiq_jobs_gitaly_seconds, anything, anything, anything)
+ .and_return(gitaly_histogram)
+ end
+
+ it "records correct Gitaly duration" do
+ expect(gitaly_histogram).to receive(:observe).with(anything, 5.0)
+
+ worker_class.perform_async(*job_args)
+ end
+ end
end
end
@@ -117,7 +138,8 @@ describe Gitlab::SidekiqMiddleware do
Gitlab::SidekiqMiddleware::ClientMetrics,
Gitlab::SidekiqMiddleware::WorkerContext::Client,
Labkit::Middleware::Sidekiq::Client,
- Gitlab::SidekiqMiddleware::AdminMode::Client
+ Gitlab::SidekiqMiddleware::AdminMode::Client,
+ Gitlab::SidekiqMiddleware::DuplicateJobs::Client
]
end
diff --git a/spec/lib/gitlab/sidekiq_queue_spec.rb b/spec/lib/gitlab/sidekiq_queue_spec.rb
new file mode 100644
index 00000000000..f5be8d9bfed
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_queue_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqQueue, :clean_gitlab_redis_queues do
+ around do |example|
+ Sidekiq::Queue.new('authorized_projects').clear
+ Sidekiq::Testing.disable!(&example)
+ Sidekiq::Queue.new('authorized_projects').clear
+ end
+
+ def add_job(user, args)
+ Sidekiq::Client.push(
+ 'class' => 'AuthorizedProjectsWorker',
+ 'queue' => 'authorized_projects',
+ 'args' => args,
+ 'meta.user' => user.username
+ )
+ end
+
+ describe '#drop_jobs!' do
+ shared_examples 'queue processing' do
+ let(:sidekiq_queue) { described_class.new('authorized_projects') }
+ let_it_be(:sidekiq_queue_user) { create(:user) }
+
+ before do
+ add_job(create(:user), [1])
+ add_job(sidekiq_queue_user, [2])
+ add_job(sidekiq_queue_user, [3])
+ end
+
+ context 'when the queue is not processed in time' do
+ before do
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(1, 2, 12)
+ end
+
+ it 'returns a non-completion flag, the number of jobs deleted, and the remaining queue size' do
+ expect(sidekiq_queue.drop_jobs!(search_metadata, timeout: 10))
+ .to eq(completed: false,
+ deleted_jobs: timeout_deleted,
+ queue_size: 3 - timeout_deleted)
+ end
+ end
+
+ context 'when the queue is processed in time' do
+ it 'returns a completion flag, the number of jobs deleted, and the remaining queue size' do
+ expect(sidekiq_queue.drop_jobs!(search_metadata, timeout: 10))
+ .to eq(completed: true,
+ deleted_jobs: no_timeout_deleted,
+ queue_size: 3 - no_timeout_deleted)
+ end
+ end
+ end
+
+ context 'when there are no matching jobs' do
+ include_examples 'queue processing' do
+ let(:search_metadata) { { project: 1 } }
+ let(:timeout_deleted) { 0 }
+ let(:no_timeout_deleted) { 0 }
+ end
+ end
+
+ context 'when there are matching jobs' do
+ include_examples 'queue processing' do
+ let(:search_metadata) { { user: sidekiq_queue_user.username } }
+ let(:timeout_deleted) { 1 }
+ let(:no_timeout_deleted) { 2 }
+ end
+ end
+
+ context 'when there are no valid metadata keys passed' do
+ it 'raises NoMetadataError' do
+ add_job(create(:user), [1])
+
+ expect { described_class.new('authorized_projects').drop_jobs!({ username: 'sidekiq_queue_user' }, timeout: 1) }
+ .to raise_error(described_class::NoMetadataError)
+ end
+ end
+
+ context 'when the queue does not exist' do
+ it 'raises InvalidQueueError' do
+ expect { described_class.new('foo').drop_jobs!({ user: 'sidekiq_queue_user' }, timeout: 1) }
+ .to raise_error(described_class::InvalidQueueError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
index 962ac3668bc..1a45b0e06ba 100644
--- a/spec/lib/gitlab/slash_commands/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
@@ -22,11 +22,11 @@ describe Gitlab::SlashCommands::IssueMove, service: true do
end
describe '#execute' do
- set(:user) { create(:user) }
- set(:issue) { create(:issue) }
- set(:chat_name) { create(:chat_name, user: user) }
- set(:project) { issue.project }
- set(:other_project) { create(:project, namespace: project.namespace) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:issue) { create(:issue) }
+ let_it_be(:chat_name) { create(:chat_name, user: user) }
+ let_it_be(:project) { issue.project }
+ let_it_be(:other_project) { create(:project, namespace: project.namespace) }
before do
[project, other_project].each { |prj| prj.add_maintainer(user) }
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
index 56b64d32192..7726c3b6a87 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -3,11 +3,11 @@
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueMove do
- set(:admin) { create(:admin) }
- set(:project) { create(:project) }
- set(:other_project) { create(:project) }
- set(:old_issue) { create(:issue, project: project) }
- set(:new_issue) { Issues::MoveService.new(project, admin).execute(old_issue, other_project) }
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:other_project) { create(:project) }
+ let_it_be(:old_issue, reload: true) { create(:issue, project: project) }
+ let(:new_issue) { Issues::MoveService.new(project, admin).execute(old_issue, other_project) }
let(:attachment) { subject[:attachments].first }
subject { described_class.new(new_issue).present(old_issue) }
diff --git a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
index 082ffa855b7..580da497944 100644
--- a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
+++ b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
@@ -15,23 +15,87 @@ describe Gitlab::Template::Finders::GlobalTemplateFinder do
FileUtils.rm_rf(base_dir)
end
- subject(:finder) { described_class.new(base_dir, '', 'Foo' => '', 'Bar' => 'bar') }
+ subject(:finder) { described_class.new(base_dir, '', { 'General' => '', 'Bar' => 'Bar' }, exclusions: exclusions) }
+
+ let(:exclusions) { [] }
describe '.find' do
- it 'finds a template in the Foo category' do
- create_template!('test-template')
+ context 'with a non-prefixed General template' do
+ before do
+ create_template!('test-template')
+ end
- expect(finder.find('test-template')).to be_present
- end
+ it 'finds the template with no prefix' do
+ expect(finder.find('test-template')).to be_present
+ end
+
+ it 'does not find a prefixed template' do
+ expect(finder.find('Bar/test-template')).to be_nil
+ end
+
+ it 'does not permit path traversal requests' do
+ expect { finder.find('../foo') }.to raise_error(/Invalid path/)
+ end
- it 'finds a template in the Bar category' do
- create_template!('bar/test-template')
+ context 'while listed as an exclusion' do
+ let(:exclusions) { %w[test-template] }
- expect(finder.find('test-template')).to be_present
+ it 'does not find the template without a prefix' do
+ expect(finder.find('test-template')).to be_nil
+ end
+
+ it 'does not find the template with a prefix' do
+ expect(finder.find('Bar/test-template')).to be_nil
+ end
+
+ it 'finds another prefixed template with the same name' do
+ create_template!('Bar/test-template')
+
+ expect(finder.find('test-template')).to be_nil
+ expect(finder.find('Bar/test-template')).to be_present
+ end
+ end
end
- it 'does not permit path traversal requests' do
- expect { finder.find('../foo') }.to raise_error(/Invalid path/)
+ context 'with a prefixed template' do
+ before do
+ create_template!('Bar/test-template')
+ end
+
+ it 'finds the template with a prefix' do
+ expect(finder.find('Bar/test-template')).to be_present
+ end
+
+ # NOTE: This spec fails, the template Bar/test-template is found
+ # See Gitlab issue: https://gitlab.com/gitlab-org/gitlab/issues/205719
+ xit 'does not find the template without a prefix' do
+ expect(finder.find('test-template')).to be_nil
+ end
+
+ it 'does not permit path traversal requests' do
+ expect { finder.find('../foo') }.to raise_error(/Invalid path/)
+ end
+
+ context 'while listed as an exclusion' do
+ let(:exclusions) { %w[Bar/test-template] }
+
+ it 'does not find the template with a prefix' do
+ expect(finder.find('Bar/test-template')).to be_nil
+ end
+
+ # NOTE: This spec fails, the template Bar/test-template is found
+ # See Gitlab issue: https://gitlab.com/gitlab-org/gitlab/issues/205719
+ xit 'does not find the template without a prefix' do
+ expect(finder.find('test-template')).to be_nil
+ end
+
+ it 'finds another non-prefixed template with the same name' do
+ create_template!('Bar/test-template')
+
+ expect(finder.find('test-template')).to be_present
+ expect(finder.find('Bar/test-template')).to be_nil
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
index c8f2a37c5d6..cd5cde76b73 100644
--- a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
+++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
@@ -3,8 +3,7 @@
require 'spec_helper'
describe Gitlab::Template::Finders::RepoTemplateFinder do
- set(:project) { create(:project, :repository) }
-
+ let_it_be(:project) { create(:project, :repository) }
let(:categories) { { 'HTML' => 'html' } }
subject(:finder) { described_class.new(project, 'files/', '.html', categories) }
diff --git a/spec/lib/gitlab/tracing_spec.rb b/spec/lib/gitlab/tracing_spec.rb
deleted file mode 100644
index e913bb600ec..00000000000
--- a/spec/lib/gitlab/tracing_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-
-describe Gitlab::Tracing do
- using RSpec::Parameterized::TableSyntax
-
- describe '.enabled?' do
- where(:connection_string, :enabled_state) do
- nil | false
- "" | false
- "opentracing://jaeger" | true
- end
-
- with_them do
- it 'returns the correct state for .enabled?' do
- expect(described_class).to receive(:connection_string).and_return(connection_string)
-
- expect(described_class.enabled?).to eq(enabled_state)
- end
- end
- end
-
- describe '.tracing_url_enabled?' do
- where(:enabled?, :tracing_url_template, :tracing_url_enabled_state) do
- false | nil | false
- false | "" | false
- false | "http://localhost" | false
- true | nil | false
- true | "" | false
- true | "http://localhost" | true
- end
-
- with_them do
- it 'returns the correct state for .tracing_url_enabled?' do
- expect(described_class).to receive(:enabled?).and_return(enabled?)
- allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template)
-
- expect(described_class.tracing_url_enabled?).to eq(tracing_url_enabled_state)
- end
- end
- end
-
- describe '.tracing_url' do
- where(:tracing_url_enabled?, :tracing_url_template, :correlation_id, :process_name, :tracing_url) do
- false | "https://localhost" | "123" | "web" | nil
- true | "https://localhost" | "123" | "web" | "https://localhost"
- true | "https://localhost?service={{ service }}" | "123" | "web" | "https://localhost?service=web"
- true | "https://localhost?c={{ correlation_id }}" | "123" | "web" | "https://localhost?c=123"
- true | "https://localhost?c={{ correlation_id }}&s={{ service }}" | "123" | "web" | "https://localhost?c=123&s=web"
- true | "https://localhost?c={{ correlation_id }}" | nil | "web" | "https://localhost?c="
- true | "https://localhost?c={{ correlation_id }}&s=%22{{ service }}%22" | "123" | "web" | "https://localhost?c=123&s=%22web%22"
- true | "https://localhost?c={{correlation_id}}&s={{service}}" | "123" | "web" | "https://localhost?c=123&s=web"
- true | "https://localhost?c={{correlation_id }}&s={{ service}}" | "123" | "web" | "https://localhost?c=123&s=web"
- end
-
- with_them do
- it 'returns the correct state for .tracing_url' do
- expect(described_class).to receive(:tracing_url_enabled?).and_return(tracing_url_enabled?)
- allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template)
- allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return(correlation_id)
- allow(Gitlab).to receive(:process_name).and_return(process_name)
-
- expect(described_class.tracing_url).to eq(tracing_url)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 97859c82e9e..08678de87c9 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -502,63 +502,31 @@ describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
end
end
- context 'with ip ranges in whitelist' do
- let(:ipv4_range) { '127.0.0.0/28' }
- let(:ipv6_range) { 'fd84:6d02:f6d8:c89e::/124' }
-
+ context 'with ports' do
let(:whitelist) do
- [
- ipv4_range,
- ipv6_range
- ]
+ ["127.0.0.1:2000"]
end
- it 'blocks ipv4 range when not in whitelist' do
- stub_application_setting(outbound_local_requests_whitelist: [])
-
- IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
- expect(described_class).to be_blocked_url("http://#{ip}",
- url_blocker_attributes)
+ it 'allows domain with port when resolved ip has port whitelisted' do
+ stub_domain_resolv("www.resolve-domain.com", '127.0.0.1') do
+ expect(described_class).not_to be_blocked_url("http://www.resolve-domain.com:2000", url_blocker_attributes)
end
end
-
- it 'allows all ipv4s in the range when in whitelist' do
- IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
- expect(described_class).not_to be_blocked_url("http://#{ip}",
- url_blocker_attributes)
- end
- end
-
- it 'blocks ipv6 range when not in whitelist' do
- stub_application_setting(outbound_local_requests_whitelist: [])
-
- IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
- expect(described_class).to be_blocked_url("http://[#{ip}]",
- url_blocker_attributes)
- end
- end
-
- it 'allows all ipv6s in the range when in whitelist' do
- IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
- expect(described_class).not_to be_blocked_url("http://[#{ip}]",
- url_blocker_attributes)
- end
- end
-
- it 'blocks IPs outside the range' do
- expect(described_class).to be_blocked_url("http://[fd84:6d02:f6d8:c89e:0:0:1:f]",
- url_blocker_attributes)
-
- expect(described_class).to be_blocked_url("http://127.0.1.15",
- url_blocker_attributes)
- end
end
end
end
- def stub_domain_resolv(domain, ip, &block)
- address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false, ipv4?: false)
- allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
+ def stub_domain_resolv(domain, ip, port = 80, &block)
+ address = instance_double(Addrinfo,
+ ip_address: ip,
+ ipv4_private?: true,
+ ipv6_linklocal?: false,
+ ipv4_loopback?: false,
+ ipv6_loopback?: false,
+ ipv4?: false,
+ ip_port: port
+ )
+ allow(Addrinfo).to receive(:getaddrinfo).with(domain, port, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
yield
diff --git a/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb b/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb
new file mode 100644
index 00000000000..34ea6c328e6
--- /dev/null
+++ b/spec/lib/gitlab/url_blockers/domain_whitelist_entry_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UrlBlockers::DomainWhitelistEntry do
+ let(:domain) { 'www.example.com' }
+
+ describe '#initialize' do
+ it 'initializes without port' do
+ domain_whitelist_entry = described_class.new(domain)
+
+ expect(domain_whitelist_entry.domain).to eq(domain)
+ expect(domain_whitelist_entry.port).to be(nil)
+ end
+
+ it 'initializes with port' do
+ port = 8080
+ domain_whitelist_entry = described_class.new(domain, port: port)
+
+ expect(domain_whitelist_entry.domain).to eq(domain)
+ expect(domain_whitelist_entry.port).to eq(port)
+ end
+ end
+
+ describe '#match?' do
+ it 'matches when domain and port are equal' do
+ port = 8080
+ domain_whitelist_entry = described_class.new(domain, port: port)
+
+ expect(domain_whitelist_entry).to be_match(domain, port)
+ end
+
+ it 'matches any port when port is nil' do
+ domain_whitelist_entry = described_class.new(domain)
+
+ expect(domain_whitelist_entry).to be_match(domain, 8080)
+ expect(domain_whitelist_entry).to be_match(domain, 9090)
+ end
+
+ it 'does not match when port is present but requested_port is nil' do
+ domain_whitelist_entry = described_class.new(domain, port: 8080)
+
+ expect(domain_whitelist_entry).not_to be_match(domain, nil)
+ end
+
+ it 'matches when port and requested_port are nil' do
+ domain_whitelist_entry = described_class.new(domain)
+
+ expect(domain_whitelist_entry).to be_match(domain)
+ end
+
+ it 'does not match if domain is not equal' do
+ domain_whitelist_entry = described_class.new(domain)
+
+ expect(domain_whitelist_entry).not_to be_match('www.gitlab.com', 8080)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb b/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb
new file mode 100644
index 00000000000..042d135d265
--- /dev/null
+++ b/spec/lib/gitlab/url_blockers/ip_whitelist_entry_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UrlBlockers::IpWhitelistEntry do
+ let(:ipv4) { IPAddr.new('192.168.1.1') }
+
+ describe '#initialize' do
+ it 'initializes without port' do
+ ip_whitelist_entry = described_class.new(ipv4)
+
+ expect(ip_whitelist_entry.ip).to eq(ipv4)
+ expect(ip_whitelist_entry.port).to be(nil)
+ end
+
+ it 'initializes with port' do
+ port = 8080
+ ip_whitelist_entry = described_class.new(ipv4, port: port)
+
+ expect(ip_whitelist_entry.ip).to eq(ipv4)
+ expect(ip_whitelist_entry.port).to eq(port)
+ end
+ end
+
+ describe '#match?' do
+ it 'matches with equivalent IP and port' do
+ port = 8080
+ ip_whitelist_entry = described_class.new(ipv4, port: port)
+
+ expect(ip_whitelist_entry).to be_match(ipv4.to_s, port)
+ end
+
+ it 'matches any port when port is nil' do
+ ip_whitelist_entry = described_class.new(ipv4)
+
+ expect(ip_whitelist_entry).to be_match(ipv4.to_s, 8080)
+ expect(ip_whitelist_entry).to be_match(ipv4.to_s, 9090)
+ end
+
+ it 'does not match when port is present but requested_port is nil' do
+ ip_whitelist_entry = described_class.new(ipv4, port: 8080)
+
+ expect(ip_whitelist_entry).not_to be_match(ipv4.to_s, nil)
+ end
+
+ it 'matches when port and requested_port are nil' do
+ ip_whitelist_entry = described_class.new(ipv4)
+
+ expect(ip_whitelist_entry).to be_match(ipv4.to_s)
+ end
+
+ it 'works with ipv6' do
+ ipv6 = IPAddr.new('fe80::c800:eff:fe74:8')
+ ip_whitelist_entry = described_class.new(ipv6)
+
+ expect(ip_whitelist_entry).to be_match(ipv6.to_s, 8080)
+ end
+
+ it 'matches ipv4 within IPv4 range' do
+ ipv4_range = IPAddr.new('127.0.0.0/28')
+ ip_whitelist_entry = described_class.new(ipv4_range)
+
+ expect(ip_whitelist_entry).to be_match(ipv4_range.to_range.last.to_s, 8080)
+ expect(ip_whitelist_entry).not_to be_match('127.0.1.1', 8080)
+ end
+
+ it 'matches IPv6 within IPv6 range' do
+ ipv6_range = IPAddr.new('fd84:6d02:f6d8:c89e::/124')
+ ip_whitelist_entry = described_class.new(ipv6_range)
+
+ expect(ip_whitelist_entry).to be_match(ipv6_range.to_range.last.to_s, 8080)
+ expect(ip_whitelist_entry).not_to be_match('fd84:6d02:f6d8:f::f', 8080)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb b/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb
index 906e0f0ba3d..e43cd819838 100644
--- a/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb
+++ b/spec/lib/gitlab/url_blockers/url_whitelist_spec.rb
@@ -13,20 +13,17 @@ describe Gitlab::UrlBlockers::UrlWhitelist do
end
describe '#domain_whitelisted?' do
- let(:whitelist) do
- [
- 'www.example.com',
- 'example.com'
- ]
- end
+ let(:whitelist) { ['www.example.com', 'example.com'] }
it 'returns true if domains present in whitelist' do
+ not_whitelisted = ['subdomain.example.com', 'example.org']
+
aggregate_failures do
whitelist.each do |domain|
expect(described_class).to be_domain_whitelisted(domain)
end
- ['subdomain.example.com', 'example.org'].each do |domain|
+ not_whitelisted.each do |domain|
expect(described_class).not_to be_domain_whitelisted(domain)
end
end
@@ -35,6 +32,28 @@ describe Gitlab::UrlBlockers::UrlWhitelist do
it 'returns false when domain is blank' do
expect(described_class).not_to be_domain_whitelisted(nil)
end
+
+ context 'with ports' do
+ let(:whitelist) { ['example.io:3000'] }
+
+ it 'returns true if domain and ports present in whitelist' do
+ parsed_whitelist = [['example.io', { port: 3000 }]]
+ not_whitelisted = [
+ 'example.io',
+ ['example.io', { port: 3001 }]
+ ]
+
+ aggregate_failures do
+ parsed_whitelist.each do |domain_and_port|
+ expect(described_class).to be_domain_whitelisted(*domain_and_port)
+ end
+
+ not_whitelisted.each do |domain_and_port|
+ expect(described_class).not_to be_domain_whitelisted(*domain_and_port)
+ end
+ end
+ end
+ end
end
describe '#ip_whitelisted?' do
@@ -68,5 +87,78 @@ describe Gitlab::UrlBlockers::UrlWhitelist do
it 'returns false when ip is blank' do
expect(described_class).not_to be_ip_whitelisted(nil)
end
+
+ context 'with ip ranges in whitelist' do
+ let(:ipv4_range) { '127.0.0.0/28' }
+ let(:ipv6_range) { 'fd84:6d02:f6d8:c89e::/124' }
+
+ let(:whitelist) do
+ [
+ ipv4_range,
+ ipv6_range
+ ]
+ end
+
+ it 'does not whitelist ipv4 range when not in whitelist' do
+ stub_application_setting(outbound_local_requests_whitelist: [])
+
+ IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
+ expect(described_class).not_to be_ip_whitelisted(ip.to_s)
+ end
+ end
+
+ it 'whitelists all ipv4s in the range when in whitelist' do
+ IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
+ expect(described_class).to be_ip_whitelisted(ip.to_s)
+ end
+ end
+
+ it 'does not whitelist ipv6 range when not in whitelist' do
+ stub_application_setting(outbound_local_requests_whitelist: [])
+
+ IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
+ expect(described_class).not_to be_ip_whitelisted(ip.to_s)
+ end
+ end
+
+ it 'whitelists all ipv6s in the range when in whitelist' do
+ IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
+ expect(described_class).to be_ip_whitelisted(ip.to_s)
+ end
+ end
+
+ it 'does not whitelist IPs outside the range' do
+ expect(described_class).not_to be_ip_whitelisted("fd84:6d02:f6d8:c89e:0:0:1:f")
+
+ expect(described_class).not_to be_ip_whitelisted("127.0.1.15")
+ end
+ end
+
+ context 'with ports' do
+ let(:whitelist) { ['127.0.0.9:3000', '[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443'] }
+
+ it 'returns true if ip and ports present in whitelist' do
+ parsed_whitelist = [
+ ['127.0.0.9', { port: 3000 }],
+ ['[2001:db8:85a3:8d3:1319:8a2e:370:7348]', { port: 443 }]
+ ]
+ not_whitelisted = [
+ '127.0.0.9',
+ ['127.0.0.9', { port: 3001 }],
+ '[2001:db8:85a3:8d3:1319:8a2e:370:7348]',
+ ['[2001:db8:85a3:8d3:1319:8a2e:370:7348]', { port: 3001 }]
+ ]
+
+ aggregate_failures do
+ parsed_whitelist.each do |ip_and_port|
+ expect(described_class).to be_ip_whitelisted(*ip_and_port)
+ end
+
+ not_whitelisted.each do |ip_and_port|
+ expect(described_class).not_to be_ip_whitelisted(*ip_and_port)
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 49011b100ab..c2eb1b4c25d 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -14,13 +14,25 @@ describe Gitlab::UrlBuilder do
end
end
+ context 'when passing a batch loaded Commit' do
+ it 'returns a proper URL' do
+ commit = BatchLoader.for(:commit).batch do |batch, loader|
+ batch.each { |commit| loader.call(:commit, build_stubbed(:commit)) }
+ end
+
+ url = described_class.build(commit)
+
+ expect(url).to eq "#{Settings.gitlab['url']}/#{commit.project.full_path}/-/commit/#{commit.id}"
+ end
+ end
+
context 'when passing an Issue' do
it 'returns a proper URL' do
issue = build_stubbed(:issue, iid: 42)
url = described_class.build(issue)
- expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.full_path}/issues/#{issue.iid}"
+ expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.full_path}/-/issues/#{issue.iid}"
end
end
@@ -107,7 +119,7 @@ describe Gitlab::UrlBuilder do
url = described_class.build(note)
- expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.full_path}/issues/#{issue.iid}#note_#{note.id}"
+ expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.full_path}/-/issues/#{issue.iid}#note_#{note.id}"
end
end
@@ -160,7 +172,7 @@ describe Gitlab::UrlBuilder do
project = build_stubbed(:project)
expect { described_class.build(project) }
- .to raise_error(NotImplementedError, 'No URL builder defined for Project')
+ .to raise_error(NotImplementedError, "No URL builder defined for #{project.inspect}")
end
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index b50481a85cd..21117f11f63 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -27,7 +27,7 @@ describe Gitlab::UsageData do
create(:service, project: projects[1], type: 'SlackService', active: true)
create(:service, project: projects[2], type: 'SlackService', active: true)
create(:service, project: projects[2], type: 'MattermostService', active: false)
- create(:service, project: projects[2], type: 'MattermostService', active: true, template: true)
+ create(:service, :template, type: 'MattermostService', active: true)
create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true)
create(:project_error_tracking_setting, project: projects[0])
create(:project_error_tracking_setting, project: projects[1], enabled: false)
@@ -324,6 +324,24 @@ describe Gitlab::UsageData do
end
end
+ describe '#cycle_analytics_usage_data' do
+ subject { described_class.cycle_analytics_usage_data }
+
+ it 'works when queries time out in new' do
+ allow(Gitlab::CycleAnalytics::UsageData)
+ .to receive(:new).and_raise(ActiveRecord::StatementInvalid.new(''))
+
+ expect { subject }.not_to raise_error
+ end
+
+ it 'works when queries time out in to_json' do
+ allow_any_instance_of(Gitlab::CycleAnalytics::UsageData)
+ .to receive(:to_json).and_raise(ActiveRecord::StatementInvalid.new(''))
+
+ expect { subject }.not_to raise_error
+ end
+ end
+
describe '#ingress_modsecurity_usage' do
subject { described_class.ingress_modsecurity_usage }
@@ -370,26 +388,19 @@ describe Gitlab::UsageData do
end
end
- describe '#approximate_counts' do
- it 'gets approximate counts for selected models', :aggregate_failures do
- create(:label)
-
- expect(Gitlab::Database::Count).to receive(:approximate_counts)
- .with(described_class::APPROXIMATE_COUNT_MODELS).once.and_call_original
+ describe '#distinct_count' do
+ let(:relation) { double(:relation) }
- counts = described_class.approximate_counts.values
+ it 'returns the count when counting succeeds' do
+ allow(relation).to receive(:distinct_count_by).and_return(1)
- expect(counts.count).to eq(described_class::APPROXIMATE_COUNT_MODELS.count)
- expect(counts.any? { |count| count < 0 }).to be_falsey
+ expect(described_class.distinct_count(relation, batch: false)).to eq(1)
end
- it 'returns default values if counts can not be retrieved', :aggregate_failures do
- described_class::APPROXIMATE_COUNT_MODELS.map do |model|
- model.name.underscore.pluralize.to_sym
- end
+ it 'returns the fallback value when counting fails' do
+ allow(relation).to receive(:distinct_count_by).and_raise(ActiveRecord::StatementInvalid.new(''))
- expect(Gitlab::Database::Count).to receive(:approximate_counts).and_return({})
- expect(described_class.approximate_counts.values.uniq).to eq([-1])
+ expect(described_class.distinct_count(relation, fallback: 15, batch: false)).to eq(15)
end
end
end
diff --git a/spec/lib/gitlab/user_access_snippet_spec.rb b/spec/lib/gitlab/user_access_snippet_spec.rb
new file mode 100644
index 00000000000..57e52e2e93d
--- /dev/null
+++ b/spec/lib/gitlab/user_access_snippet_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UserAccessSnippet do
+ subject(:access) { described_class.new(user, snippet: snippet) }
+
+ let_it_be(:project) { create(:project, :private) }
+ let_it_be(:snippet) { create(:project_snippet, :private, project: project) }
+ let(:user) { create(:user) }
+
+ describe '#can_do_action?' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :ability, snippet).and_return(:foo)
+ end
+
+ context 'when can access_git' do
+ it 'calls Ability#allowed? and returns its result' do
+ expect(access.can_do_action?(:ability)).to eq(:foo)
+ end
+ end
+
+ context 'when can not access_git' do
+ it 'disallows access' do
+ expect(Ability).to receive(:allowed?).with(user, :access_git, :global).and_return(false)
+
+ expect(access.can_do_action?(:ability)).to eq(false)
+ end
+ end
+
+ context 'when user is nil' do
+ let(:user) { nil }
+
+ it 'disallows access' do
+ expect(access.can_do_action?(:ability)).to eq(false)
+ end
+ end
+ end
+
+ describe '#can_push_to_branch?' do
+ include ProjectHelpers
+
+ [:anonymous, :non_member, :guest, :reporter, :maintainer, :admin, :author].each do |membership|
+ context membership.to_s do
+ let(:user) do
+ membership == :author ? snippet.author : create_user_from_membership(project, membership)
+ end
+
+ context 'when can access_git' do
+ it 'respects accessibility' do
+ expected_result = Ability.allowed?(user, :update_snippet, snippet)
+
+ expect(access.can_push_to_branch?('random_branch')).to eq(expected_result)
+ end
+ end
+
+ context 'when can not access_git' do
+ it 'disallows access' do
+ expect(Ability).to receive(:allowed?).with(user, :access_git, :global).and_return(false) if user
+
+ expect(access.can_push_to_branch?('random_branch')).to eq(false)
+ end
+ end
+ end
+ end
+
+ context 'when snippet is nil' do
+ let(:user) { create_user_from_membership(project, :admin) }
+ let(:snippet) { nil }
+
+ it 'disallows access' do
+ expect(access.can_push_to_branch?('random_branch')).to eq(false)
+ end
+ end
+ end
+
+ describe '#can_create_tag?' do
+ it 'returns false' do
+ expect(access.can_create_tag?('random_tag')).to be_falsey
+ end
+ end
+
+ describe '#can_delete_branch?' do
+ it 'returns false' do
+ expect(access.can_delete_branch?('random_branch')).to be_falsey
+ end
+ end
+
+ describe '#can_merge_to_branch?' do
+ it 'returns false' do
+ expect(access.can_merge_to_branch?('random_branch')).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 181ea1e7fd3..78370f0136c 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -57,32 +57,27 @@ describe Gitlab::UserAccess do
expect(project_access.can_push_to_branch?('master')).to be_truthy
end
- it 'returns false if user is developer and project is fully protected' do
- empty_project.add_developer(user)
- stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_FULL)
-
- expect(project_access.can_push_to_branch?('master')).to be_falsey
- end
-
- it 'returns false if user is developer and it is not allowed to push new commits but can merge into branch' do
- empty_project.add_developer(user)
- stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
-
- expect(project_access.can_push_to_branch?('master')).to be_falsey
- end
-
- it 'returns true if user is developer and project is unprotected' do
- empty_project.add_developer(user)
- stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
-
- expect(project_access.can_push_to_branch?('master')).to be_truthy
- end
-
- it 'returns true if user is developer and project grants developers permission' do
- empty_project.add_developer(user)
- stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
-
- expect(project_access.can_push_to_branch?('master')).to be_truthy
+ context 'when the user is a developer' do
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ empty_project.add_developer(user)
+ end
+
+ where(:default_branch_protection_level, :result) do
+ Gitlab::Access::PROTECTION_NONE | true
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false
+ Gitlab::Access::PROTECTION_FULL | false
+ end
+
+ with_them do
+ it do
+ expect(empty_project.namespace).to receive(:default_branch_protection).and_return(default_branch_protection_level).at_least(:once)
+
+ expect(project_access.can_push_to_branch?('master')).to eq(result)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/utils/json_size_estimator_spec.rb b/spec/lib/gitlab/utils/json_size_estimator_spec.rb
new file mode 100644
index 00000000000..ae24e25558a
--- /dev/null
+++ b/spec/lib/gitlab/utils/json_size_estimator_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Utils::JsonSizeEstimator do
+ RSpec::Matchers.define :match_json_bytesize_of do |expected|
+ match do |actual|
+ actual == expected.to_json.bytesize
+ end
+ end
+
+ def estimate(object)
+ described_class.estimate(object)
+ end
+
+ [
+ [],
+ [[[[]]]],
+ [1, "str", 3.14, ["str", { a: -1 }]],
+ {},
+ { a: {} },
+ { a: { b: { c: [1, 2, 3], e: Time.now, f: nil } } },
+ { 100 => 500 },
+ { '狸' => '狸' },
+ nil
+ ].each do |example|
+ it { expect(estimate(example)).to match_json_bytesize_of(example) }
+ end
+
+ it 'calls #to_s on unknown object' do
+ klass = Class.new do
+ def to_s
+ 'hello'
+ end
+ end
+
+ expect(estimate(klass.new)).to match_json_bytesize_of(klass.new.to_s) # "hello"
+ end
+end
diff --git a/spec/lib/gitlab/utils/log_limited_array_spec.rb b/spec/lib/gitlab/utils/log_limited_array_spec.rb
index 2729b2c7b6f..a236ab37614 100644
--- a/spec/lib/gitlab/utils/log_limited_array_spec.rb
+++ b/spec/lib/gitlab/utils/log_limited_array_spec.rb
@@ -18,12 +18,26 @@ describe Gitlab::Utils::LogLimitedArray do
end
context 'when the array exceeds the limit' do
- it 'replaces arguments after the limit with an ellipsis string' do
+ let(:long_array) do
half_limit = described_class::MAXIMUM_ARRAY_LENGTH / 2
- long_array = ['a' * half_limit, 'b' * half_limit, 'c']
- expect(described_class.log_limited_array(long_array))
- .to eq(long_array.take(1) + ['...'])
+ ['a' * half_limit, 'b' * half_limit, 'c']
+ end
+
+ context 'when no sentinel value is passed' do
+ it 'replaces arguments after the limit with an ellipsis string' do
+ expect(described_class.log_limited_array(long_array))
+ .to eq(long_array.take(1) + ['...'])
+ end
+ end
+
+ context 'when a sentinel value is passed' do
+ it 'replaces arguments after the limit with the sentinel' do
+ sentinel = { truncated: true }
+
+ expect(described_class.log_limited_array(long_array, sentinel: sentinel))
+ .to eq(long_array.take(1) + [sentinel])
+ end
end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 6841e7719dc..d3780d22241 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -31,6 +31,14 @@ describe Gitlab::Utils do
it 'does nothing for a safe string' do
expect(check_path_traversal!('./foo')).to eq('./foo')
end
+
+ it 'does nothing if an absolute path is allowed' do
+ expect(check_path_traversal!('/etc/folder/path', allowed_absolute: true)). to eq('/etc/folder/path')
+ end
+
+ it 'raises exception if an absolute path is not allowed' do
+ expect { check_path_traversal!('/etc/folder/path') }.to raise_error(/Invalid path/)
+ end
end
describe '.slugify' do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 89381057f6b..921ed568b71 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::Workhorse do
- set(:project) { create(:project, :repository) }
+ let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
def decode_workhorse_header(array)
diff --git a/spec/lib/gitlab/x509/commit_spec.rb b/spec/lib/gitlab/x509/commit_spec.rb
index c31e9e4b8e6..07d7eba6b9a 100644
--- a/spec/lib/gitlab/x509/commit_spec.rb
+++ b/spec/lib/gitlab/x509/commit_spec.rb
@@ -111,6 +111,22 @@ describe Gitlab::X509::Commit do
expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes)
expect(signature.persisted?).to be_truthy
end
+
+ context 'revoked certificate' do
+ let(:x509_issuer) { create(:x509_issuer, user1_issuer_attributes) }
+ let!(:x509_certificate) { create(:x509_certificate, user1_certificate_attributes.merge(x509_issuer_id: x509_issuer.id, certificate_status: :revoked)) }
+
+ it 'returns an unverified signature' do
+ expect(signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ verification_status: 'unverified'
+ )
+ expect(signature.x509_certificate).to have_attributes(user1_certificate_attributes)
+ expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes)
+ expect(signature.persisted?).to be_truthy
+ end
+ end
end
context 'without trusted certificate within store' do