summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorAchilleas Pipinellis <axilleas@axilleas.me>2016-03-18 14:31:33 +0200
committerAchilleas Pipinellis <axilleas@axilleas.me>2016-03-18 14:31:33 +0200
commit4d3e8ceea562683a8ee3a87a45ece6c476558446 (patch)
treea83c195a72ac7e32edb4888e733792302c13505f /spec
parent11dda8db29a4843026464c0a61f65ada20646e3b (diff)
parentdadd28e317ace1e3d3a2a02926eb352832b97f08 (diff)
downloadgitlab-ce-4d3e8ceea562683a8ee3a87a45ece6c476558446.tar.gz
Merge branch 'master' into docs_select_version_to_install
Diffstat (limited to 'spec')
-rw-r--r--spec/benchmarks/finders/issues_finder_spec.rb55
-rw-r--r--spec/benchmarks/finders/trending_projects_finder_spec.rb14
-rw-r--r--spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb41
-rw-r--r--spec/benchmarks/models/milestone_spec.rb17
-rw-r--r--spec/benchmarks/models/project_spec.rb50
-rw-r--r--spec/benchmarks/models/project_team_spec.rb23
-rw-r--r--spec/benchmarks/models/user_spec.rb78
-rw-r--r--spec/benchmarks/services/projects/create_service_spec.rb28
-rw-r--r--spec/config/mail_room_spec.rb56
-rw-r--r--spec/controllers/abuse_reports_controller_spec.rb80
-rw-r--r--spec/controllers/admin/identities_controller_spec.rb26
-rw-r--r--spec/controllers/admin/spam_logs_controller_spec.rb37
-rw-r--r--spec/controllers/admin/users_controller_spec.rb35
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb2
-rw-r--r--spec/controllers/blame_controller_spec.rb43
-rw-r--r--spec/controllers/branches_controller_spec.rb104
-rw-r--r--spec/controllers/ci/projects_controller_spec.rb53
-rw-r--r--spec/controllers/commit_controller_spec.rb51
-rw-r--r--spec/controllers/groups_controller_spec.rb23
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb (renamed from spec/controllers/profile_keys_controller_spec.rb)0
-rw-r--r--spec/controllers/projects/blame_controller_spec.rb29
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb134
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb37
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb (renamed from spec/controllers/commits_controller_spec.rb)0
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb8
-rw-r--r--spec/controllers/projects/find_file_controller_spec.rb66
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb72
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb121
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb162
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb62
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb42
-rw-r--r--spec/controllers/projects_controller_spec.rb21
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb26
-rw-r--r--spec/controllers/users_controller_spec.rb18
-rw-r--r--spec/factories.rb215
-rw-r--r--spec/factories/abuse_reports.rb2
-rw-r--r--spec/factories/appearances.rb8
-rw-r--r--spec/factories/broadcast_messages.rb20
-rw-r--r--spec/factories/ci/builds.rb76
-rw-r--r--spec/factories/ci/commits.rb1
-rw-r--r--spec/factories/ci/runner_projects.rb2
-rw-r--r--spec/factories/ci/runners.rb12
-rw-r--r--spec/factories/ci/trigger_requests.rb4
-rw-r--r--spec/factories/ci/triggers.rb2
-rw-r--r--spec/factories/ci/variables.rb20
-rw-r--r--spec/factories/commit_statuses.rb8
-rw-r--r--spec/factories/deploy_keys_projects.rb6
-rw-r--r--spec/factories/emails.rb6
-rw-r--r--spec/factories/events.rb10
-rw-r--r--spec/factories/forked_project_links.rb2
-rw-r--r--spec/factories/groups.rb7
-rw-r--r--spec/factories/identities.rb6
-rw-r--r--spec/factories/issues.rb22
-rw-r--r--spec/factories/keys.rb24
-rw-r--r--spec/factories/label_links.rb2
-rw-r--r--spec/factories/labels.rb4
-rw-r--r--spec/factories/lfs_objects.rb2
-rw-r--r--spec/factories/lfs_objects_projects.rb2
-rw-r--r--spec/factories/merge_requests.rb62
-rw-r--r--spec/factories/milestones.rb12
-rw-r--r--spec/factories/namespaces.rb7
-rw-r--r--spec/factories/notes.rb16
-rw-r--r--spec/factories/personal_snippets.rb4
-rw-r--r--spec/factories/project_group_links.rb6
-rw-r--r--spec/factories/project_hooks.rb5
-rw-r--r--spec/factories/project_members.rb27
-rw-r--r--spec/factories/project_snippets.rb5
-rw-r--r--spec/factories/projects.rb7
-rw-r--r--spec/factories/protected_branches.rb6
-rw-r--r--spec/factories/releases.rb2
-rw-r--r--spec/factories/sent_notifications.rb8
-rw-r--r--spec/factories/service_hooks.rb6
-rw-r--r--spec/factories/services.rb5
-rw-r--r--spec/factories/snippets.rb28
-rw-r--r--spec/factories/spam_logs.rb9
-rw-r--r--spec/factories/system_hooks.rb5
-rw-r--r--spec/factories/todos.rb34
-rw-r--r--spec/factories/users.rb43
-rw-r--r--spec/features/admin/admin_builds_spec.rb119
-rw-r--r--spec/features/builds_spec.rb33
-rw-r--r--spec/features/ci_lint_spec.rb8
-rw-r--r--spec/features/commits_spec.rb169
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb11
-rw-r--r--spec/features/issues/new_branch_button_spec.rb49
-rw-r--r--spec/features/issues_spec.rb12
-rw-r--r--spec/features/login_spec.rb36
-rw-r--r--spec/features/markdown_spec.rb64
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb5
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb8
-rw-r--r--spec/features/projects_spec.rb25
-rw-r--r--spec/features/runners_spec.rb10
-rw-r--r--spec/features/security/project/internal_access_spec.rb57
-rw-r--r--spec/features/security/project/private_access_spec.rb52
-rw-r--r--spec/features/security/project/public_access_spec.rb95
-rw-r--r--spec/finders/groups_finder_spec.rb48
-rw-r--r--spec/finders/joined_groups_finder_spec.rb49
-rw-r--r--spec/finders/projects_finder_spec.rb34
-rw-r--r--spec/finders/snippets_finder_spec.rb25
-rw-r--r--spec/fixtures/ci_build_artifacts.zipbin0 -> 106365 bytes
-rw-r--r--spec/fixtures/ci_build_artifacts_metadata.gzbin0 -> 461 bytes
-rw-r--r--spec/fixtures/logo_sample.svg27
-rw-r--r--spec/fixtures/mail_room_disabled.yml11
-rw-r--r--spec/fixtures/mail_room_enabled.yml11
-rw-r--r--spec/fixtures/markdown.md.erb18
-rw-r--r--spec/fixtures/parallel_diff_result.yml324
-rw-r--r--spec/helpers/application_helper_spec.rb16
-rw-r--r--spec/helpers/blob_helper_spec.rb63
-rw-r--r--spec/helpers/broadcast_messages_helper_spec.rb62
-rw-r--r--spec/helpers/diff_helper_spec.rb122
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb7
-rw-r--r--spec/helpers/labels_helper_spec.rb38
-rw-r--r--spec/helpers/page_layout_helper_spec.rb64
-rw-r--r--spec/helpers/search_helper_spec.rb6
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb6
-rw-r--r--spec/initializers/settings_spec.rb44
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js.coffee11
-rw-r--r--spec/javascripts/fixtures/behaviors/quick_submit.html.haml6
-rw-r--r--spec/javascripts/fixtures/project_title.html.haml7
-rw-r--r--spec/javascripts/fixtures/projects.json1
-rw-r--r--spec/javascripts/fixtures/zen_mode.html.haml9
-rw-r--r--spec/javascripts/issue_spec.js.coffee34
-rw-r--r--spec/javascripts/project_title_spec.js.coffee46
-rw-r--r--spec/javascripts/zen_mode_spec.js.coffee26
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/gollum_tags_filter_spec.rb104
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb27
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb75
-rw-r--r--spec/lib/banzai/filter/redactor_filter_spec.rb72
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb8
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb63
-rw-r--r--spec/lib/banzai/filter/task_list_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb53
-rw-r--r--spec/lib/banzai/filter_array_spec.rb39
-rw-r--r--spec/lib/banzai/pipeline/description_pipeline_spec.rb37
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb53
-rw-r--r--spec/lib/banzai/querying_spec.rb13
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb148
-rw-r--r--spec/lib/ci/status_spec.rb94
-rw-r--r--spec/lib/gitlab/akismet_helper_spec.rb35
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb16
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb88
-rw-r--r--spec/lib/gitlab/blame_spec.rb24
-rw-r--r--spec/lib/gitlab/build_data_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb173
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb97
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb12
-rw-r--r--spec/lib/gitlab/database_spec.rb52
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb16
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb77
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb29
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb40
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb22
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb9
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb2
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb11
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb21
-rw-r--r--spec/lib/gitlab/github_import/comment_formatter_spec.rb80
-rw-r--r--spec/lib/gitlab/github_import/issue_formatter_spec.rb139
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb185
-rw-r--r--spec/lib/gitlab/github_import/wiki_formatter_spec.rb22
-rw-r--r--spec/lib/gitlab/highlight_spec.rb21
-rw-r--r--spec/lib/gitlab/inline_diff_spec.rb39
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb35
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb28
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb16
-rw-r--r--spec/lib/gitlab/metrics/metric_spec.rb3
-rw-r--r--spec/lib/gitlab/metrics/obfuscated_sql_spec.rb93
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/sampler_spec.rb60
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb17
-rw-r--r--spec/lib/gitlab/metrics/subscribers/action_view_spec.rb12
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb33
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb67
-rw-r--r--spec/lib/gitlab/metrics_spec.rb27
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb30
-rw-r--r--spec/lib/gitlab/note_data_builder_spec.rb42
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb26
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb69
-rw-r--r--spec/lib/gitlab/push_data_builder_spec.rb27
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/regex_spec.rb8
-rw-r--r--spec/lib/gitlab/saml/user_spec.rb271
-rw-r--r--spec/lib/gitlab/search_results_spec.rb144
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb25
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb18
-rw-r--r--spec/mailers/abuse_report_mailer_spec.rb38
-rw-r--r--spec/mailers/emails/builds_spec.rb65
-rw-r--r--spec/mailers/emails/profile_spec.rb111
-rw-r--r--spec/mailers/notify_spec.rb347
-rw-r--r--spec/mailers/shared/notify.rb121
-rw-r--r--spec/models/abuse_report_spec.rb35
-rw-r--r--spec/models/appearance_spec.rb10
-rw-r--r--spec/models/application_setting_spec.rb79
-rw-r--r--spec/models/blob_spec.rb81
-rw-r--r--spec/models/broadcast_message_spec.rb72
-rw-r--r--spec/models/build_spec.rb216
-rw-r--r--spec/models/ci/build_spec.rb22
-rw-r--r--spec/models/ci/commit_spec.rb75
-rw-r--r--spec/models/ci/runner_project_spec.rb11
-rw-r--r--spec/models/ci/runner_spec.rb42
-rw-r--r--spec/models/ci/trigger_spec.rb13
-rw-r--r--spec/models/ci/variable_spec.rb3
-rw-r--r--spec/models/commit_spec.rb47
-rw-r--r--spec/models/commit_status_spec.rb1
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb12
-rw-r--r--spec/models/concerns/issuable_spec.rb116
-rw-r--r--spec/models/concerns/mentionable_spec.rb5
-rw-r--r--spec/models/concerns/milestoneish_spec.rb104
-rw-r--r--spec/models/concerns/subscribable_spec.rb57
-rw-r--r--spec/models/email_spec.rb22
-rw-r--r--spec/models/event_spec.rb37
-rw-r--r--spec/models/external_issue_spec.rb15
-rw-r--r--spec/models/external_wiki_service_spec.rb1
-rw-r--r--spec/models/generic_commit_status_spec.rb1
-rw-r--r--spec/models/group_spec.rb40
-rw-r--r--spec/models/hooks/project_hook_spec.rb4
-rw-r--r--spec/models/hooks/service_hook_spec.rb2
-rw-r--r--spec/models/hooks/system_hook_spec.rb6
-rw-r--r--spec/models/hooks/web_hook_spec.rb34
-rw-r--r--spec/models/identity_spec.rb38
-rw-r--r--spec/models/issue_spec.rb42
-rw-r--r--spec/models/label_spec.rb30
-rw-r--r--spec/models/member_spec.rb6
-rw-r--r--spec/models/merge_request_spec.rb194
-rw-r--r--spec/models/milestone_spec.rb65
-rw-r--r--spec/models/namespace_spec.rb30
-rw-r--r--spec/models/note_spec.rb137
-rw-r--r--spec/models/project_group_link_spec.rb17
-rw-r--r--spec/models/project_services/asana_service_spec.rb77
-rw-r--r--spec/models/project_services/builds_email_service_spec.rb23
-rw-r--r--spec/models/project_snippet_spec.rb1
-rw-r--r--spec/models/project_spec.rb164
-rw-r--r--spec/models/project_team_spec.rb70
-rw-r--r--spec/models/project_wiki_spec.rb7
-rw-r--r--spec/models/repository_spec.rb597
-rw-r--r--spec/models/service_spec.rb1
-rw-r--r--spec/models/snippet_spec.rb45
-rw-r--r--spec/models/spam_log_spec.rb25
-rw-r--r--spec/models/todo_spec.rb69
-rw-r--r--spec/models/tree_spec.rb64
-rw-r--r--spec/models/user_spec.rb260
-rw-r--r--spec/models/wiki_page_spec.rb32
-rw-r--r--spec/requests/api/branches_spec.rb4
-rw-r--r--spec/requests/api/builds_spec.rb244
-rw-r--r--spec/requests/api/commit_status_spec.rb188
-rw-r--r--spec/requests/api/commits_spec.rb4
-rw-r--r--spec/requests/api/fork_spec.rb2
-rw-r--r--spec/requests/api/internal_spec.rb12
-rw-r--r--spec/requests/api/issues_spec.rb149
-rw-r--r--spec/requests/api/merge_requests_spec.rb100
-rw-r--r--spec/requests/api/notes_spec.rb56
-rw-r--r--spec/requests/api/project_members_spec.rb4
-rw-r--r--spec/requests/api/project_snippets_spec.rb18
-rw-r--r--spec/requests/api/projects_spec.rb86
-rw-r--r--spec/requests/api/repositories_spec.rb17
-rw-r--r--spec/requests/api/runners_spec.rb464
-rw-r--r--spec/requests/api/tags_spec.rb25
-rw-r--r--spec/requests/api/triggers_spec.rb139
-rw-r--r--spec/requests/api/users_spec.rb52
-rw-r--r--spec/requests/api/variables_spec.rb182
-rw-r--r--spec/requests/ci/api/builds_spec.rb191
-rw-r--r--spec/requests/ci/api/runners_spec.rb14
-rw-r--r--spec/routing/project_routing_spec.rb25
-rw-r--r--spec/routing/routing_spec.rb5
-rw-r--r--spec/services/archive_repository_service_spec.rb25
-rw-r--r--spec/services/ci/create_builds_service_spec.rb28
-rw-r--r--spec/services/delete_tag_service_spec.rb26
-rw-r--r--spec/services/delete_user_service_spec.rb58
-rw-r--r--spec/services/git_push_service_spec.rb173
-rw-r--r--spec/services/git_tag_push_service_spec.rb4
-rw-r--r--spec/services/issues/close_service_spec.rb6
-rw-r--r--spec/services/issues/create_service_spec.rb23
-rw-r--r--spec/services/issues/update_service_spec.rb114
-rw-r--r--spec/services/merge_requests/close_service_spec.rb5
-rw-r--r--spec/services/merge_requests/create_service_spec.rb42
-rw-r--r--spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb84
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb2
-rw-r--r--spec/services/merge_requests/update_service_spec.rb124
-rw-r--r--spec/services/notes/create_service_spec.rb4
-rw-r--r--spec/services/notes/post_process_service_spec.rb27
-rw-r--r--spec/services/notes/update_service_spec.rb45
-rw-r--r--spec/services/notification_service_spec.rb161
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb79
-rw-r--r--spec/services/projects/create_service_spec.rb1
-rw-r--r--spec/services/projects/download_service_spec.rb24
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb48
-rw-r--r--spec/services/projects/import_service_spec.rb106
-rw-r--r--spec/services/projects/update_service_spec.rb4
-rw-r--r--spec/services/repair_ldap_blocked_user_service_spec.rb23
-rw-r--r--spec/services/system_hooks_service_spec.rb47
-rw-r--r--spec/services/system_note_service_spec.rb33
-rw-r--r--spec/services/todo_service_spec.rb274
-rw-r--r--spec/spec_helper.rb13
-rw-r--r--spec/support/api/pagination_shared_examples.rb20
-rw-r--r--spec/support/capybara.rb10
-rw-r--r--spec/support/email_format_shared_examples.rb44
-rw-r--r--spec/support/email_helpers.rb13
-rw-r--r--spec/support/filter_spec_helper.rb4
-rw-r--r--spec/support/gitlab_stubs/gitlab_ci.yml8
-rw-r--r--spec/support/markdown_feature.rb12
-rw-r--r--spec/support/matchers/access_matchers.rb2
-rw-r--r--spec/support/matchers/benchmark_matchers.rb61
-rw-r--r--spec/support/matchers/markdown_matchers.rb27
-rw-r--r--spec/support/mentionable_shared_examples.rb2
-rw-r--r--spec/support/project_hook_data_shared_example.rb27
-rw-r--r--spec/support/test_env.rb17
-rw-r--r--spec/support/wait_for_ajax.rb2
-rw-r--r--spec/support/workhorse_helpers.rb16
-rw-r--r--spec/views/devise/shared/_signin_box.html.haml_spec.rb37
-rw-r--r--spec/workers/delete_user_worker_spec.rb20
-rw-r--r--spec/workers/post_receive_spec.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb12
-rw-r--r--spec/workers/repository_import_worker_spec.rb19
314 files changed, 12323 insertions, 2769 deletions
diff --git a/spec/benchmarks/finders/issues_finder_spec.rb b/spec/benchmarks/finders/issues_finder_spec.rb
deleted file mode 100644
index b57a33004a4..00000000000
--- a/spec/benchmarks/finders/issues_finder_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-require 'spec_helper'
-
-describe IssuesFinder, benchmark: true do
- describe '#execute' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :public) }
-
- let(:label1) { create(:label, project: project, title: 'A') }
- let(:label2) { create(:label, project: project, title: 'B') }
-
- before do
- 10.times do |n|
- issue = create(:issue, author: user, project: project)
-
- if n > 4
- create(:label_link, label: label1, target: issue)
- create(:label_link, label: label2, target: issue)
- end
- end
- end
-
- describe 'retrieving issues without labels' do
- let(:finder) do
- IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
- state: 'opened')
- end
-
- benchmark_subject { finder.execute }
-
- it { is_expected.to iterate_per_second(2000) }
- end
-
- describe 'retrieving issues with labels' do
- let(:finder) do
- IssuesFinder.new(user, scope: 'all', label_name: label1.title,
- state: 'opened')
- end
-
- benchmark_subject { finder.execute }
-
- it { is_expected.to iterate_per_second(1000) }
- end
-
- describe 'retrieving issues for a single project' do
- let(:finder) do
- IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
- state: 'opened', project_id: project.id)
- end
-
- benchmark_subject { finder.execute }
-
- it { is_expected.to iterate_per_second(2000) }
- end
- end
-end
diff --git a/spec/benchmarks/finders/trending_projects_finder_spec.rb b/spec/benchmarks/finders/trending_projects_finder_spec.rb
deleted file mode 100644
index 551ce21840d..00000000000
--- a/spec/benchmarks/finders/trending_projects_finder_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'spec_helper'
-
-describe TrendingProjectsFinder, benchmark: true do
- describe '#execute' do
- let(:finder) { described_class.new }
- let(:user) { create(:user) }
-
- # to_a is used to force actually running the query (instead of just building
- # it).
- benchmark_subject { finder.execute(user).non_archived.to_a }
-
- it { is_expected.to iterate_per_second(500) }
- end
-end
diff --git a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
deleted file mode 100644
index 3855763b200..00000000000
--- a/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'spec_helper'
-
-describe Banzai::Filter::ReferenceFilter, benchmark: true do
- let(:input) do
- html = <<-EOF
-<p>Hello @alice and @bob, how are you doing today?</p>
-<p>This is simple @dummy text to see how the @ReferenceFilter class performs
-when @processing HTML.</p>
- EOF
-
- Nokogiri::HTML.fragment(html)
- end
-
- let(:project) { create(:empty_project) }
-
- let(:filter) { described_class.new(input, project: project) }
-
- describe '#replace_text_nodes_matching' do
- let(:iterations) { 6000 }
-
- describe 'with identical input and output HTML' do
- benchmark_subject do
- filter.replace_text_nodes_matching(User.reference_pattern) do |content|
- content
- end
- end
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'with different input and output HTML' do
- benchmark_subject do
- filter.replace_text_nodes_matching(User.reference_pattern) do |content|
- '@eve'
- end
- end
-
- it { is_expected.to iterate_per_second(iterations) }
- end
- end
-end
diff --git a/spec/benchmarks/models/milestone_spec.rb b/spec/benchmarks/models/milestone_spec.rb
deleted file mode 100644
index a94afc4c40d..00000000000
--- a/spec/benchmarks/models/milestone_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Milestone, benchmark: true do
- describe '#sort_issues' do
- let(:milestone) { create(:milestone) }
-
- let(:issue1) { create(:issue, milestone: milestone) }
- let(:issue2) { create(:issue, milestone: milestone) }
- let(:issue3) { create(:issue, milestone: milestone) }
-
- let(:issue_ids) { [issue3.id, issue2.id, issue1.id] }
-
- benchmark_subject { milestone.sort_issues(issue_ids) }
-
- it { is_expected.to iterate_per_second(500) }
- end
-end
diff --git a/spec/benchmarks/models/project_spec.rb b/spec/benchmarks/models/project_spec.rb
deleted file mode 100644
index cee0949edc5..00000000000
--- a/spec/benchmarks/models/project_spec.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require 'spec_helper'
-
-describe Project, benchmark: true do
- describe '.trending' do
- let(:group) { create(:group) }
- let(:project1) { create(:empty_project, :public, group: group) }
- let(:project2) { create(:empty_project, :public, group: group) }
-
- let(:iterations) { 500 }
-
- before do
- 2.times do
- create(:note_on_commit, project: project1)
- end
-
- create(:note_on_commit, project: project2)
- end
-
- describe 'without an explicit start date' do
- benchmark_subject { described_class.trending.to_a }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'with an explicit start date' do
- let(:date) { 1.month.ago }
-
- benchmark_subject { described_class.trending(date).to_a }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
- end
-
- describe '.find_with_namespace' do
- let(:group) { create(:group, name: 'sisinmaru') }
- let(:project) { create(:project, name: 'maru', namespace: group) }
-
- describe 'using a capitalized namespace' do
- benchmark_subject { described_class.find_with_namespace('sisinmaru/MARU') }
-
- it { is_expected.to iterate_per_second(600) }
- end
-
- describe 'using a lowercased namespace' do
- benchmark_subject { described_class.find_with_namespace('sisinmaru/maru') }
-
- it { is_expected.to iterate_per_second(600) }
- end
- end
-end
diff --git a/spec/benchmarks/models/project_team_spec.rb b/spec/benchmarks/models/project_team_spec.rb
deleted file mode 100644
index 8b039ef7317..00000000000
--- a/spec/benchmarks/models/project_team_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-describe ProjectTeam, benchmark: true do
- describe '#max_member_access' do
- let(:group) { create(:group) }
- let(:project) { create(:empty_project, group: group) }
- let(:user) { create(:user) }
-
- before do
- project.team << [user, :master]
-
- 5.times do
- project.team << [create(:user), :reporter]
-
- project.group.add_user(create(:user), :reporter)
- end
- end
-
- benchmark_subject { project.team.max_member_access(user.id) }
-
- it { is_expected.to iterate_per_second(35000) }
- end
-end
diff --git a/spec/benchmarks/models/user_spec.rb b/spec/benchmarks/models/user_spec.rb
deleted file mode 100644
index 1be7a8d3ed9..00000000000
--- a/spec/benchmarks/models/user_spec.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-require 'spec_helper'
-
-describe User, benchmark: true do
- describe '.all' do
- before do
- 10.times { create(:user) }
- end
-
- benchmark_subject { User.all.to_a }
-
- it { is_expected.to iterate_per_second(500) }
- end
-
- describe '.by_login' do
- before do
- %w{Alice Bob Eve}.each do |name|
- create(:user,
- email: "#{name}@gitlab.com",
- username: name,
- name: name)
- end
- end
-
- # The iteration count is based on the query taking little over 1 ms when
- # using PostgreSQL.
- let(:iterations) { 900 }
-
- describe 'using a capitalized username' do
- benchmark_subject { User.by_login('Alice') }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'using a lowercase username' do
- benchmark_subject { User.by_login('alice') }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'using a capitalized Email address' do
- benchmark_subject { User.by_login('Alice@gitlab.com') }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
-
- describe 'using a lowercase Email address' do
- benchmark_subject { User.by_login('alice@gitlab.com') }
-
- it { is_expected.to iterate_per_second(iterations) }
- end
- end
-
- describe '.find_by_any_email' do
- let(:user) { create(:user) }
-
- describe 'using a user with only a single Email address' do
- let(:email) { user.email }
-
- benchmark_subject { User.find_by_any_email(email) }
-
- it { is_expected.to iterate_per_second(1000) }
- end
-
- describe 'using a user with multiple Email addresses' do
- let(:email) { user.emails.first.email }
-
- benchmark_subject { User.find_by_any_email(email) }
-
- before do
- 10.times do
- user.emails.create(email: FFaker::Internet.email)
- end
- end
-
- it { is_expected.to iterate_per_second(1000) }
- end
- end
-end
diff --git a/spec/benchmarks/services/projects/create_service_spec.rb b/spec/benchmarks/services/projects/create_service_spec.rb
deleted file mode 100644
index 25ed48c34fd..00000000000
--- a/spec/benchmarks/services/projects/create_service_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe Projects::CreateService, benchmark: true do
- describe '#execute' do
- let(:user) { create(:user, :admin) }
-
- let(:group) do
- group = create(:group)
-
- create(:group_member, group: group, user: user)
-
- group
- end
-
- benchmark_subject do
- name = SecureRandom.hex
- service = described_class.new(user,
- name: name,
- path: name,
- namespace_id: group.id,
- visibility_level: Gitlab::VisibilityLevel::PUBLIC)
-
- service.execute
- end
-
- it { is_expected.to iterate_per_second(0.5) }
- end
-end
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
new file mode 100644
index 00000000000..462afb24f08
--- /dev/null
+++ b/spec/config/mail_room_spec.rb
@@ -0,0 +1,56 @@
+require "spec_helper"
+
+describe "mail_room.yml" do
+ let(:config_path) { "config/mail_room.yml" }
+ let(:configuration) { YAML.load(ERB.new(File.read(config_path)).result) }
+
+ context "when incoming email is disabled" do
+ before do
+ ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = Rails.root.join("spec/fixtures/mail_room_disabled.yml").to_s
+ end
+
+ after do
+ ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = nil
+ end
+
+ it "contains no configuration" do
+ expect(configuration[:mailboxes]).to be_nil
+ end
+ end
+
+ context "when incoming email is enabled" do
+ before do
+ ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = Rails.root.join("spec/fixtures/mail_room_enabled.yml").to_s
+ end
+
+ after do
+ ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = nil
+ end
+
+ it "contains the intended configuration" do
+ expect(configuration[:mailboxes].length).to eq(1)
+
+ mailbox = configuration[:mailboxes].first
+
+ expect(mailbox[:host]).to eq("imap.gmail.com")
+ expect(mailbox[:port]).to eq(993)
+ expect(mailbox[:ssl]).to eq(true)
+ expect(mailbox[:start_tls]).to eq(false)
+ expect(mailbox[:email]).to eq("gitlab-incoming@gmail.com")
+ expect(mailbox[:password]).to eq("[REDACTED]")
+ expect(mailbox[:name]).to eq("inbox")
+
+ redis_config_file = Rails.root.join('config', 'resque.yml')
+
+ redis_url =
+ if File.exists?(redis_config_file)
+ YAML.load_file(redis_config_file)[Rails.env]
+ else
+ "redis://localhost:6379"
+ end
+
+ expect(mailbox[:delivery_options][:redis_url]).to eq(redis_url)
+ expect(mailbox[:arbitration_options][:redis_url]).to eq(redis_url)
+ end
+ end
+end
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
index 15824a1c67f..80a418feb3e 100644
--- a/spec/controllers/abuse_reports_controller_spec.rb
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -1,76 +1,46 @@
require 'spec_helper'
describe AbuseReportsController do
- let(:reporter) { create(:user) }
- let(:user) { create(:user) }
- let(:message) { "This user is a spammer" }
+ let(:reporter) { create(:user) }
+ let(:user) { create(:user) }
+ let(:attrs) do
+ attributes_for(:abuse_report) do |hash|
+ hash[:user_id] = user.id
+ end
+ end
before do
sign_in(reporter)
end
- describe "POST create" do
- context "with admin notification email set" do
- let(:admin_email) { "admin@example.com"}
-
- before(:each) do
- stub_application_setting(admin_notification_email: admin_email)
+ describe 'POST create' do
+ context 'with valid attributes' do
+ it 'saves the abuse report' do
+ expect do
+ post :create, abuse_report: attrs
+ end.to change { AbuseReport.count }.by(1)
end
- it "sends a notification email" do
- perform_enqueued_jobs do
- post :create,
- abuse_report: {
- user_id: user.id,
- message: message
- }
-
- email = ActionMailer::Base.deliveries.last
+ it 'calls notify' do
+ expect_any_instance_of(AbuseReport).to receive(:notify)
- expect(email.to).to eq([admin_email])
- expect(email.subject).to include(user.username)
- expect(email.text_part.body).to include(message)
- end
+ post :create, abuse_report: attrs
end
- it "saves the abuse report" do
- perform_enqueued_jobs do
- expect do
- post :create,
- abuse_report: {
- user_id: user.id,
- message: message
- }
- end.to change { AbuseReport.count }.by(1)
- end
- end
- end
+ it 'redirects back to the reported user' do
+ post :create, abuse_report: attrs
- context "without admin notification email set" do
- before(:each) do
- stub_application_setting(admin_notification_email: nil)
+ expect(response).to redirect_to user
end
+ end
- it "does not send a notification email" do
- expect do
- post :create,
- abuse_report: {
- user_id: user.id,
- message: message
- }
- end.not_to change { ActionMailer::Base.deliveries.count }
- end
+ context 'with invalid attributes' do
+ it 'renders new' do
+ attrs.delete(:user_id)
+ post :create, abuse_report: attrs
- it "saves the abuse report" do
- expect do
- post :create,
- abuse_report: {
- user_id: user.id,
- message: message
- }
- end.to change { AbuseReport.count }.by(1)
+ expect(response).to render_template(:new)
end
end
end
-
end
diff --git a/spec/controllers/admin/identities_controller_spec.rb b/spec/controllers/admin/identities_controller_spec.rb
new file mode 100644
index 00000000000..c131d22a30a
--- /dev/null
+++ b/spec/controllers/admin/identities_controller_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Admin::IdentitiesController do
+ let(:admin) { create(:admin) }
+ before { sign_in(admin) }
+
+ describe 'UPDATE identity' do
+ let(:user) { create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=myuser,ou=people,dc=example,dc=com') }
+
+ it 'repairs ldap blocks' do
+ expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute)
+
+ put :update, user_id: user.username, id: user.ldap_identity.id, identity: { provider: 'twitter' }
+ end
+ end
+
+ describe 'DELETE identity' do
+ let(:user) { create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=myuser,ou=people,dc=example,dc=com') }
+
+ it 'repairs ldap blocks' do
+ expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute)
+
+ delete :destroy, user_id: user.username, id: user.ldap_identity.id
+ end
+ end
+end
diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb
new file mode 100644
index 00000000000..b51b303a714
--- /dev/null
+++ b/spec/controllers/admin/spam_logs_controller_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Admin::SpamLogsController do
+ let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
+ let!(:first_spam) { create(:spam_log, user: user) }
+ let!(:second_spam) { create(:spam_log, user: user) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe '#index' do
+ it 'lists all spam logs' do
+ get :index
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ describe '#destroy' do
+ it 'removes only the spam log when removing log' do
+ expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1)
+ expect(User.find(user.id)).to be_truthy
+ expect(response.status).to eq(200)
+ end
+
+ it 'removes user and his spam logs when removing the user' do
+ delete :destroy, id: first_spam.id, remove_user: true
+
+ expect(flash[:notice]).to eq "User #{user.username} was successfully removed."
+ expect(response.status).to eq(302)
+ expect(SpamLog.count).to eq(0)
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 8b7af4d3a0a..5b1f65d7aff 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -34,17 +34,34 @@ describe Admin::UsersController do
end
describe 'PUT unblock/:id' do
- let(:user) { create(:user) }
-
- before do
- user.block
+ context 'ldap blocked users' do
+ let(:user) { create(:omniauth_user, provider: 'ldapmain') }
+
+ before do
+ user.ldap_block
+ end
+
+ it 'will not unblock user' do
+ put :unblock, id: user.username
+ user.reload
+ expect(user.blocked?).to be_truthy
+ expect(flash[:alert]).to eq 'This user cannot be unlocked manually from GitLab'
+ end
end
- it 'unblocks user' do
- put :unblock, id: user.username
- user.reload
- expect(user.blocked?).to be_falsey
- expect(flash[:notice]).to eq 'Successfully unblocked'
+ context 'manually blocked users' do
+ let(:user) { create(:user) }
+
+ before do
+ user.block
+ end
+
+ it 'unblocks user' do
+ put :unblock, id: user.username
+ user.reload
+ expect(user.blocked?).to be_falsey
+ expect(flash[:notice]).to eq 'Successfully unblocked'
+ end
end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 85379a8e984..410b993fdfb 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -21,7 +21,7 @@ describe AutocompleteController do
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 }
- it { expect(body.first["username"]).to eq user.username }
+ it { expect(body.map { |u| u["username"] }).to include(user.username) }
end
describe 'GET #users with unknown project' do
diff --git a/spec/controllers/blame_controller_spec.rb b/spec/controllers/blame_controller_spec.rb
deleted file mode 100644
index 3ad4d5fc0a8..00000000000
--- a/spec/controllers/blame_controller_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-describe Projects::BlameController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
-
- before do
- sign_in(user)
-
- project.team << [user, :master]
- controller.instance_variable_set(:@project, project)
- end
-
- describe "GET show" do
- render_views
-
- before do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: id)
- end
-
- context "valid file" do
- let(:id) { 'master/files/ruby/popen.rb' }
- it { is_expected.to respond_with(:success) }
-
- it 'groups blames properly' do
- blame = assigns(:blame)
- # Sanity check a few items
- expect(blame.count).to eq(18)
- expect(blame[0][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
- expect(blame[0][:lines]).to eq(["require 'fileutils'", "require 'open3'", ""])
-
- expect(blame[1][:commit].sha).to eq('874797c3a73b60d2187ed6e2fcabd289ff75171e')
- expect(blame[1][:lines]).to eq(["module Popen", " extend self"])
-
- expect(blame[-1][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
- expect(blame[-1][:lines]).to eq([" end", "end"])
- end
- end
- end
-end
diff --git a/spec/controllers/branches_controller_spec.rb b/spec/controllers/branches_controller_spec.rb
deleted file mode 100644
index 8e06d4bdc77..00000000000
--- a/spec/controllers/branches_controller_spec.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-require 'spec_helper'
-
-describe Projects::BranchesController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
-
- before do
- sign_in(user)
-
- project.team << [user, :master]
-
- allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz'])
- allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0'])
- controller.instance_variable_set(:@project, project)
- end
-
- describe "POST create" do
- render_views
-
- before do
- post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- branch_name: branch,
- ref: ref
- end
-
- context "valid branch name, valid source" do
- let(:branch) { "merge_branch" }
- let(:ref) { "master" }
- it 'redirects' do
- expect(subject).
- to redirect_to("/#{project.path_with_namespace}/tree/merge_branch")
- end
- end
-
- context "invalid branch name, valid ref" do
- let(:branch) { "<script>alert('merge');</script>" }
- let(:ref) { "master" }
- it 'redirects' do
- expect(subject).
- to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');")
- end
- end
-
- context "valid branch name, invalid ref" do
- let(:branch) { "merge_branch" }
- let(:ref) { "<script>alert('ref');</script>" }
- it { is_expected.to render_template('new') }
- end
-
- context "invalid branch name, invalid ref" do
- let(:branch) { "<script>alert('merge');</script>" }
- let(:ref) { "<script>alert('ref');</script>" }
- it { is_expected.to render_template('new') }
- end
-
- context "valid branch name with encoded slashes" do
- let(:branch) { "feature%2Ftest" }
- let(:ref) { "<script>alert('ref');</script>" }
- it { is_expected.to render_template('new') }
- it { project.repository.branch_names.include?('feature/test')}
- end
- end
-
- describe "POST destroy" do
- render_views
-
- before do
- post :destroy,
- format: :js,
- id: branch,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param
- end
-
- context "valid branch name, valid source" do
- let(:branch) { "feature" }
-
- it { expect(response.status).to eq(200) }
- it { expect(subject).to render_template('destroy') }
- end
-
- context "valid branch name with unencoded slashes" do
- let(:branch) { "improve/awesome" }
-
- it { expect(response.status).to eq(200) }
- it { expect(subject).to render_template('destroy') }
- end
-
- context "valid branch name with encoded slashes" do
- let(:branch) { "improve%2Fawesome" }
-
- it { expect(response.status).to eq(200) }
- it { expect(subject).to render_template('destroy') }
- end
- context "invalid branch name, valid ref" do
- let(:branch) { "no-branch" }
-
- it { expect(response.status).to eq(404) }
- it { expect(subject).to render_template('destroy') }
- end
- end
-end
diff --git a/spec/controllers/ci/projects_controller_spec.rb b/spec/controllers/ci/projects_controller_spec.rb
new file mode 100644
index 00000000000..db0748f323f
--- /dev/null
+++ b/spec/controllers/ci/projects_controller_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Ci::ProjectsController do
+ let(:visibility) { :public }
+ let!(:project) { create(:project, visibility, ci_id: 1) }
+ let(:ci_id) { project.ci_id }
+
+ ##
+ # Specs for *deprecated* CI badge
+ #
+ describe '#badge' do
+ shared_examples 'badge provider' do
+ it 'shows badge' do
+ expect(response.status).to eq 200
+ expect(response.headers)
+ .to include('Content-Type' => 'image/svg+xml')
+ end
+ end
+
+ context 'user not signed in' do
+ before { get(:badge, id: ci_id) }
+
+ context 'project has no ci_id reference' do
+ let(:ci_id) { 123 }
+
+ it 'returns 404' do
+ expect(response.status).to eq 404
+ end
+ end
+
+ context 'project is public' do
+ let(:visibility) { :public }
+ it_behaves_like 'badge provider'
+ end
+
+ context 'project is private' do
+ let(:visibility) { :private }
+ it_behaves_like 'badge provider'
+ end
+ end
+
+ context 'user signed in' do
+ let(:user) { create(:user) }
+ before { sign_in(user) }
+ before { get(:badge, id: ci_id) }
+
+ context 'private is internal' do
+ let(:visibility) { :internal }
+ it_behaves_like 'badge provider'
+ end
+ end
+ end
+end
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
index 7793bf1e421..f09e4fcb154 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -81,7 +81,7 @@ describe Projects::CommitController do
expect(response.body).to start_with("diff --git")
# without whitespace option, there are more than 2 diff_splits
- diff_splits = assigns(:diffs)[0].diff.split("\n")
+ diff_splits = assigns(:diffs).first.diff.split("\n")
expect(diff_splits.length).to be <= 2
end
end
@@ -143,4 +143,53 @@ describe Projects::CommitController do
expect(assigns(:tags)).to include("v1.1.0")
end
end
+
+ describe '#revert' do
+ context 'when target branch is not provided' do
+ it 'should render the 404 page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: commit.id)
+
+ expect(response).not_to be_success
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when the revert was successful' do
+ it 'should redirect to the commits page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(flash[:notice]).to eq('The commit has been successfully reverted.')
+ end
+ end
+
+ context 'when the revert failed' do
+ before do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+ end
+
+ it 'should redirect to the commit page' do
+ # Reverting a commit that has been already reverted.
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
+ expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
+ end
+ end
+ end
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
new file mode 100644
index 00000000000..938e97298b6
--- /dev/null
+++ b/spec/controllers/groups_controller_spec.rb
@@ -0,0 +1,23 @@
+require 'rails_helper'
+
+describe GroupsController do
+ describe 'GET index' do
+ context 'as a user' do
+ it 'redirects to Groups Dashboard' do
+ sign_in(create(:user))
+
+ get :index
+
+ expect(response).to redirect_to(dashboard_groups_path)
+ end
+ end
+
+ context 'as a guest' do
+ it 'redirects to Explore Groups' do
+ get :index
+
+ expect(response).to redirect_to(explore_groups_path)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/profile_keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index b6573f105dc..b6573f105dc 100644
--- a/spec/controllers/profile_keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb
new file mode 100644
index 00000000000..25f06299a29
--- /dev/null
+++ b/spec/controllers/projects/blame_controller_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Projects::BlameController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ project.team << [user, :master]
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "GET show" do
+ render_views
+
+ before do
+ get(:show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id)
+ end
+
+ context "valid file" do
+ let(:id) { 'master/files/ruby/popen.rb' }
+ it { is_expected.to respond_with(:success) }
+ end
+ end
+end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
new file mode 100644
index 00000000000..98ae424ed7c
--- /dev/null
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -0,0 +1,134 @@
+require 'spec_helper'
+
+describe Projects::BranchesController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ project.team << [user, :master]
+
+ allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz'])
+ allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0'])
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "POST create" do
+ render_views
+
+ context "on creation of a new branch" do
+ before do
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ ref: ref
+ end
+
+ context "valid branch name, valid source" do
+ let(:branch) { "merge_branch" }
+ let(:ref) { "master" }
+ it 'redirects' do
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/tree/merge_branch")
+ end
+ end
+
+ context "invalid branch name, valid ref" do
+ let(:branch) { "<script>alert('merge');</script>" }
+ let(:ref) { "master" }
+ it 'redirects' do
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');")
+ end
+ end
+
+ context "valid branch name, invalid ref" do
+ let(:branch) { "merge_branch" }
+ let(:ref) { "<script>alert('ref');</script>" }
+ it { is_expected.to render_template('new') }
+ end
+
+ context "invalid branch name, invalid ref" do
+ let(:branch) { "<script>alert('merge');</script>" }
+ let(:ref) { "<script>alert('ref');</script>" }
+ it { is_expected.to render_template('new') }
+ end
+
+ context "valid branch name with encoded slashes" do
+ let(:branch) { "feature%2Ftest" }
+ let(:ref) { "<script>alert('ref');</script>" }
+ it { is_expected.to render_template('new') }
+ it { project.repository.branch_names.include?('feature/test') }
+ end
+ end
+
+ describe "created from the new branch button on issues" do
+ let(:branch) { "1-feature-branch" }
+ let!(:issue) { create(:issue, project: project) }
+
+
+ it 'redirects' do
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ issue_iid: issue.iid
+
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/tree/1-feature-branch")
+ end
+
+ it 'posts a system note' do
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch")
+
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ issue_iid: issue.iid
+ end
+
+ end
+ end
+
+ describe "POST destroy" do
+ render_views
+
+ before do
+ post :destroy,
+ format: :js,
+ id: branch,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ end
+
+ context "valid branch name, valid source" do
+ let(:branch) { "feature" }
+
+ it { expect(response.status).to eq(200) }
+ it { expect(subject).to render_template('destroy') }
+ end
+
+ context "valid branch name with unencoded slashes" do
+ let(:branch) { "improve/awesome" }
+
+ it { expect(response.status).to eq(200) }
+ it { expect(subject).to render_template('destroy') }
+ end
+
+ context "valid branch name with encoded slashes" do
+ let(:branch) { "improve%2Fawesome" }
+
+ it { expect(response.status).to eq(200) }
+ it { expect(subject).to render_template('destroy') }
+ end
+ context "invalid branch name, valid ref" do
+ let(:branch) { "no-branch" }
+
+ it { expect(response.status).to eq(404) }
+ it { expect(subject).to render_template('destroy') }
+ end
+ end
+end
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
new file mode 100644
index 00000000000..438e776ec4b
--- /dev/null
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+describe Projects::CommitController do
+ describe 'GET show' do
+ let(:project) { create(:project) }
+
+ before do
+ user = create(:user)
+ project.team << [user, :master]
+
+ sign_in(user)
+ end
+
+ context 'with valid id' do
+ it 'responds with 200' do
+ go id: project.commit.id
+
+ expect(response).to be_ok
+ end
+ end
+
+ context 'with invalid id' do
+ it 'responds with 404' do
+ go id: project.commit.id.reverse
+
+ expect(response).to be_not_found
+ end
+ end
+
+ def go(id:)
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id
+ end
+ end
+end
diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 7d8089c4bc6..7d8089c4bc6 100644
--- a/spec/controllers/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index be19f1abc53..788a609ee40 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::CompareController do
to: ref_to)
expect(response).to be_success
- expect(assigns(:diffs).length).to be >= 1
+ expect(assigns(:diffs).first).to_not be_nil
expect(assigns(:commits).length).to be >= 1
end
@@ -32,10 +32,10 @@ describe Projects::CompareController do
w: 1)
expect(response).to be_success
- expect(assigns(:diffs).length).to be >= 1
+ expect(assigns(:diffs).first).to_not be_nil
expect(assigns(:commits).length).to be >= 1
# without whitespace option, there are more than 2 diff_splits
- diff_splits = assigns(:diffs)[0].diff.split("\n")
+ diff_splits = assigns(:diffs).first.diff.split("\n")
expect(diff_splits.length).to be <= 2
end
@@ -48,7 +48,7 @@ describe Projects::CompareController do
to: ref_to)
expect(response).to be_success
- expect(assigns(:diffs)).to eq([])
+ expect(assigns(:diffs).to_a).to eq([])
expect(assigns(:commits)).to eq([])
end
diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
new file mode 100644
index 00000000000..038dfeb8466
--- /dev/null
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Projects::FindFileController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ project.team << [user, :master]
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "GET #show" do
+ # Make sure any errors accessing the tree in our views bubble up to this spec
+ render_views
+
+ before do
+ get(:show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id)
+ end
+
+ context "valid branch" do
+ let(:id) { 'master' }
+ it { is_expected.to respond_with(:success) }
+ end
+
+ context "invalid branch" do
+ let(:id) { 'invalid-branch' }
+ it { is_expected.to respond_with(:not_found) }
+ end
+ end
+
+ describe "GET #list" do
+ def go(format: 'json')
+ get :list,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id,
+ format: format
+ end
+
+ context "valid branch" do
+ let(:id) { 'master' }
+ it 'returns an array of file path list' do
+ go
+
+ json = JSON.parse(response.body)
+ is_expected.to respond_with(:success)
+ expect(json).not_to eq(nil)
+ expect(json.length).to be >= 0
+ end
+ end
+
+ context "invalid branch" do
+ let(:id) { 'invalid-branch' }
+
+ it 'responds with status 404' do
+ go
+ is_expected.to respond_with(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
new file mode 100644
index 00000000000..70ed8f3a62e
--- /dev/null
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Projects::ForksController do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:forked_project) { Projects::ForkService.new(project, user).execute }
+ let(:group) { create(:group, owner: forked_project.creator) }
+
+ describe 'GET index' do
+ def get_forks
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ end
+
+ context 'when fork is public' do
+ before { forked_project.update_attribute(:visibility_level, Project::PUBLIC) }
+
+ it 'should be visible for non logged in users' do
+ get_forks
+
+ expect(assigns[:forks]).to be_present
+ end
+ end
+
+ context 'when fork is private' do
+ before do
+ forked_project.update_attributes(visibility_level: Project::PRIVATE, group: group)
+ end
+
+ it 'should not be visible for non logged in users' do
+ get_forks
+
+ expect(assigns[:forks]).to be_blank
+ end
+
+ context 'when user is logged in' do
+ before { sign_in(project.creator) }
+
+ context 'when user is not a Project member neither a group member' do
+ it 'should not see the Project listed' do
+ get_forks
+
+ expect(assigns[:forks]).to be_blank
+ end
+ end
+
+ context 'when user is a member of the Project' do
+ before { forked_project.team << [project.creator, :developer] }
+
+ it 'should see the project listed' do
+ get_forks
+
+ expect(assigns[:forks]).to be_present
+ end
+ end
+
+ context 'when user is a member of the Group' do
+ before { forked_project.group.add_developer(project.creator) }
+
+ it 'should see the project listed' do
+ get_forks
+
+ expect(assigns[:forks]).to be_present
+ end
+ end
+
+ end
+ end
+ end
+
+end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
new file mode 100644
index 00000000000..2acbba469e3
--- /dev/null
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -0,0 +1,121 @@
+require 'spec_helper'
+
+describe Projects::ImportsController do
+ let(:user) { create(:user) }
+
+ describe 'GET #show' do
+ context 'when repository does not exists' do
+ let(:project) { create(:empty_project) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ end
+
+ it 'renders template' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(response).to render_template :show
+ end
+
+ it 'sets flash.now if params is present' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'Started' }
+
+ expect(flash.now[:notice]).to eq 'Started'
+ end
+ end
+
+ context 'when repository exists' do
+ let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git') }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ end
+
+ context 'when import is in progress' do
+ before do
+ project.update_attribute(:import_status, :started)
+ end
+
+ it 'renders template' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(response).to render_template :show
+ end
+
+ it 'sets flash.now if params is present' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: { to: '/', notice_now: 'In progress' }
+
+ expect(flash.now[:notice]).to eq 'In progress'
+ end
+ end
+
+ context 'when import failed' do
+ before do
+ project.update_attribute(:import_status, :failed)
+ end
+
+ it 'redirects to new_namespace_project_import_path' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project)
+ end
+ end
+
+ context 'when import finished' do
+ before do
+ project.update_attribute(:import_status, :finished)
+ end
+
+ context 'when project is a fork' do
+ it 'redirects to namespace_project_path' do
+ allow_any_instance_of(Project).to receive(:forked?).and_return(true)
+
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(flash[:notice]).to eq 'The project was successfully forked.'
+ expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ end
+ end
+
+ context 'when project is external' do
+ it 'redirects to namespace_project_path' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(flash[:notice]).to eq 'The project was successfully imported.'
+ expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ end
+ end
+
+ context 'when continue params is present' do
+ let(:params) do
+ {
+ to: namespace_project_path(project.namespace, project),
+ notice: 'Finished'
+ }
+ end
+
+ it 'redirects to params[:to]' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, continue: params
+
+ expect(flash[:notice]).to eq params[:notice]
+ expect(response).to redirect_to params[:to]
+ end
+ end
+ end
+
+ context 'when import never happened' do
+ before do
+ project.update_attribute(:import_status, :none)
+ end
+
+ it 'redirects to namespace_project_path' do
+ get :show, namespace_id: project.namespace.to_param, project_id: project.to_param
+
+ expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 76d56bc989d..2cd81231144 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1,16 +1,16 @@
require('spec_helper')
describe Projects::IssuesController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:issue) { create(:issue, project: project) }
+ describe "GET #index" do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
- before do
- sign_in(user)
- project.team << [user, :developer]
- end
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
- describe "GET #index" do
it "returns index" do
get :index, namespace_id: project.namespace.path, project_id: project.path
@@ -38,6 +38,152 @@ describe Projects::IssuesController do
get :index, namespace_id: project.namespace.path, project_id: project.path
expect(response.status).to eq(404)
end
+ end
+
+ describe 'Confidential Issues' do
+ let(:project) { create(:empty_project, :public) }
+ let(:assignee) { create(:assignee) }
+ let(:author) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:issue) { create(:issue, project: project) }
+ let!(:unescaped_parameter_value) { create(:issue, :confidential, project: project, author: author) }
+ let!(:request_forgery_timing_attack) { create(:issue, :confidential, project: project, assignee: assignee) }
+
+ describe 'GET #index' do
+ it 'should not list confidential issues for guests' do
+ sign_out(:user)
+ get_issues
+
+ expect(assigns(:issues)).to eq [issue]
+ end
+
+ it 'should not list confidential issues for non project members' do
+ sign_in(non_member)
+ get_issues
+
+ expect(assigns(:issues)).to eq [issue]
+ end
+
+ it 'should list confidential issues for author' do
+ sign_in(author)
+ get_issues
+
+ expect(assigns(:issues)).to include unescaped_parameter_value
+ expect(assigns(:issues)).not_to include request_forgery_timing_attack
+ end
+
+ it 'should list confidential issues for assignee' do
+ sign_in(assignee)
+ get_issues
+
+ expect(assigns(:issues)).not_to include unescaped_parameter_value
+ expect(assigns(:issues)).to include request_forgery_timing_attack
+ end
+
+ it 'should list confidential issues for project members' do
+ sign_in(member)
+ project.team << [member, :developer]
+
+ get_issues
+
+ expect(assigns(:issues)).to include unescaped_parameter_value
+ expect(assigns(:issues)).to include request_forgery_timing_attack
+ end
+
+ it 'should list confidential issues for admin' do
+ sign_in(admin)
+ get_issues
+
+ expect(assigns(:issues)).to include unescaped_parameter_value
+ expect(assigns(:issues)).to include request_forgery_timing_attack
+ end
+
+ def get_issues
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ end
+ end
+ shared_examples_for 'restricted action' do |http_status|
+ it 'returns 404 for guests' do
+ sign_out :user
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status :not_found
+ end
+
+ it 'returns 404 for non project members' do
+ sign_in(non_member)
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status :not_found
+ end
+
+ it "returns #{http_status[:success]} for author" do
+ sign_in(author)
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+
+ it "returns #{http_status[:success]} for assignee" do
+ sign_in(assignee)
+ go(id: request_forgery_timing_attack.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+
+ it "returns #{http_status[:success]} for project members" do
+ sign_in(member)
+ project.team << [member, :developer]
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+
+ it "returns #{http_status[:success]} for admin" do
+ sign_in(admin)
+ go(id: unescaped_parameter_value.to_param)
+
+ expect(response).to have_http_status http_status[:success]
+ end
+ end
+
+ describe 'GET #show' do
+ it_behaves_like 'restricted action', success: 200
+
+ def go(id:)
+ get :show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id
+ end
+ end
+
+ describe 'GET #edit' do
+ it_behaves_like 'restricted action', success: 200
+
+ def go(id:)
+ get :edit,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id
+ end
+ end
+
+ describe 'PUT #update' do
+ it_behaves_like 'restricted action', success: 302
+
+ def go(id:)
+ put :update,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id,
+ issue: { title: 'New title' }
+ end
+ end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 6aaec224f6e..e82fe26c7a6 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -123,6 +123,40 @@ describe Projects::MergeRequestsController do
end
end
+ describe 'GET #index' do
+ def get_merge_requests
+ get :index,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ state: 'opened'
+ end
+
+ context 'when filtering by opened state' do
+
+ context 'with opened merge requests' do
+ it 'should list those merge requests' do
+ get_merge_requests
+
+ expect(assigns(:merge_requests)).to include(merge_request)
+ end
+ end
+
+ context 'with reopened merge requests' do
+ before do
+ merge_request.close!
+ merge_request.reopen!
+ end
+
+ it 'should list those merge requests' do
+ get_merge_requests
+
+ expect(assigns(:merge_requests)).to include(merge_request)
+ end
+ end
+
+ end
+ end
+
describe 'GET diffs' do
def go(format: 'html')
get :diffs,
@@ -188,7 +222,7 @@ describe Projects::MergeRequestsController do
expect(response).to render_template('diffs')
end
end
-
+
context 'as json' do
it 'renders the diffs template to a string' do
go format: 'json'
@@ -199,6 +233,32 @@ describe Projects::MergeRequestsController do
end
end
+ describe 'GET diffs with view' do
+ def go(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.iid
+ }
+
+ get :diffs, params.merge(extra_params)
+ end
+
+ it 'saves the preferred diff view in a cookie' do
+ go view: 'parallel'
+
+ expect(response.cookies['diff_view']).to eq('parallel')
+ end
+
+ it 'assigns :view param based on cookie' do
+ request.cookies['diff_view'] = 'parallel'
+
+ go
+
+ expect(controller.params[:view]).to eq 'parallel'
+ end
+ end
+
describe 'GET commits' do
def go(format: 'html')
get :commits,
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 18a30033ed8..0ddbec9eac2 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -2,35 +2,41 @@ require "spec_helper"
describe Projects::RepositoriesController do
let(:project) { create(:project) }
- let(:user) { create(:user) }
describe "GET archive" do
- before do
- sign_in(user)
- project.team << [user, :developer]
+ context 'as a guest' do
+ it 'responds with redirect in correct format' do
+ get :archive, namespace_id: project.namespace.path, project_id: project.path, format: "zip"
- allow(ArchiveRepositoryService).to receive(:new).and_return(service)
- end
-
- let(:service) { ArchiveRepositoryService.new(project, "master", "zip") }
-
- it "executes ArchiveRepositoryService" do
- expect(ArchiveRepositoryService).to receive(:new).with(project, "master", "zip")
- expect(service).to receive(:execute)
-
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+ expect(response.content_type).to start_with 'text/html'
+ expect(response).to be_redirect
+ end
end
- context "when the service raises an error" do
+ context 'as a user' do
+ let(:user) { create(:user) }
before do
- allow(service).to receive(:execute).and_raise("Archive failed")
+ project.team << [user, :developer]
+ sign_in(user)
end
+ it "uses Gitlab::Workhorse" do
+ expect(Gitlab::Workhorse).to receive(:send_git_archive).with(project, "master", "zip")
- it "renders Not Found" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
+ end
+
+ context "when the service raises an error" do
+
+ before do
+ allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
+ end
+
+ it "renders Not Found" do
+ get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
- expect(response.status).to eq(404)
+ expect(response.status).to eq(404)
+ end
end
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 665526fde93..1893e946f5c 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -9,19 +9,6 @@ describe ProjectsController do
describe "GET show" do
- context "when requested by `go get`" do
- render_views
-
- it "renders the go-import meta tag" do
- get :show, "go-get" => "1", namespace_id: "bogus_namespace", id: "bogus_project"
-
- expect(response.body).to include("name='go-import'")
-
- content = "localhost/bogus_namespace/bogus_project git http://localhost/bogus_namespace/bogus_project.git"
- expect(response.body).to include("content='#{content}'")
- end
- end
-
context "rendering default project view" do
render_views
@@ -86,6 +73,14 @@ describe ProjectsController do
end
end
end
+
+ context "when the url contains .atom" do
+ let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') }
+
+ it 'expect an error creating the project' do
+ expect(public_project_with_dot_atom).not_to be_valid
+ end
+ end
end
describe "#destroy" do
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
new file mode 100644
index 00000000000..9ced397bd4a
--- /dev/null
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+describe SentNotificationsController, type: :controller do
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, author: user) }
+ let(:sent_notification) { create(:sent_notification, noteable: issue) }
+
+ describe 'GET #unsubscribe' do
+ it 'returns a 404 when calling without existing id' do
+ get(:unsubscribe, id: '0' * 32)
+
+ expect(response.status).to be 404
+ end
+
+ context 'calling with id' do
+ it 'shows a flash message to the user' do
+ get(:unsubscribe, id: sent_notification.reply_key)
+
+ expect(response.status).to be 302
+
+ expect(response).to redirect_to new_user_session_path
+ expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+ end
+ end
+ end
+end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 104a5f50143..7337ff58be1 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -41,6 +41,7 @@ describe UsersController do
end
describe 'GET #calendar' do
+
it 'renders calendar' do
sign_in(user)
@@ -48,6 +49,23 @@ describe UsersController do
expect(response).to render_template('calendar')
end
+
+ context 'forked project' do
+ let!(:project) { create(:project) }
+ let!(:forked_project) { Projects::ForkService.new(project, user).execute }
+
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ EventCreateService.new.push(project, user, [])
+ EventCreateService.new.push(forked_project, user, [])
+ end
+
+ it 'includes forked projects' do
+ get :calendar, username: user.username
+ expect(assigns(:contributions_calendar).projects.count).to eq(2)
+ end
+ end
end
describe 'GET #calendar_activities' do
diff --git a/spec/factories.rb b/spec/factories.rb
deleted file mode 100644
index d6b4efa9a03..00000000000
--- a/spec/factories.rb
+++ /dev/null
@@ -1,215 +0,0 @@
-include ActionDispatch::TestProcess
-
-FactoryGirl.define do
- sequence :sentence, aliases: [:title, :content] do
- FFaker::Lorem.sentence
- end
-
- sequence :name do
- FFaker::Name.name
- end
-
- sequence :file_name do
- FFaker::Internet.user_name
- end
-
- sequence(:url) { FFaker::Internet.uri('http') }
-
- factory :user, aliases: [:author, :assignee, :owner, :creator] do
- email { FFaker::Internet.email }
- name
- sequence(:username) { |n| "#{FFaker::Internet.user_name}#{n}" }
- password "12345678"
- confirmed_at { Time.now }
- confirmation_token { nil }
- can_create_group true
-
- trait :admin do
- admin true
- end
-
- trait :two_factor do
- before(:create) do |user|
- user.two_factor_enabled = true
- user.otp_secret = User.generate_otp_secret(32)
- user.generate_otp_backup_codes!
- end
- end
-
- factory :omniauth_user do
- ignore do
- extern_uid '123456'
- provider 'ldapmain'
- end
-
- after(:create) do |user, evaluator|
- user.identities << create(
- :identity,
- provider: evaluator.provider,
- extern_uid: evaluator.extern_uid
- )
- end
- end
-
- factory :admin, traits: [:admin]
- end
-
- factory :group do
- sequence(:name) { |n| "group#{n}" }
- path { name.downcase.gsub(/\s/, '_') }
- type 'Group'
- end
-
- factory :namespace do
- sequence(:name) { |n| "namespace#{n}" }
- path { name.downcase.gsub(/\s/, '_') }
- owner
- end
-
- factory :project_member do
- user
- project
- access_level { ProjectMember::MASTER }
- end
-
- factory :issue do
- title
- author
- project
-
- trait :closed do
- state :closed
- end
-
- trait :reopened do
- state :reopened
- end
-
- factory :closed_issue, traits: [:closed]
- factory :reopened_issue, traits: [:reopened]
- end
-
- factory :event do
- factory :closed_issue_event do
- project
- action { Event::CLOSED }
- target factory: :closed_issue
- author factory: :user
- end
- end
-
- factory :key do
- title
- key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com"
- end
-
- factory :deploy_key, class: 'DeployKey' do
- end
-
- factory :personal_key do
- user
- end
-
- factory :another_key do
- key do
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ"
- end
-
- factory :another_deploy_key, class: 'DeployKey' do
- end
- end
- end
-
- factory :email do
- user
- email do
- FFaker::Internet.email('alias')
- end
-
- factory :another_email do
- email do
- FFaker::Internet.email('another.alias')
- end
- end
- end
-
- factory :milestone do
- title
- project
-
- trait :closed do
- state :closed
- end
-
- factory :closed_milestone, traits: [:closed]
- end
-
- factory :system_hook do
- url
- end
-
- factory :project_hook do
- url
- end
-
- factory :project_snippet do
- project
- author
- title
- content
- file_name
- end
-
- factory :personal_snippet do
- author
- title
- content
- file_name
-
- trait :public do
- visibility_level Gitlab::VisibilityLevel::PUBLIC
- end
-
- trait :internal do
- visibility_level Gitlab::VisibilityLevel::INTERNAL
- end
-
- trait :private do
- visibility_level Gitlab::VisibilityLevel::PRIVATE
- end
- end
-
- factory :snippet do
- author
- title
- content
- file_name
- end
-
- factory :protected_branch do
- name
- project
- end
-
- factory :service do
- type ""
- title "GitLab CI"
- project
- end
-
- factory :service_hook do
- url
- service
- end
-
- factory :deploy_keys_project do
- deploy_key
- project
- end
-
- factory :identity do
- provider 'ldapmain'
- extern_uid 'my-ldap-id'
- end
-end
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb
index 8d287ded292..d0e8c778518 100644
--- a/spec/factories/abuse_reports.rb
+++ b/spec/factories/abuse_reports.rb
@@ -10,8 +10,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :abuse_report do
reporter factory: :user
diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb
new file mode 100644
index 00000000000..cf2a2b76bcb
--- /dev/null
+++ b/spec/factories/appearances.rb
@@ -0,0 +1,8 @@
+# Read about factories at https://github.com/thoughtbot/factory_girl
+
+FactoryGirl.define do
+ factory :appearance do
+ title "MepMep"
+ description "This is my Community Edition instance"
+ end
+end
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index ea0039d39e6..373ca75467e 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -6,22 +6,26 @@
# message :text not null
# starts_at :datetime
# ends_at :datetime
-# alert_type :integer
# created_at :datetime
# updated_at :datetime
# color :string(255)
# font :string(255)
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :broadcast_message do
message "MyText"
- starts_at "2013-11-12 13:43:25"
- ends_at "2013-11-12 13:43:25"
- alert_type 1
- color "#555555"
- font "#BBBBBB"
+ starts_at Date.today
+ ends_at Date.tomorrow
+
+ trait :expired do
+ starts_at 5.days.ago
+ ends_at 3.days.ago
+ end
+
+ trait :future do
+ starts_at 5.days.from_now
+ ends_at 6.days.from_now
+ end
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index f76e826f138..cd49e559b7d 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -1,35 +1,11 @@
-# == Schema Information
-#
-# Table name: builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# commit_id :integer
-# coverage :float
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-#
-
-# Read about factories at https://github.com/thoughtbot/factory_girl
+include ActionDispatch::TestProcess
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
name 'test'
ref 'master'
tag false
+ created_at 'Di 29. Okt 09:50:00 CET 2013'
started_at 'Di 29. Okt 09:51:28 CET 2013'
finished_at 'Di 29. Okt 09:53:28 CET 2013'
commands 'ls -a'
@@ -42,6 +18,30 @@ FactoryGirl.define do
commit factory: :ci_commit
+ trait :success do
+ status 'success'
+ end
+
+ trait :failed do
+ status 'failed'
+ end
+
+ trait :canceled do
+ status 'canceled'
+ end
+
+ trait :running do
+ status 'running'
+ end
+
+ trait :pending do
+ status 'pending'
+ end
+
+ trait :allowed_to_fail do
+ allow_failure true
+ end
+
after(:build) do |build, evaluator|
build.project = build.commit.project
end
@@ -54,5 +54,29 @@ FactoryGirl.define do
factory :ci_build_tag do
tag true
end
+
+ factory :ci_build_with_coverage do
+ coverage 99.9
+ end
+
+ trait :trace do
+ after(:create) do |build, evaluator|
+ build.trace = 'BUILD TRACE'
+ end
+ end
+
+ trait :artifacts do
+ after(:create) do |build, _|
+ build.artifacts_file =
+ fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
+ 'application/zip')
+
+ build.artifacts_metadata =
+ fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
+ 'application/x-gzip')
+
+ build.save!
+ end
+ end
end
end
diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/commits.rb
index b42cafa518a..645cd7ae766 100644
--- a/spec/factories/ci/commits.rb
+++ b/spec/factories/ci/commits.rb
@@ -16,7 +16,6 @@
# gl_project_id :integer
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :ci_empty_commit, class: Ci::Commit do
sha '97de212e80737a608d939f648d959671fb0a0142'
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index 008d1c5d961..83fccad679f 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -9,8 +9,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_runner_project, class: Ci::RunnerProject do
runner_id 1
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index db759eca9ac..5b645fab32e 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -17,22 +17,18 @@
# architecture :string(255)
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_runner, class: Ci::Runner do
sequence :description do |n|
"My runner#{n}"
end
- platform "darwin"
+ platform "darwin"
+ is_shared false
+ active true
- factory :ci_shared_runner do
+ trait :shared do
is_shared true
end
-
- factory :ci_specific_runner do
- is_shared false
- end
end
end
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index db053c610cd..6d47d05f8ad 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -1,8 +1,8 @@
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_trigger_request, class: Ci::TriggerRequest do
factory :ci_trigger_request_with_variables do
+ trigger factory: :ci_trigger
+
variables do
{
TRIGGER_KEY: 'TRIGGER_VALUE'
diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb
index fd3afdb1ec2..a27b04424e5 100644
--- a/spec/factories/ci/triggers.rb
+++ b/spec/factories/ci/triggers.rb
@@ -1,5 +1,3 @@
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :ci_trigger_without_token, class: Ci::Trigger do
factory :ci_trigger do
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
new file mode 100644
index 00000000000..856a8e725eb
--- /dev/null
+++ b/spec/factories/ci/variables.rb
@@ -0,0 +1,20 @@
+# == Schema Information
+#
+# Table name: ci_variables
+#
+# id :integer not null, primary key
+# project_id :integer not null
+# key :string(255)
+# value :text
+# encrypted_value :text
+# encrypted_value_salt :string(255)
+# encrypted_value_iv :string(255)
+# gl_project_id :integer
+#
+
+FactoryGirl.define do
+ factory :ci_variable, class: Ci::Variable do
+ sequence(:key) { |n| "VARIABLE_#{n}" }
+ value 'VARIABLE_VALUE'
+ end
+end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 8898b71e2a3..b7c2b32cb13 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -1,11 +1,15 @@
FactoryGirl.define do
factory :commit_status, class: CommitStatus do
- started_at 'Di 29. Okt 09:51:28 CET 2013'
- finished_at 'Di 29. Okt 09:53:28 CET 2013'
name 'default'
status 'success'
description 'commit status'
commit factory: :ci_commit_with_one_job
+ started_at 'Tue, 26 Jan 2016 08:21:42 +0100'
+ finished_at 'Tue, 26 Jan 2016 08:23:42 +0100'
+
+ after(:build) do |build, evaluator|
+ build.project = build.commit.project
+ end
factory :generic_commit_status, class: GenericCommitStatus do
name 'generic'
diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb
new file mode 100644
index 00000000000..27cece487bd
--- /dev/null
+++ b/spec/factories/deploy_keys_projects.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :deploy_keys_project do
+ deploy_key
+ project
+ end
+end
diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb
new file mode 100644
index 00000000000..9794772ac7d
--- /dev/null
+++ b/spec/factories/emails.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :email do
+ user
+ email { FFaker::Internet.email('alias') }
+ end
+end
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
new file mode 100644
index 00000000000..90788f30ac9
--- /dev/null
+++ b/spec/factories/events.rb
@@ -0,0 +1,10 @@
+FactoryGirl.define do
+ factory :event do
+ factory :closed_issue_event do
+ project
+ action { Event::CLOSED }
+ target factory: :closed_issue
+ author factory: :user
+ end
+ end
+end
diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb
index 906e4106b32..252bf2747e1 100644
--- a/spec/factories/forked_project_links.rb
+++ b/spec/factories/forked_project_links.rb
@@ -9,8 +9,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :forked_project_link do
association :forked_to_project, factory: :project
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
new file mode 100644
index 00000000000..4a3a155d7ff
--- /dev/null
+++ b/spec/factories/groups.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :group do
+ sequence(:name) { |n| "group#{n}" }
+ path { name.downcase.gsub(/\s/, '_') }
+ type 'Group'
+ end
+end
diff --git a/spec/factories/identities.rb b/spec/factories/identities.rb
new file mode 100644
index 00000000000..26ef6f18698
--- /dev/null
+++ b/spec/factories/identities.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :identity do
+ provider 'ldapmain'
+ extern_uid 'my-ldap-id'
+ end
+end
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
new file mode 100644
index 00000000000..e72aa9479b7
--- /dev/null
+++ b/spec/factories/issues.rb
@@ -0,0 +1,22 @@
+FactoryGirl.define do
+ factory :issue do
+ title
+ author
+ project
+
+ trait :confidential do
+ confidential true
+ end
+
+ trait :closed do
+ state :closed
+ end
+
+ trait :reopened do
+ state :reopened
+ end
+
+ factory :closed_issue, traits: [:closed]
+ factory :reopened_issue, traits: [:reopened]
+ end
+end
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
new file mode 100644
index 00000000000..d69c5b38d0a
--- /dev/null
+++ b/spec/factories/keys.rb
@@ -0,0 +1,24 @@
+FactoryGirl.define do
+ factory :key do
+ title
+ key do
+ "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0= dummy@gitlab.com"
+ end
+
+ factory :deploy_key, class: 'DeployKey' do
+ end
+
+ factory :personal_key do
+ user
+ end
+
+ factory :another_key do
+ key do
+ "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ"
+ end
+
+ factory :another_deploy_key, class: 'DeployKey' do
+ end
+ end
+ end
+end
diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb
index bd304b5db6b..2939d4307c5 100644
--- a/spec/factories/label_links.rb
+++ b/spec/factories/label_links.rb
@@ -10,8 +10,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :label_link do
label
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index 8b12ee11af5..ea2be8928d5 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -11,11 +11,9 @@
# template :boolean default(FALSE)
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :label do
- title "Bug"
+ sequence(:title) { |n| "label#{n}" }
color "#990000"
project
end
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index 2da107ba24b..327858ce435 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -10,7 +10,7 @@
# file :string(255)
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
+include ActionDispatch::TestProcess
FactoryGirl.define do
factory :lfs_object do
diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb
index 3772236a77a..50b45843c99 100644
--- a/spec/factories/lfs_objects_projects.rb
+++ b/spec/factories/lfs_objects_projects.rb
@@ -9,8 +9,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :lfs_objects_project do
lfs_object
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 5b4d7f41bc4..e281e2f227b 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -2,25 +2,29 @@
#
# Table name: merge_requests
#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
+# id :integer not null, primary key
+# target_branch :string(255) not null
+# source_branch :string(255) not null
+# source_project_id :integer not null
+# author_id :integer
+# assignee_id :integer
+# title :string(255)
+# created_at :datetime
+# updated_at :datetime
+# milestone_id :integer
+# state :string(255)
+# merge_status :string(255)
+# target_project_id :integer not null
+# iid :integer
+# description :text
+# position :integer default(0)
+# locked_at :datetime
+# updated_by_id :integer
+# merge_error :string(255)
+# merge_params :text
+# merge_when_build_succeeds :boolean default(FALSE), not null
+# merge_user_id :integer
+# merge_commit_sha :string
#
FactoryGirl.define do
@@ -47,11 +51,20 @@ FactoryGirl.define do
trait :with_diffs do
end
+ trait :without_diffs do
+ source_branch "improve/awesome"
+ target_branch "master"
+ end
+
trait :conflict do
source_branch "feature_conflict"
target_branch "feature"
end
+ trait :merged do
+ state :merged
+ end
+
trait :closed do
state :closed
end
@@ -65,11 +78,22 @@ FactoryGirl.define do
target_branch "master"
end
+ trait :rebased do
+ source_branch "markdown"
+ target_branch "improve/awesome"
+ end
+
+ trait :diverged do
+ source_branch "feature"
+ target_branch "master"
+ end
+
trait :merge_when_build_succeeds do
merge_when_build_succeeds true
merge_user author
end
+ factory :merged_merge_request, traits: [:merged]
factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:reopened]
factory :merge_request_with_diffs, traits: [:with_diffs]
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
new file mode 100644
index 00000000000..e9e85962fe4
--- /dev/null
+++ b/spec/factories/milestones.rb
@@ -0,0 +1,12 @@
+FactoryGirl.define do
+ factory :milestone do
+ title
+ project
+
+ trait :closed do
+ state :closed
+ end
+
+ factory :closed_milestone, traits: [:closed]
+ end
+end
diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb
new file mode 100644
index 00000000000..1b1fc4ce80d
--- /dev/null
+++ b/spec/factories/namespaces.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :namespace do
+ sequence(:name) { |n| "namespace#{n}" }
+ path { name.downcase.gsub(/\s/, '_') }
+ owner
+ end
+end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 35a20adeef3..e5dcb159014 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -21,6 +21,8 @@
require_relative '../support/repo_helpers'
+include ActionDispatch::TestProcess
+
FactoryGirl.define do
factory :note do
project
@@ -34,6 +36,8 @@ FactoryGirl.define do
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system]
+ factory :downvote_note, traits: [:award, :downvote]
+ factory :upvote_note, traits: [:award, :upvote]
trait :on_commit do
project
@@ -65,6 +69,18 @@ FactoryGirl.define do
system true
end
+ trait :award do
+ is_award true
+ end
+
+ trait :downvote do
+ note "thumbsdown"
+ end
+
+ trait :upvote do
+ note "thumbsup"
+ end
+
trait :with_attachment do
attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
end
diff --git a/spec/factories/personal_snippets.rb b/spec/factories/personal_snippets.rb
new file mode 100644
index 00000000000..0f13b2c1020
--- /dev/null
+++ b/spec/factories/personal_snippets.rb
@@ -0,0 +1,4 @@
+FactoryGirl.define do
+ factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
+ end
+end
diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb
new file mode 100644
index 00000000000..e73cc05f9d7
--- /dev/null
+++ b/spec/factories/project_group_links.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :project_group_link do
+ project
+ group
+ end
+end
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
new file mode 100644
index 00000000000..94dd935a039
--- /dev/null
+++ b/spec/factories/project_hooks.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :project_hook do
+ url { FFaker::Internet.uri('http') }
+ end
+end
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
new file mode 100644
index 00000000000..cf3659ba275
--- /dev/null
+++ b/spec/factories/project_members.rb
@@ -0,0 +1,27 @@
+FactoryGirl.define do
+ factory :project_member do
+ user
+ project
+ master
+
+ trait :guest do
+ access_level ProjectMember::GUEST
+ end
+
+ trait :reporter do
+ access_level ProjectMember::REPORTER
+ end
+
+ trait :developer do
+ access_level ProjectMember::DEVELOPER
+ end
+
+ trait :master do
+ access_level ProjectMember::MASTER
+ end
+
+ trait :owner do
+ access_level ProjectMember::OWNER
+ end
+ end
+end
diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb
new file mode 100644
index 00000000000..d681a2c8483
--- /dev/null
+++ b/spec/factories/project_snippets.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
+ project
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 112213377ff..c14b99606ba 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -29,6 +29,13 @@
# import_source :string(255)
# commit_count :integer default(0)
# import_error :text
+# ci_id :integer
+# builds_enabled :boolean default(TRUE), not null
+# shared_runners_enabled :boolean default(TRUE), not null
+# runners_token :string
+# build_coverage_regex :string
+# build_allow_git_fetch :boolean default(TRUE), not null
+# build_timeout :integer default(3600), not null
#
FactoryGirl.define do
diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb
new file mode 100644
index 00000000000..28ed8078157
--- /dev/null
+++ b/spec/factories/protected_branches.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :protected_branch do
+ name
+ project
+ end
+end
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index 43d09b17534..7f331c37256 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -10,8 +10,6 @@
# updated_at :datetime
#
-# Read about factories at https://github.com/thoughtbot/factory_girl
-
FactoryGirl.define do
factory :release do
tag "v1.1.0"
diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb
new file mode 100644
index 00000000000..78eb929c6e7
--- /dev/null
+++ b/spec/factories/sent_notifications.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :sent_notification do
+ project
+ recipient factory: :user
+ noteable factory: :issue
+ reply_key "0123456789abcdef" * 2
+ end
+end
diff --git a/spec/factories/service_hooks.rb b/spec/factories/service_hooks.rb
new file mode 100644
index 00000000000..6dd6af63f3e
--- /dev/null
+++ b/spec/factories/service_hooks.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :service_hook do
+ url { FFaker::Internet.uri('http') }
+ service
+ end
+end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
new file mode 100644
index 00000000000..9de78d68280
--- /dev/null
+++ b/spec/factories/services.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :service do
+ project
+ end
+end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
new file mode 100644
index 00000000000..365f12a0c95
--- /dev/null
+++ b/spec/factories/snippets.rb
@@ -0,0 +1,28 @@
+FactoryGirl.define do
+ sequence :title, aliases: [:content] do
+ FFaker::Lorem.sentence
+ end
+
+ sequence :file_name do
+ FFaker::Internet.user_name
+ end
+
+ factory :snippet do
+ author
+ title
+ content
+ file_name
+
+ trait :public do
+ visibility_level Snippet::PUBLIC
+ end
+
+ trait :internal do
+ visibility_level Snippet::INTERNAL
+ end
+
+ trait :private do
+ visibility_level Snippet::PRIVATE
+ end
+ end
+end
diff --git a/spec/factories/spam_logs.rb b/spec/factories/spam_logs.rb
new file mode 100644
index 00000000000..a4f6d291269
--- /dev/null
+++ b/spec/factories/spam_logs.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+ factory :spam_log do
+ user
+ source_ip { FFaker::Internet.ip_v4_address }
+ noteable_type 'Issue'
+ title { FFaker::Lorem.sentence }
+ description { FFaker::Lorem.paragraph(5) }
+ end
+end
diff --git a/spec/factories/system_hooks.rb b/spec/factories/system_hooks.rb
new file mode 100644
index 00000000000..c786e9cb79b
--- /dev/null
+++ b/spec/factories/system_hooks.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :system_hook do
+ url { FFaker::Internet.uri('http') }
+ end
+end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
new file mode 100644
index 00000000000..bd85b1d798a
--- /dev/null
+++ b/spec/factories/todos.rb
@@ -0,0 +1,34 @@
+# == Schema Information
+#
+# Table name: todos
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# target_id :integer not null
+# target_type :string not null
+# author_id :integer
+# note_id :integer
+# action :integer not null
+# state :string not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+FactoryGirl.define do
+ factory :todo do
+ project
+ author
+ user
+ target factory: :issue
+ action { Todo::ASSIGNED }
+
+ trait :assigned do
+ action { Todo::ASSIGNED }
+ end
+
+ trait :mentioned do
+ action { Todo::MENTIONED }
+ end
+ end
+end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
new file mode 100644
index 00000000000..a5c60c51c5b
--- /dev/null
+++ b/spec/factories/users.rb
@@ -0,0 +1,43 @@
+FactoryGirl.define do
+ sequence(:name) { FFaker::Name.name }
+
+ factory :user, aliases: [:author, :assignee, :recipient, :owner, :creator] do
+ email { FFaker::Internet.email }
+ name
+ sequence(:username) { |n| "#{FFaker::Internet.user_name}#{n}" }
+ password "12345678"
+ confirmed_at { Time.now }
+ confirmation_token { nil }
+ can_create_group true
+
+ trait :admin do
+ admin true
+ end
+
+ trait :two_factor do
+ before(:create) do |user|
+ user.two_factor_enabled = true
+ user.otp_secret = User.generate_otp_secret(32)
+ user.otp_grace_period_started_at = Time.now
+ user.generate_otp_backup_codes!
+ end
+ end
+
+ factory :omniauth_user do
+ transient do
+ extern_uid '123456'
+ provider 'ldapmain'
+ end
+
+ after(:create) do |user, evaluator|
+ user.identities << create(
+ :identity,
+ provider: evaluator.provider,
+ extern_uid: evaluator.extern_uid
+ )
+ end
+ end
+
+ factory :admin, traits: [:admin]
+ end
+end
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 72764b1629d..2e9851fb442 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -1,69 +1,98 @@
require 'spec_helper'
-describe "Admin Builds" do
- let(:commit) { FactoryGirl.create :ci_commit }
- let(:build) { FactoryGirl.create :ci_build, commit: commit }
-
+describe 'Admin Builds' do
before do
login_as :admin
end
- describe "GET /admin/builds" do
- before do
- build
- visit admin_builds_path
- end
-
- it { expect(page).to have_content "Running" }
- it { expect(page).to have_content build.short_sha }
- end
+ describe 'GET /admin/builds' do
+ let(:commit) { create(:ci_commit) }
- describe "Tabs" do
- it "shows all builds" do
- FactoryGirl.create :ci_build, commit: commit, status: "pending"
- FactoryGirl.create :ci_build, commit: commit, status: "running"
- FactoryGirl.create :ci_build, commit: commit, status: "success"
- FactoryGirl.create :ci_build, commit: commit, status: "failed"
+ context 'All tab' do
+ context 'when have builds' do
+ it 'shows all builds' do
+ create(:ci_build, commit: commit, status: :pending)
+ create(:ci_build, commit: commit, status: :running)
+ create(:ci_build, commit: commit, status: :success)
+ create(:ci_build, commit: commit, status: :failed)
- visit admin_builds_path
+ visit admin_builds_path
- within ".center-top-menu" do
- click_on "All"
+ expect(page).to have_selector('.nav-links li.active', text: 'All')
+ expect(page.all('.build-link').size).to eq(4)
+ expect(page).to have_link 'Cancel all'
+ end
end
- expect(page.all(".build-link").size).to eq(4)
+ context 'when have no builds' do
+ it 'shows a message' do
+ visit admin_builds_path
+
+ expect(page).to have_selector('.nav-links li.active', text: 'All')
+ expect(page).to have_content 'No builds to show'
+ expect(page).not_to have_link 'Cancel all'
+ end
+ end
end
- it "shows finished builds" do
- build = FactoryGirl.create :ci_build, commit: commit, status: "pending"
- build1 = FactoryGirl.create :ci_build, commit: commit, status: "running"
- build2 = FactoryGirl.create :ci_build, commit: commit, status: "success"
+ context 'Running tab' do
+ context 'when have running builds' do
+ it 'shows running builds' do
+ build1 = create(:ci_build, commit: commit, status: :pending)
+ build2 = create(:ci_build, commit: commit, status: :success)
+ build3 = create(:ci_build, commit: commit, status: :failed)
+
+ visit admin_builds_path(scope: :running)
+
+ expect(page).to have_selector('.nav-links li.active', text: 'Running')
+ expect(page.find('.build-link')).to have_content(build1.id)
+ expect(page.find('.build-link')).not_to have_content(build2.id)
+ expect(page.find('.build-link')).not_to have_content(build3.id)
+ expect(page).to have_link 'Cancel all'
+ end
+ end
- visit admin_builds_path
+ context 'when have no builds running' do
+ it 'shows a message' do
+ create(:ci_build, commit: commit, status: :success)
- within ".center-top-menu" do
- click_on "Finished"
- end
+ visit admin_builds_path(scope: :running)
- expect(page.find(".build-link")).not_to have_content(build.id)
- expect(page.find(".build-link")).not_to have_content(build1.id)
- expect(page.find(".build-link")).to have_content(build2.id)
+ expect(page).to have_selector('.nav-links li.active', text: 'Running')
+ expect(page).to have_content 'No builds to show'
+ expect(page).not_to have_link 'Cancel all'
+ end
+ end
end
- it "shows running builds" do
- build = FactoryGirl.create :ci_build, commit: commit, status: "pending"
- build2 = FactoryGirl.create :ci_build, commit: commit, status: "success"
- build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed"
+ context 'Finished tab' do
+ context 'when have finished builds' do
+ it 'shows finished builds' do
+ build1 = create(:ci_build, commit: commit, status: :pending)
+ build2 = create(:ci_build, commit: commit, status: :running)
+ build3 = create(:ci_build, commit: commit, status: :success)
+
+ visit admin_builds_path(scope: :finished)
+
+ expect(page).to have_selector('.nav-links li.active', text: 'Finished')
+ expect(page.find('.build-link')).not_to have_content(build1.id)
+ expect(page.find('.build-link')).not_to have_content(build2.id)
+ expect(page.find('.build-link')).to have_content(build3.id)
+ expect(page).to have_link 'Cancel all'
+ end
+ end
- visit admin_builds_path
+ context 'when have no builds finished' do
+ it 'shows a message' do
+ create(:ci_build, commit: commit, status: :running)
- within ".center-top-menu" do
- click_on "Running"
- end
+ visit admin_builds_path(scope: :finished)
- expect(page.find(".build-link")).to have_content(build.id)
- expect(page.find(".build-link")).not_to have_content(build2.id)
- expect(page.find(".build-link")).not_to have_content(build3.id)
+ expect(page).to have_selector('.nav-links li.active', text: 'Finished')
+ expect(page).to have_content 'No builds to show'
+ expect(page).to have_link 'Cancel all'
+ end
+ end
end
end
end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index f0031a0a247..6da3a857b3f 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -8,18 +8,18 @@ describe "Builds" do
@commit = FactoryGirl.create :ci_commit
@build = FactoryGirl.create :ci_build, commit: @commit
@project = @commit.project
- @project.team << [@user, :master]
+ @project.team << [@user, :developer]
end
describe "GET /:project/builds" do
context "Running scope" do
before do
@build.run!
- visit namespace_project_builds_path(@project.namespace, @project)
+ visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
end
- it { expect(page).to have_content 'Running' }
- it { expect(page).to have_content 'Cancel running' }
+ it { expect(page).to have_selector('.nav-links li.active', text: 'Running') }
+ it { expect(page).to have_link 'Cancel running' }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
@@ -31,21 +31,22 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
end
+ it { expect(page).to have_selector('.nav-links li.active', text: 'Finished') }
it { expect(page).to have_content 'No builds to show' }
- it { expect(page).to have_content 'Cancel running' }
+ it { expect(page).to have_link 'Cancel running' }
end
context "All builds" do
before do
@project.builds.running_or_pending.each(&:success)
- visit namespace_project_builds_path(@project.namespace, @project, scope: :all)
+ visit namespace_project_builds_path(@project.namespace, @project)
end
- it { expect(page).to have_content 'All' }
+ it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
- it { expect(page).to_not have_content 'Cancel running' }
+ it { expect(page).to_not have_link 'Cancel running' }
end
end
@@ -56,8 +57,12 @@ describe "Builds" do
click_link "Cancel running"
end
- it { expect(page).to have_content 'No builds to show' }
- it { expect(page).to_not have_content 'Cancel running' }
+ it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
+ it { expect(page).to have_content 'canceled' }
+ it { expect(page).to have_content @build.short_sha }
+ it { expect(page).to have_content @build.ref }
+ it { expect(page).to have_content @build.name }
+ it { expect(page).to_not have_link 'Cancel running' }
end
describe "GET /:project/builds/:id" do
@@ -75,7 +80,11 @@ describe "Builds" do
visit namespace_project_build_path(@project.namespace, @project, @build)
end
- it { expect(page).to have_content 'Download artifacts' }
+ it 'has button to download artifacts' do
+ page.within('.artifacts') do
+ expect(page).to have_content 'Download'
+ end
+ end
end
end
@@ -106,7 +115,7 @@ describe "Builds" do
before do
@build.update_attributes(artifacts_file: artifacts_file)
visit namespace_project_build_path(@project.namespace, @project, @build)
- click_link 'Download artifacts'
+ page.within('.artifacts') { click_link 'Download' }
end
it { expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type) }
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
index e6e73e5e67c..30e29d9d552 100644
--- a/spec/features/ci_lint_spec.rb
+++ b/spec/features/ci_lint_spec.rb
@@ -35,5 +35,13 @@ describe 'CI Lint' do
expect(page).to have_content('Error: Please provide content of .gitlab-ci.yml')
end
end
+
+ describe 'YAML revalidate' do
+ let(:yaml_content) { 'my yaml content' }
+
+ it 'loads previous YAML content after validation' do
+ expect(page).to have_field('content', with: 'my yaml content', type: 'textarea')
+ end
+ end
end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index fe7f07f5b75..dacaa96d760 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -8,7 +8,6 @@ describe 'Commits' do
describe 'CI' do
before do
login_as :user
- project.team << [@user, :master]
stub_ci_commit_to_return_yaml_file
end
@@ -16,83 +15,149 @@ describe 'Commits' do
FactoryGirl.create :ci_commit, project: project, sha: project.commit.sha
end
- let!(:build) { FactoryGirl.create :ci_build, commit: commit }
+ context 'commit status is Generic Commit Status' do
+ let!(:status) { FactoryGirl.create :generic_commit_status, commit: commit }
- describe 'Project commits' do
before do
- visit namespace_project_commits_path(project.namespace, project, :master)
+ project.team << [@user, :reporter]
end
- it 'should show build status' do
- page.within("//li[@id='commit-#{commit.short_sha}']") do
- expect(page).to have_css(".ci-status-link")
+ describe 'Commit builds' do
+ before do
+ visit ci_status_path(commit)
end
- end
- end
- describe 'Commit builds' do
- before do
- visit ci_status_path(commit)
- end
+ it { expect(page).to have_content commit.sha[0..7] }
- it { expect(page).to have_content commit.sha[0..7] }
- it { expect(page).to have_content commit.git_commit_message }
- it { expect(page).to have_content commit.git_author_name }
+ it 'contains generic commit status build' do
+ page.within('.table-holder') do
+ expect(page).to have_content "##{status.id}" # build id
+ expect(page).to have_content 'generic' # build name
+ end
+ end
+ end
end
- context 'Download artifacts' do
+ context 'commit status is Ci Build' do
+ let!(:build) { FactoryGirl.create :ci_build, commit: commit }
let(:artifacts_file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
- before do
- build.update_attributes(artifacts_file: artifacts_file)
- end
+ context 'when logged as developer' do
+ before do
+ project.team << [@user, :developer]
+ end
- it do
- visit ci_status_path(commit)
- click_on 'Download artifacts'
- expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
- end
- end
+ describe 'Project commits' do
+ before do
+ visit namespace_project_commits_path(project.namespace, project, :master)
+ end
- describe 'Cancel all builds' do
- it 'cancels commit' do
- visit ci_status_path(commit)
- click_on 'Cancel running'
- expect(page).to have_content 'canceled'
- end
- end
+ it 'should show build status' do
+ page.within("//li[@id='commit-#{commit.short_sha}']") do
+ expect(page).to have_css(".ci-status-link")
+ end
+ end
+ end
+
+ describe 'Commit builds' do
+ before do
+ visit ci_status_path(commit)
+ end
+
+ it { expect(page).to have_content commit.sha[0..7] }
+ it { expect(page).to have_content commit.git_commit_message }
+ it { expect(page).to have_content commit.git_author_name }
+ end
- describe 'Cancel build' do
- it 'cancels build' do
- visit ci_status_path(commit)
- click_on 'Cancel'
- expect(page).to have_content 'canceled'
+ context 'Download artifacts' do
+ before do
+ build.update_attributes(artifacts_file: artifacts_file)
+ end
+
+ it do
+ visit ci_status_path(commit)
+ click_on 'Download artifacts'
+ expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
+ end
+ end
+
+ describe 'Cancel all builds' do
+ it 'cancels commit' do
+ visit ci_status_path(commit)
+ click_on 'Cancel running'
+ expect(page).to have_content 'canceled'
+ end
+ end
+
+ describe 'Cancel build' do
+ it 'cancels build' do
+ visit ci_status_path(commit)
+ click_on 'Cancel'
+ expect(page).to have_content 'canceled'
+ end
+ end
+
+ describe '.gitlab-ci.yml not found warning' do
+ context 'ci builds enabled' do
+ it "does not show warning" do
+ visit ci_status_path(commit)
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
+
+ it 'shows warning' do
+ stub_ci_commit_yaml_file(nil)
+ visit ci_status_path(commit)
+ expect(page).to have_content '.gitlab-ci.yml not found in this commit'
+ end
+ end
+
+ context 'ci builds disabled' do
+ before do
+ stub_ci_builds_disabled
+ stub_ci_commit_yaml_file(nil)
+ visit ci_status_path(commit)
+ end
+
+ it 'does not show warning' do
+ expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ end
+ end
+ end
end
- end
- describe '.gitlab-ci.yml not found warning' do
- context 'ci builds enabled' do
- it "does not show warning" do
+ context "when logged as reporter" do
+ before do
+ project.team << [@user, :reporter]
+ build.update_attributes(artifacts_file: artifacts_file)
visit ci_status_path(commit)
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
end
- it 'shows warning' do
- stub_ci_commit_yaml_file(nil)
- visit ci_status_path(commit)
- expect(page).to have_content '.gitlab-ci.yml not found in this commit'
+ it do
+ expect(page).to have_content commit.sha[0..7]
+ expect(page).to have_content commit.git_commit_message
+ expect(page).to have_content commit.git_author_name
+ expect(page).to have_link('Download artifacts')
+ expect(page).to_not have_link('Cancel running')
+ expect(page).to_not have_link('Retry failed')
end
end
- context 'ci builds disabled' do
+ context 'when accessing internal project with disallowed access' do
before do
- stub_ci_builds_disabled
- stub_ci_commit_yaml_file(nil)
+ project.update(
+ visibility_level: Gitlab::VisibilityLevel::INTERNAL,
+ public_builds: false)
+ build.update_attributes(artifacts_file: artifacts_file)
visit ci_status_path(commit)
end
- it 'does not show warning' do
- expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
+ it do
+ expect(page).to have_content commit.sha[0..7]
+ expect(page).to have_content commit.git_commit_message
+ expect(page).to have_content commit.git_author_name
+ expect(page).to_not have_link('Download artifacts')
+ expect(page).to_not have_link('Cancel running')
+ expect(page).to_not have_link('Retry failed')
end
end
end
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index 38c8d343ce3..f6e33f651c4 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
feature 'Issue filtering by Milestone', feature: true do
- include Select2Helper
-
let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: project) }
@@ -13,7 +11,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(Milestone::None.title)
- expect(page).to have_css('.title', count: 1)
+ expect(page).to have_css('.issue .title', count: 1)
end
scenario 'filters by a specific Milestone', js: true do
@@ -23,7 +21,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(milestone.title)
- expect(page).to have_css('.title', count: 1)
+ expect(page).to have_css('.issue .title', count: 1)
end
def visit_issues(project)
@@ -31,6 +29,9 @@ feature 'Issue filtering by Milestone', feature: true do
end
def filter_by_milestone(title)
- select2(title, from: '#milestone_title')
+ find(".js-milestone-select").click
+ sleep 0.5
+ find(".milestone-filter a", text: title).click
+ sleep 1
end
end
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
new file mode 100644
index 00000000000..9219b767547
--- /dev/null
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+feature 'Start new branch from an issue', feature: true do
+ let!(:project) { create(:project) }
+ let!(:issue) { create(:issue, project: project) }
+ let!(:user) { create(:user)}
+
+ context "for team members" do
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ it 'shown the new branch button', js: false do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+
+ expect(page).to have_link "New Branch"
+ end
+
+ context "when there is a referenced merge request" do
+ let(:note) do
+ create(:note, :on_issue, :system, project: project,
+ note: "mentioned in !#{referenced_mr.iid}")
+ end
+ let(:referenced_mr) do
+ create(:merge_request, :simple, source_project: project, target_project: project,
+ description: "Fixes ##{issue.iid}", author: user)
+ end
+
+ before do
+ issue.notes << note
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it "hides the new branch button", js: true do
+ expect(page).not_to have_link "New Branch"
+ expect(page).to have_content /1 Related Merge Request/
+ end
+ end
+ end
+
+ context "for visiters" do
+ it 'no button is shown', js: false do
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ expect(page).not_to have_link "New Branch"
+ end
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index a2fb3e4c75d..e844e681ebf 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -127,15 +127,15 @@ describe 'Issues', feature: true do
it 'sorts by newest' do
visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_created)
- expect(first_issue).to include('foo')
- expect(last_issue).to include('baz')
+ expect(first_issue).to include('baz')
+ expect(last_issue).to include('foo')
end
it 'sorts by oldest' do
visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_created)
- expect(first_issue).to include('baz')
- expect(last_issue).to include('foo')
+ expect(first_issue).to include('foo')
+ expect(last_issue).to include('baz')
end
it 'sorts by most recently updated' do
@@ -190,8 +190,8 @@ describe 'Issues', feature: true do
sort: sort_value_oldest_created,
assignee_id: user2.id)
- expect(first_issue).to include('bar')
- expect(last_issue).to include('foo')
+ expect(first_issue).to include('foo')
+ expect(last_issue).to include('bar')
expect(page).not_to have_content 'baz'
end
end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 2451e56fe7c..4433ef2d6f1 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -1,6 +1,32 @@
require 'spec_helper'
feature 'Login', feature: true do
+ describe 'initial login after setup' do
+ it 'allows the initial admin to create a password' do
+ # This behavior is dependent on there only being one user
+ User.delete_all
+
+ user = create(:admin, password_automatically_set: true)
+
+ visit root_path
+ expect(current_path).to eq edit_user_password_path
+ expect(page).to have_content('Please create a password for your new account.')
+
+ fill_in 'user_password', with: 'password'
+ fill_in 'user_password_confirmation', with: 'password'
+ click_button 'Change your password'
+
+ expect(current_path).to eq new_user_session_path
+ expect(page).to have_content(I18n.t('devise.passwords.updated_not_active'))
+
+ fill_in 'user_login', with: user.username
+ fill_in 'user_password', with: 'password'
+ click_button 'Sign in'
+
+ expect(current_path).to eq root_path
+ end
+ end
+
describe 'with two-factor authentication' do
context 'with valid username/password' do
let(:user) { create(:user, :two_factor) }
@@ -112,10 +138,10 @@ feature 'Login', feature: true do
context 'within the grace period' do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must configure Two-Factor Authentication in your account until')
+ expect(page).to have_content('You must enable Two-factor Authentication for your account before')
end
- it 'two-factor configuration is skippable' do
+ it 'disallows skipping two-factor configuration' do
expect(current_path).to eq new_profile_two_factor_auth_path
click_link 'Configure it later'
@@ -128,10 +154,10 @@ feature 'Login', feature: true do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ expect(page).to have_content('You must enable Two-factor Authentication for your account.')
end
- it 'two-factor configuration is not skippable' do
+ it 'disallows skipping two-factor configuration' do
expect(current_path).to eq new_profile_two_factor_auth_path
expect(page).not_to have_link('Configure it later')
end
@@ -146,7 +172,7 @@ feature 'Login', feature: true do
it 'redirects to two-factor configuration page' do
expect(current_path).to eq new_profile_two_factor_auth_path
- expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ expect(page).to have_content('You must enable Two-factor Authentication for your account.')
end
end
end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index fdd8cf07b12..12fd8d37210 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -175,13 +175,15 @@ describe 'GitLab Markdown', feature: true do
end
end
- context 'default pipeline' do
- before(:all) do
- @feat = MarkdownFeature.new
+ before(:all) do
+ @feat = MarkdownFeature.new
- # `markdown` helper expects a `@project` variable
- @project = @feat.project
+ # `markdown` helper expects a `@project` variable
+ @project = @feat.project
+ end
+ context 'default pipeline' do
+ before(:all) do
@html = markdown(@feat.raw_markdown)
end
@@ -212,6 +214,7 @@ describe 'GitLab Markdown', feature: true do
expect(doc).to reference_commit_ranges
expect(doc).to reference_commits
expect(doc).to reference_labels
+ expect(doc).to reference_milestones
end
end
@@ -220,6 +223,57 @@ describe 'GitLab Markdown', feature: true do
end
end
+ context 'wiki pipeline' do
+ before do
+ @project_wiki = @feat.project_wiki
+
+ file = Gollum::File.new(@project_wiki.wiki)
+ expect(file).to receive(:path).and_return('images/example.jpg')
+ expect(@project_wiki).to receive(:find_file).with('images/example.jpg').and_return(file)
+
+ @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki })
+ end
+
+ it_behaves_like 'all pipelines'
+
+ it 'includes RelativeLinkFilter' do
+ expect(doc).not_to parse_relative_links
+ end
+
+ it 'includes EmojiFilter' do
+ expect(doc).to parse_emoji
+ end
+
+ it 'includes TableOfContentsFilter' do
+ expect(doc).to create_header_links
+ end
+
+ it 'includes AutolinkFilter' do
+ expect(doc).to create_autolinks
+ end
+
+ it 'includes all reference filters' do
+ aggregate_failures do
+ expect(doc).to reference_users
+ expect(doc).to reference_issues
+ expect(doc).to reference_merge_requests
+ expect(doc).to reference_snippets
+ expect(doc).to reference_commit_ranges
+ expect(doc).to reference_commits
+ expect(doc).to reference_labels
+ expect(doc).to reference_milestones
+ end
+ end
+
+ it 'includes TaskListFilter' do
+ expect(doc).to parse_task_lists
+ end
+
+ it 'includes GollumTagsFilter' do
+ expect(doc).to parse_gollum_tags
+ end
+ end
+
# Fake a `current_user` helper
def current_user
@feat.user
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index f70214e1122..b76e4c74c79 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
feature 'Merge Request filtering by Milestone', feature: true do
- include Select2Helper
-
let(:project) { create(:project, :public) }
let(:milestone) { create(:milestone, project: project) }
@@ -31,6 +29,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
end
def filter_by_milestone(title)
- select2(title, from: '#milestone_title')
+ find(".js-milestone-select").click
+ find(".milestone-filter a", text: title).click
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index f0fc6916c4d..d9a8058efd9 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -22,7 +22,7 @@ describe 'Comments', feature: true do
it 'should be valid' do
is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
expect(find('.js-main-target-form input[type=submit]').value).
- to eq('Add Comment')
+ to eq('Comment')
page.within('.js-main-target-form') do
expect(page).not_to have_link('Cancel')
end
@@ -49,7 +49,7 @@ describe 'Comments', feature: true do
page.within('.js-main-target-form') do
fill_in 'note[note]', with: 'This is awsome!'
find('.js-md-preview-button').click
- click_button 'Add Comment'
+ click_button 'Comment'
end
end
@@ -167,7 +167,7 @@ describe 'Comments', feature: true do
end
it 'should be removed when canceled' do
- page.within(".diff-file form[rel$='#{line_code}']") do
+ page.within(".diff-file form[id$='#{line_code}']") do
find('.js-close-discussion-note-form').trigger('click')
end
@@ -202,7 +202,7 @@ describe 'Comments', feature: true do
before do
page.within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
fill_in 'note[note]', with: 'Another comment on line 10'
- click_button('Add Comment')
+ click_button('Comment')
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 74b148f5d17..ed97b6cb577 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -80,8 +80,29 @@ feature 'Project', feature: true do
visit namespace_project_path(project.namespace, project)
end
- it { expect(page).to have_content('You have Master access to this project.') }
- it { expect(page).to have_link('Leave this project') }
+ it 'click project-settings and find leave project' do
+ find('#project-settings-button').click
+ expect(page).to have_link('Leave Project')
+ end
+ end
+
+ describe 'project title' do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ login_with(user)
+ project.team.add_user(user, Gitlab::Access::MASTER)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ it 'click toggle and show dropdown', js: true do
+ find('.js-projects-dropdown-toggle').click
+ wait_for_ajax
+ expect(page).to have_css('.select2-results li', count: 1)
+ end
end
def remove_with_confirm(button_text, confirm_with)
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index d97831aae14..e8886e7edf9 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -17,10 +17,10 @@ describe "Runners" do
@project3 = FactoryGirl.create :empty_project
@project3.team << [user, :developer]
- @shared_runner = FactoryGirl.create :ci_shared_runner
- @specific_runner = FactoryGirl.create :ci_specific_runner
- @specific_runner2 = FactoryGirl.create :ci_specific_runner
- @specific_runner3 = FactoryGirl.create :ci_specific_runner
+ @shared_runner = FactoryGirl.create :ci_runner, :shared
+ @specific_runner = FactoryGirl.create :ci_runner
+ @specific_runner2 = FactoryGirl.create :ci_runner
+ @specific_runner3 = FactoryGirl.create :ci_runner
@project.runners << @specific_runner
@project2.runners << @specific_runner2
@project3.runners << @specific_runner3
@@ -84,7 +84,7 @@ describe "Runners" do
before do
@project = FactoryGirl.create :empty_project
@project.team << [user, :master]
- @specific_runner = FactoryGirl.create :ci_specific_runner
+ @specific_runner = FactoryGirl.create :ci_runner
@project.runners << @specific_runner
end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 57563add74c..f88c591d897 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -8,10 +8,12 @@ describe "Internal Project Access", feature: true do
let(:master) { create(:user) }
let(:guest) { create(:user) }
let(:reporter) { create(:user) }
+ let(:external_team_member) { create(:user, external: true) }
before do
# full access
project.team << [master, :master]
+ project.team << [external_team_member, :master]
# readonly
project.team << [reporter, :reporter]
@@ -34,6 +36,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -45,6 +49,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -56,6 +62,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -67,6 +75,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -78,6 +88,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -89,22 +101,23 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/blob" do
- before do
- commit = project.repository.commit
- path = '.gitignore'
- @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path))
- end
+ let(:commit) { project.repository.commit }
+ subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
- it { expect(@blob_path).to be_allowed_for master }
- it { expect(@blob_path).to be_allowed_for reporter }
- it { expect(@blob_path).to be_allowed_for :admin }
- it { expect(@blob_path).to be_allowed_for guest }
- it { expect(@blob_path).to be_allowed_for :user }
- it { expect(@blob_path).to be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/edit" do
@@ -115,6 +128,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -126,6 +141,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -137,6 +154,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -149,6 +168,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -160,6 +181,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -171,6 +194,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -182,6 +207,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -193,6 +220,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -209,6 +238,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -225,6 +256,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -236,6 +269,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index a1e111c6cab..19f287ce7a4 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -8,10 +8,12 @@ describe "Private Project Access", feature: true do
let(:master) { create(:user) }
let(:guest) { create(:user) }
let(:reporter) { create(:user) }
+ let(:external_team_member) { create(:user, external: true) }
before do
# full access
project.team << [master, :master]
+ project.team << [external_team_member, :master]
# readonly
project.team << [reporter, :reporter]
@@ -34,6 +36,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -45,6 +49,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -56,6 +62,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -67,6 +75,7 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -78,6 +87,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -89,22 +100,23 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/blob" do
- before do
- commit = project.repository.commit
- path = '.gitignore'
- @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path))
- end
+ let(:commit) { project.repository.commit }
+ subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))}
- it { expect(@blob_path).to be_allowed_for master }
- it { expect(@blob_path).to be_allowed_for reporter }
- it { expect(@blob_path).to be_allowed_for :admin }
- it { expect(@blob_path).to be_denied_for guest }
- it { expect(@blob_path).to be_denied_for :user }
- it { expect(@blob_path).to be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/edit" do
@@ -115,6 +127,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -126,6 +140,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -137,6 +153,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -149,6 +167,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -160,6 +180,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -171,6 +193,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -187,6 +211,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -203,6 +229,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
@@ -214,6 +242,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_allowed_for external_team_member }
it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 655d2c8b7d9..4e135076367 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -38,6 +38,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -49,6 +50,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -60,6 +62,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -71,6 +74,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -82,6 +86,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -93,22 +98,79 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
- describe "GET /:project_path/blob" do
- before do
- commit = project.repository.commit
- path = '.gitignore'
- @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path))
+ describe "GET /:project_path/builds" do
+ subject { namespace_project_builds_path(project.namespace, project) }
+
+ context "when allowed for public" do
+ before { project.update(public_builds: true) }
+
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ context "when disallowed for public" do
+ before { project.update(public_builds: false) }
+
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
+ describe "GET /:project_path/builds/:id" do
+ let(:commit) { create(:ci_commit, project: project) }
+ let(:build) { create(:ci_build, commit: commit) }
+ subject { namespace_project_build_path(project.namespace, project, build.id) }
+
+ context "when allowed for public" do
+ before { project.update(public_builds: true) }
+
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
end
- it { expect(@blob_path).to be_allowed_for master }
- it { expect(@blob_path).to be_allowed_for reporter }
- it { expect(@blob_path).to be_allowed_for :admin }
- it { expect(@blob_path).to be_allowed_for guest }
- it { expect(@blob_path).to be_allowed_for :user }
- it { expect(@blob_path).to be_allowed_for :visitor }
+ context "when disallowed for public" do
+ before { project.update(public_builds: false) }
+
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
+ describe "GET /:project_path/blob" do
+ let(:commit) { project.repository.commit }
+
+ subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
+
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/edit" do
@@ -119,6 +181,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -130,6 +193,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -141,6 +205,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -153,6 +218,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -164,6 +230,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -175,6 +242,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -186,6 +254,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -197,6 +266,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
@@ -213,6 +283,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -229,6 +300,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
@@ -240,6 +312,7 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
deleted file mode 100644
index 4f6a000822e..00000000000
--- a/spec/finders/groups_finder_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'spec_helper'
-
-describe GroupsFinder do
- describe '#execute' do
- let(:user) { create(:user) }
-
- let(:group1) { create(:group) }
- let(:group2) { create(:group) }
- let(:group3) { create(:group) }
- let(:group4) { create(:group, public: true) }
-
- let!(:public_project) { create(:project, :public, group: group1) }
- let!(:internal_project) { create(:project, :internal, group: group2) }
- let!(:private_project) { create(:project, :private, group: group3) }
-
- let(:finder) { described_class.new }
-
- describe 'with a user' do
- subject { finder.execute(user) }
-
- describe 'when the user is not a member of any groups' do
- it { is_expected.to eq([group4, group2, group1]) }
- end
-
- describe 'when the user is a member of a group' do
- before do
- group3.add_user(user, Gitlab::Access::DEVELOPER)
- end
-
- it { is_expected.to eq([group4, group3, group2, group1]) }
- end
-
- describe 'when the user is a member of a private project' do
- before do
- private_project.team.add_user(user, Gitlab::Access::DEVELOPER)
- end
-
- it { is_expected.to eq([group4, group3, group2, group1]) }
- end
- end
-
- describe 'without a user' do
- subject { finder.execute }
-
- it { is_expected.to eq([group4, group1]) }
- end
- end
-end
diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb
deleted file mode 100644
index 2d9068cc720..00000000000
--- a/spec/finders/joined_groups_finder_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'spec_helper'
-
-describe JoinedGroupsFinder do
- describe '#execute' do
- let(:source_user) { create(:user) }
- let(:current_user) { create(:user) }
-
- let(:group1) { create(:group) }
- let(:group2) { create(:group) }
- let(:group3) { create(:group) }
- let(:group4) { create(:group, public: true) }
-
- let!(:public_project) { create(:project, :public, group: group1) }
- let!(:internal_project) { create(:project, :internal, group: group2) }
- let!(:private_project) { create(:project, :private, group: group3) }
-
- let(:finder) { described_class.new(source_user) }
-
- before do
- [group1, group2, group3, group4].each do |group|
- group.add_user(source_user, Gitlab::Access::MASTER)
- end
- end
-
- describe 'with a current user' do
- describe 'when the current user has access to the projects of the source user' do
- before do
- private_project.team.add_user(current_user, Gitlab::Access::DEVELOPER)
- end
-
- subject { finder.execute(current_user) }
-
- it { is_expected.to eq([group4, group3, group2, group1]) }
- end
-
- describe 'when the current user does not have access to the projects of the source user' do
- subject { finder.execute(current_user) }
-
- it { is_expected.to eq([group4, group2, group1]) }
- end
- end
-
- describe 'without a current user' do
- subject { finder.execute }
-
- it { is_expected.to eq([group4, group1]) }
- end
- end
-end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index f32641ef0f6..fae0da9d898 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -17,6 +17,10 @@ describe ProjectsFinder do
create(:project, :public, group: group, name: 'C', path: 'C')
end
+ let!(:shared_project) do
+ create(:project, :private, name: 'D', path: 'D')
+ end
+
let(:finder) { described_class.new }
describe 'without a group' do
@@ -56,7 +60,35 @@ describe ProjectsFinder do
describe 'with a user' do
subject { finder.execute(user, group: group) }
- it { is_expected.to eq([public_project, internal_project]) }
+ describe 'without shared projects' do
+ it { is_expected.to eq([public_project, internal_project]) }
+ end
+
+ describe 'with shared projects and group membership' do
+ before do
+ group.add_user(user, Gitlab::Access::DEVELOPER)
+
+ shared_project.project_group_links.
+ create(group_access: Gitlab::Access::MASTER, group: group)
+ end
+
+ it do
+ is_expected.to eq([shared_project, public_project, internal_project])
+ end
+ end
+
+ describe 'with shared projects and project membership' do
+ before do
+ shared_project.team.add_user(user, Gitlab::Access::DEVELOPER)
+
+ shared_project.project_group_links.
+ create(group_access: Gitlab::Access::MASTER, group: group)
+ end
+
+ it do
+ is_expected.to eq([shared_project, public_project, internal_project])
+ end
+ end
end
end
end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 1b4ffc2d717..7fdc5e5d7aa 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -5,15 +5,14 @@ describe SnippetsFinder do
let(:user1) { create :user }
let(:group) { create :group }
- let(:project1) { create(:empty_project, :public, group: group) }
- let(:project2) { create(:empty_project, :private, group: group) }
-
+ let(:project1) { create(:empty_project, :public, group: group) }
+ let(:project2) { create(:empty_project, :private, group: group) }
context ':all filter' do
before do
- @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE)
- @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL)
- @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC)
+ @snippet1 = create(:personal_snippet, :private)
+ @snippet2 = create(:personal_snippet, :internal)
+ @snippet3 = create(:personal_snippet, :public)
end
it "returns all private and internal snippets" do
@@ -31,9 +30,9 @@ describe SnippetsFinder do
context ':by_user filter' do
before do
- @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user)
- @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user)
- @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user)
+ @snippet1 = create(:personal_snippet, :private, author: user)
+ @snippet2 = create(:personal_snippet, :internal, author: user)
+ @snippet3 = create(:personal_snippet, :public, author: user)
end
it "returns all public and internal snippets" do
@@ -75,9 +74,9 @@ describe SnippetsFinder do
context 'by_project filter' do
before do
- @snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1)
- @snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1)
- @snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1)
+ @snippet1 = create(:project_snippet, :private, project: project1)
+ @snippet2 = create(:project_snippet, :internal, project: project1)
+ @snippet3 = create(:project_snippet, :public, project: project1)
end
it "returns public snippets for unauthorized user" do
@@ -93,7 +92,7 @@ describe SnippetsFinder do
end
it "returns all snippets for project members" do
- project1.team << [user, :developer]
+ project1.team << [user, :developer]
snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
expect(snippets).to include(@snippet1, @snippet2, @snippet3)
end
diff --git a/spec/fixtures/ci_build_artifacts.zip b/spec/fixtures/ci_build_artifacts.zip
new file mode 100644
index 00000000000..dae976d918e
--- /dev/null
+++ b/spec/fixtures/ci_build_artifacts.zip
Binary files differ
diff --git a/spec/fixtures/ci_build_artifacts_metadata.gz b/spec/fixtures/ci_build_artifacts_metadata.gz
new file mode 100644
index 00000000000..e6d17e4595d
--- /dev/null
+++ b/spec/fixtures/ci_build_artifacts_metadata.gz
Binary files differ
diff --git a/spec/fixtures/logo_sample.svg b/spec/fixtures/logo_sample.svg
new file mode 100644
index 00000000000..883e7e6cf92
--- /dev/null
+++ b/spec/fixtures/logo_sample.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
+ <title>Slice 1</title>
+ <desc>Created with Sketch.</desc>
+ <script>alert('FAIL')</script>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
+ <g id="Page-1" sketch:type="MSShapeGroup">
+ <g id="Fill-1-+-Group-24">
+ <g id="Group-24">
+ <g id="Group">
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/spec/fixtures/mail_room_disabled.yml b/spec/fixtures/mail_room_disabled.yml
new file mode 100644
index 00000000000..97f8cff051f
--- /dev/null
+++ b/spec/fixtures/mail_room_disabled.yml
@@ -0,0 +1,11 @@
+test:
+ incoming_email:
+ enabled: false
+ address: "gitlab-incoming+%{key}@gmail.com"
+ user: "gitlab-incoming@gmail.com"
+ password: "[REDACTED]"
+ host: "imap.gmail.com"
+ port: 993
+ ssl: true
+ start_tls: false
+ mailbox: "inbox"
diff --git a/spec/fixtures/mail_room_enabled.yml b/spec/fixtures/mail_room_enabled.yml
new file mode 100644
index 00000000000..9c94649244d
--- /dev/null
+++ b/spec/fixtures/mail_room_enabled.yml
@@ -0,0 +1,11 @@
+test:
+ incoming_email:
+ enabled: true
+ address: "gitlab-incoming+%{key}@gmail.com"
+ user: "gitlab-incoming@gmail.com"
+ password: "[REDACTED]"
+ host: "imap.gmail.com"
+ port: 993
+ ssl: true
+ start_tls: false
+ mailbox: "inbox"
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index e8dfc5c0eb1..1772cc3f6a4 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -209,11 +209,18 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Label by ID: <%= simple_label.to_reference %>
- Label by name: <%= Label.reference_prefix %><%= simple_label.name %>
-- Label by name in quotes: <%= label.to_reference(:name) %>
+- Label by name in quotes: <%= label.to_reference(format: :name) %>
- Ignored in code: `<%= simple_label.to_reference %>`
- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
- Link to label by reference: [Label](<%= label.to_reference %>)
+#### MilestoneReferenceFilter
+
+- Milestone: <%= milestone.to_reference %>
+- Milestone in another project: <%= xmilestone.to_reference(project) %>
+- Ignored in code: `<%= milestone.to_reference %>`
+- Link to milestone by URL: [Milestone](<%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %>)
+
### Task Lists
- [ ] Incomplete task 1
@@ -223,3 +230,12 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- [ ] Incomplete sub-task 2
- [x] Complete sub-task 1
- [X] Complete task 2
+
+#### Gollum Tags
+
+- [[linked-resource]]
+- [[link-text|linked-resource]]
+- [[http://example.com]]
+- [[link-text|http://example.com/pdfs/gollum.pdf]]
+- [[images/example.jpg]]
+- [[http://example.com/images/example.jpg]]
diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml
new file mode 100644
index 00000000000..a8b7907d4ba
--- /dev/null
+++ b/spec/fixtures/parallel_diff_result.yml
@@ -0,0 +1,324 @@
+---
+- :left:
+ :type: match
+ :number: 6
+ :text: "@@ -6,12 +6,18 @@ module Popen"
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :right:
+ :type: match
+ :number: 6
+ :text: "@@ -6,12 +6,18 @@ module Popen"
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+- :left:
+ :type:
+ :number: 6
+ :text: |2
+ <span id="LC6" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :right:
+ :type:
+ :number: 6
+ :text: |2
+ <span id="LC6" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+- :left:
+ :type:
+ :number: 7
+ :text: |2
+ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
+ :right:
+ :type:
+ :number: 7
+ :text: |2
+ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
+- :left:
+ :type:
+ :number: 8
+ :text: |2
+ <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
+ :right:
+ :type:
+ :number: 8
+ :text: |2
+ <span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
+- :left:
+ :type: old
+ :number: 9
+ :text: |
+ -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9
+ :right:
+ :type: new
+ :number: 9
+ :text: |
+ +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
+- :left:
+ :type:
+ :number: 10
+ :text: |2
+ <span id="LC10" class="line"> <span class="k">end</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
+ :right:
+ :type:
+ :number: 10
+ :text: |2
+ <span id="LC10" class="line"> <span class="k">end</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
+- :left:
+ :type:
+ :number: 11
+ :text: |2
+ <span id="LC11" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
+ :right:
+ :type:
+ :number: 11
+ :text: |2
+ <span id="LC11" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
+- :left:
+ :type:
+ :number: 12
+ :text: |2
+ <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
+ :right:
+ :type:
+ :number: 12
+ :text: |2
+ <span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
+- :left:
+ :type: old
+ :number: 13
+ :text: |
+ -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&quot;PWD&quot;</span> <span class="o">=&gt;</span> <span class="n">path</span> <span class="p">}</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13
+ :right:
+ :type: old
+ :number:
+ :text: ''
+ :line_code:
+- :left:
+ :type: old
+ :number: 14
+ :text: |
+ -<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13
+ :right:
+ :type: new
+ :number: 13
+ :text: |
+ +<span id="LC13" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13
+- :left:
+ :type:
+ :number:
+ :text: ''
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
+ :right:
+ :type: new
+ :number: 14
+ :text: |
+ +<span id="LC14" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
+- :left:
+ :type:
+ :number:
+ :text: ''
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
+ :right:
+ :type: new
+ :number: 15
+ :text: |
+ +<span id="LC15" class="line"> <span class="s2">&quot;PWD&quot;</span> <span class="o">=&gt;</span> <span class="n">path</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
+- :left:
+ :type:
+ :number:
+ :text: ''
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
+ :right:
+ :type: new
+ :number: 16
+ :text: |
+ +<span id="LC16" class="line"> <span class="p">}</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16
+- :left:
+ :type:
+ :number:
+ :text: ''
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
+ :right:
+ :type: new
+ :number: 17
+ :text: |
+ +<span id="LC17" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17
+- :left:
+ :type:
+ :number:
+ :text: ''
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
+ :right:
+ :type: new
+ :number: 18
+ :text: |
+ +<span id="LC18" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18
+- :left:
+ :type:
+ :number:
+ :text: ''
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
+ :right:
+ :type: new
+ :number: 19
+ :text: |
+ +<span id="LC19" class="line"> <span class="ss">chdir: </span><span class="n">path</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19
+- :left:
+ :type:
+ :number:
+ :text: ''
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
+ :right:
+ :type: new
+ :number: 20
+ :text: |
+ +<span id="LC20" class="line"> <span class="p">}</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20
+- :left:
+ :type:
+ :number: 15
+ :text: |2
+ <span id="LC21" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
+ :right:
+ :type:
+ :number: 21
+ :text: |2
+ <span id="LC21" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21
+- :left:
+ :type:
+ :number: 16
+ :text: |2
+ <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
+ :right:
+ :type:
+ :number: 22
+ :text: |2
+ <span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22
+- :left:
+ :type:
+ :number: 17
+ :text: |2
+ <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
+ :right:
+ :type:
+ :number: 23
+ :text: |2
+ <span id="LC23" class="line"> <span class="no">FileUtils</span><span class="p">.</span><span class="nf">mkdir_p</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23
+- :left:
+ :type: match
+ :number: 19
+ :text: "@@ -19,6 +25,7 @@ module Popen"
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+ :right:
+ :type: match
+ :number: 25
+ :text: "@@ -19,6 +25,7 @@ module Popen"
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+- :left:
+ :type:
+ :number: 19
+ :text: |2
+ <span id="LC25" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+ :right:
+ :type:
+ :number: 25
+ :text: |2
+ <span id="LC25" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25
+- :left:
+ :type:
+ :number: 20
+ :text: |2
+ <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
+ :right:
+ :type:
+ :number: 26
+ :text: |2
+ <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
+- :left:
+ :type:
+ :number: 21
+ :text: |2
+ <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
+ :right:
+ :type:
+ :number: 27
+ :text: |2
+ <span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
+- :left:
+ :type:
+ :number:
+ :text: ''
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
+ :right:
+ :type: new
+ :number: 28
+ :text: |
+ +<span id="LC28" class="line"></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
+- :left:
+ :type:
+ :number: 22
+ :text: |2
+ <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
+ :right:
+ :type:
+ :number: 29
+ :text: |2
+ <span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
+- :left:
+ :type:
+ :number: 23
+ :text: |2
+ <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
+ :right:
+ :type:
+ :number: 30
+ :text: |2
+ <span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stdout</span><span class="p">.</span><span class="nf">read</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30
+- :left:
+ :type:
+ :number: 24
+ :text: |2
+ <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
+ :right:
+ :type:
+ :number: 31
+ :text: |2
+ <span id="LC31" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</span> <span class="n">stderr</span><span class="p">.</span><span class="nf">read</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 68527c3a4f8..f6c1005d265 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -240,7 +240,7 @@ describe ApplicationHelper do
describe 'time_ago_with_tooltip' do
def element(*arguments)
Time.zone = 'UTC'
- time = Time.zone.parse('2015-07-02 08:00')
+ time = Time.zone.parse('2015-07-02 08:23')
element = helper.time_ago_with_tooltip(time, *arguments)
Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
@@ -251,15 +251,15 @@ describe ApplicationHelper do
end
it 'includes the date string' do
- expect(element.text).to eq '2015-07-02 08:00:00 UTC'
+ expect(element.text).to eq '2015-07-02 08:23:00 UTC'
end
it 'has a datetime attribute' do
- expect(element.attr('datetime')).to eq '2015-07-02T08:00:00Z'
+ expect(element.attr('datetime')).to eq '2015-07-02T08:23:00Z'
end
it 'has a formatted title attribute' do
- expect(element.attr('title')).to eq 'Jul 02, 2015 8:00am'
+ expect(element.attr('title')).to eq 'Jul 2, 2015 8:23am'
end
it 'includes a default js-timeago class' do
@@ -285,10 +285,18 @@ describe ApplicationHelper do
it 'allows the script tag to be excluded' do
expect(element(skip_js: true)).not_to include 'script'
end
+
+ it 'converts to Time' do
+ expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error
+ end
end
describe 'render_markup' do
let(:content) { 'Noël' }
+ let(:user) { create(:user) }
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
it 'should preserve encoding' do
expect(content.encoding.name).to eq('UTF-8')
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index b8bba36439a..87849230dbe 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -1,22 +1,22 @@
require 'spec_helper'
describe BlobHelper do
- describe 'highlight' do
- let(:blob_name) { 'test.lisp' }
- let(:no_context_content) { ":type \"assem\"))" }
- let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
- let(:split_content) { blob_content.split("\n") }
- let(:multiline_content) do
- %q(
- def test(input):
- """This is line 1 of a multi-line comment.
- This is line 2.
- """
- )
- end
+ let(:blob_name) { 'test.lisp' }
+ let(:no_context_content) { ":type \"assem\"))" }
+ let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
+ let(:split_content) { blob_content.split("\n") }
+ let(:multiline_content) do
+ %q(
+ def test(input):
+ """This is line 1 of a multi-line comment.
+ This is line 2.
+ """
+ )
+ end
+ describe '#highlight' do
it 'should return plaintext for unknown lexer context' do
- result = highlight(blob_name, no_context_content, nowrap: true, continue: false)
+ result = helper.highlight(blob_name, no_context_content, nowrap: true)
expect(result).to eq('<span id="LC1" class="line">:type &quot;assem&quot;))</span>')
end
@@ -24,28 +24,17 @@ describe BlobHelper do
expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>]
- expect(highlight(blob_name, blob_content, nowrap: true, continue: false)).to eq(expected)
- end
-
- it 'should highlight continued blocks' do
- # Both lines have LC1 as ID since formatter doesn't support continue at the moment
- expected = [
- '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>',
- '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>'
- ]
-
- result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) }
- expect(result).to eq(expected)
+ expect(helper.highlight(blob_name, blob_content, nowrap: true)).to eq(expected)
end
it 'should highlight multi-line comments' do
- result = highlight(blob_name, multiline_content, nowrap: true, continue: false)
+ result = helper.highlight(blob_name, multiline_content, nowrap: true)
html = Nokogiri::HTML(result)
lines = html.search('.s')
expect(lines.count).to eq(3)
expect(lines[0].text).to eq('"""This is line 1 of a multi-line comment.')
- expect(lines[1].text).to eq(' This is line 2.')
- expect(lines[2].text).to eq(' """')
+ expect(lines[1].text).to eq(' This is line 2.')
+ expect(lines[2].text).to eq(' """')
end
context 'diff highlighting' do
@@ -59,9 +48,23 @@ describe BlobHelper do
end
it 'should highlight each line properly' do
- result = highlight(blob_name, blob_content, nowrap: true, continue: false)
+ result = helper.highlight(blob_name, blob_content, nowrap: true)
expect(result).to eq(expected)
end
end
end
+
+ describe "#highlighter" do
+ it 'should highlight continued blocks' do
+ # Both lines have LC1 as ID since formatter doesn't support continue at the moment
+ expected = [
+ '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>',
+ '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>'
+ ]
+
+ highlighter = helper.highlighter(blob_name, blob_content, nowrap: true)
+ result = split_content.map{ |content| highlighter.highlight(content) }
+ expect(result).to eq(expected)
+ end
+ end
end
diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb
index c7c6f45d144..157cc4665a2 100644
--- a/spec/helpers/broadcast_messages_helper_spec.rb
+++ b/spec/helpers/broadcast_messages_helper_spec.rb
@@ -1,22 +1,60 @@
require 'spec_helper'
describe BroadcastMessagesHelper do
- describe 'broadcast_styling' do
- let(:broadcast_message) { double(color: '', font: '') }
+ describe 'broadcast_message' do
+ it 'returns nil when no current message' do
+ expect(helper.broadcast_message(nil)).to be_nil
+ end
+
+ it 'includes the current message' do
+ current = double(message: 'Current Message')
+
+ allow(helper).to receive(:broadcast_message_style).and_return(nil)
+
+ expect(helper.broadcast_message(current)).to include 'Current Message'
+ end
+
+ it 'includes custom style' do
+ current = double(message: 'Current Message')
+
+ allow(helper).to receive(:broadcast_message_style).and_return('foo')
+
+ expect(helper.broadcast_message(current)).to include 'style="foo"'
+ end
+ end
+
+ describe 'broadcast_message_style' do
+ it 'defaults to no style' do
+ broadcast_message = spy
+
+ expect(helper.broadcast_message_style(broadcast_message)).to eq ''
+ end
+
+ it 'allows custom style' do
+ broadcast_message = double(color: '#f2dede', font: '#b94a48')
+
+ expect(helper.broadcast_message_style(broadcast_message)).
+ to match('background-color: #f2dede; color: #b94a48')
+ end
+ end
+
+ describe 'broadcast_message_status' do
+ it 'returns Active' do
+ message = build(:broadcast_message)
+
+ expect(helper.broadcast_message_status(message)).to eq 'Active'
+ end
+
+ it 'returns Expired' do
+ message = build(:broadcast_message, :expired)
- context "default style" do
- it "should have no style" do
- expect(broadcast_styling(broadcast_message)).to eq ''
- end
+ expect(helper.broadcast_message_status(message)).to eq 'Expired'
end
- context "customized style" do
- let(:broadcast_message) { double(color: "#f2dede", font: '#b94a48') }
+ it 'returns Pending' do
+ message = build(:broadcast_message, :future)
- it "should have a customized style" do
- expect(broadcast_styling(broadcast_message)).
- to match('background-color: #f2dede; color: #b94a48')
- end
+ expect(helper.broadcast_message_status(message)).to eq 'Pending'
end
end
end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 7c96a74e581..982c113e84b 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -4,10 +4,12 @@ describe DiffHelper do
include RepoHelpers
let(:project) { create(:project) }
+ let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
let(:diffs) { commit.diffs }
let(:diff) { diffs.first }
- let(:diff_file) { Gitlab::Diff::File.new(diff) }
+ let(:diff_refs) { [commit.parent, commit] }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
describe 'diff_hard_limit_enabled?' do
it 'should return true if param is provided' do
@@ -20,79 +22,14 @@ describe DiffHelper do
end
end
- describe 'allowed_diff_size' do
+ describe 'diff_options' do
it 'should return hard limit for a diff if force diff is true' do
allow(controller).to receive(:params) { { force_show_diff: true } }
- expect(allowed_diff_size).to eq(1000)
+ expect(diff_options).to include(Commit.max_diff_options)
end
it 'should return safe limit for a diff if force diff is false' do
- expect(allowed_diff_size).to eq(100)
- end
- end
-
- describe 'allowed_diff_lines' do
- it 'should return hard limit for number of lines in a diff if force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- expect(allowed_diff_lines).to eq(50000)
- end
-
- it 'should return safe limit for numbers of lines a diff if force diff is false' do
- expect(allowed_diff_lines).to eq(5000)
- end
- end
-
- describe 'safe_diff_files' do
- it 'should return all files from a commit that is smaller than safe limits' do
- expect(safe_diff_files(diffs).length).to eq(2)
- end
-
- it 'should return only the first file if the diff line count in the 2nd file takes the total beyond safe limits' do
- allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
- expect(safe_diff_files(diffs).length).to eq(1)
- end
-
- it 'should return all files from a commit that is beyond safe limit for numbers of lines if force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
- expect(safe_diff_files(diffs).length).to eq(2)
- end
-
- it 'should return only the first file if the diff line count in the 2nd file takes the total beyond hard limits' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- allow(diffs[1].diff).to receive(:lines).and_return([""] * 49999) #simulate 49999 lines
- expect(safe_diff_files(diffs).length).to eq(1)
- end
-
- it 'should return only a safe number of file diffs if a commit touches more files than the safe limits' do
- large_diffs = diffs * 100 #simulate 200 diffs
- expect(safe_diff_files(large_diffs).length).to eq(100)
- end
-
- it 'should return all file diffs if a commit touches more files than the safe limits but force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- large_diffs = diffs * 100 #simulate 200 diffs
- expect(safe_diff_files(large_diffs).length).to eq(200)
- end
-
- it 'should return a limited file diffs if a commit touches more files than the hard limits and force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- very_large_diffs = diffs * 1000 #simulate 2000 diffs
- expect(safe_diff_files(very_large_diffs).length).to eq(1000)
- end
- end
-
- describe 'parallel_diff' do
- it 'should return an array of arrays containing the parsed diff' do
- expect(parallel_diff(diff_file, 0)).
- to match_array(parallel_diff_result_array)
- end
- end
-
- describe 'generate_line_code' do
- it 'should generate correct line code' do
- expect(generate_line_code(diff_file.file_path, diff_file.diff_lines.first)).
- to eq('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6')
+ expect(diff_options).not_to include(:max_lines, :max_files)
end
end
@@ -116,8 +53,7 @@ describe DiffHelper do
end
end
- describe 'diff_line_content' do
-
+ describe '#diff_line_content' do
it 'should return non breaking space when line is empty' do
expect(diff_line_content(nil)).to eq(' &nbsp;')
end
@@ -126,39 +62,21 @@ describe DiffHelper do
expect(diff_line_content(diff_file.diff_lines.first.text)).
to eq('@@ -6,12 +6,18 @@ module Popen')
expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match')
- expect(diff_line_content(diff_file.diff_lines.first.new_pos)).to eq(6)
+ expect(diff_file.diff_lines.first.new_pos).to eq(6)
end
end
- def parallel_diff_result_array
- [
- ["match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", "match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"],
- [nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"], [nil, 7, " def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7", nil, 7, " def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"],
- [nil, 8, " unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8", nil, 8, " unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"],
- ["old", 9, "- raise <span class='idiff'></span>&quot;System commands must be given as an array of strings&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9", "new", 9, "+ raise <span class='idiff'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"],
- [nil, 10, " end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10", nil, 10, " end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"],
- [nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11", nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11"],
- [nil, 12, " path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12", nil, 12, " path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12"],
- ["old", 13, "- vars = { &quot;PWD&quot; =&gt; path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13", "old", nil, "&nbsp;", nil],
- ["old", 14, "- options = { chdir: path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13", "new", 13, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13"],
- [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14", "new", 14, "+ vars = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14"],
- [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15", "new", 15, "+ &quot;PWD&quot; =&gt; path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"],
- [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16", "new", 16, "+ }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16"],
- [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17", "new", 17, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17"],
- [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18", "new", 18, "+ options = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18"],
- [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19", "new", 19, "+ chdir: path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19"],
- [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20", "new", 20, "+ }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20"],
- [nil, 15, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21", nil, 21, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21"],
- [nil, 16, " unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22", nil, 22, " unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22"],
- [nil, 17, " FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23", nil, 23, " FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23"],
- ["match", 19, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", "match", 25, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"],
- [nil, 19, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", nil, 25, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"],
- [nil, 20, " @cmd_output = &quot;&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26", nil, 26, " @cmd_output = &quot;&quot;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26"],
- [nil, 21, " @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27", nil, 27, " @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27"],
- [nil, nil, "&nbsp;", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28", "new", 28, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28"],
- [nil, 22, " Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29", nil, 29, " Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29"],
- [nil, 23, " @cmd_output &lt;&lt; stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30", nil, 30, " @cmd_output &lt;&lt; stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30"],
- [nil, 24, " @cmd_output &lt;&lt; stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31", nil, 31, " @cmd_output &lt;&lt; stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31"]
- ]
+ describe "#mark_inline_diffs" do
+ let(:old_line) { %{abc 'def'} }
+ let(:new_line) { %{abc "def"} }
+
+ it "returns strings with marked inline diffs" do
+ marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
+
+ expect(marked_old_line).to eq("abc <span class='idiff left right'>&#39;def&#39;</span>")
+ expect(marked_old_line).to be_html_safe
+ expect(marked_new_line).to eq("abc <span class='idiff left right'>&quot;def&quot;</span>")
+ expect(marked_new_line).to be_html_safe
+ end
end
end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 762ec25c4f5..9adcd916ced 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -113,7 +113,7 @@ describe GitlabMarkdownHelper do
it 'should replace commit message with emoji to link' do
actual = link_to_gfm(':book:Book', '/foo')
expect(actual).
- to eq %Q(<img class="emoji" title=":book:" alt=":book:" src="http://localhost/assets/emoji/1F4D6.png" height="20" width="20" align="absmiddle"><a href="/foo">Book</a>)
+ to eq %Q(<img class="emoji" title=":book:" alt=":book:" src="http://localhost/assets/1F4D6.png" height="20" width="20" align="absmiddle"><a href="/foo">Book</a>)
end
end
@@ -121,12 +121,13 @@ describe GitlabMarkdownHelper do
before do
@wiki = double('WikiPage')
allow(@wiki).to receive(:content).and_return('wiki content')
+ helper.instance_variable_set(:@project_wiki, @wiki)
end
- it "should use GitLab Flavored Markdown for markdown files" do
+ it "should use Wiki pipeline for markdown files" do
allow(@wiki).to receive(:format).and_return(:markdown)
- expect(helper).to receive(:markdown).with('wiki content')
+ expect(helper).to receive(:markdown).with('wiki content', pipeline: :wiki, project_wiki: @wiki)
helper.render_wiki_content(@wiki)
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 0c8d06b7059..4f129eca183 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe LabelsHelper do
describe 'link_to_label' do
let(:project) { create(:empty_project) }
- let(:label) { create(:label, project: project) }
+ let(:label) { create(:label, project: project) }
context 'with @project set' do
before do
@@ -11,34 +11,31 @@ describe LabelsHelper do
end
it 'uses the instance variable' do
- expect(label).not_to receive(:project)
- link_to_label(label)
+ expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
end
end
context 'without @project set' do
it "uses the label's project" do
- expect(label).to receive(:project).and_return(project)
- link_to_label(label)
+ expect(link_to_label(label)).to match %r{<a href="/#{label.project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
end
end
- context 'with a named project argument' do
- it 'uses the provided project' do
- arg = double('project')
- expect(arg).to receive(:namespace).and_return('foo')
- expect(arg).to receive(:to_param).and_return('foo')
+ context 'with a project argument' do
+ let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') }
- link_to_label(label, project: arg)
+ it 'links to merge requests page' do
+ expect(link_to_label(label, project: another_project)).to match %r{<a href="/foo3/bar3/issues\?label_name=#{label.name}">.*</a>}
end
+ end
- it 'takes precedence over other types' do
- @project = project
- expect(@project).not_to receive(:namespace)
- expect(label).not_to receive(:project)
-
- arg = double('project', namespace: 'foo', to_param: 'foo')
- link_to_label(label, project: arg)
+ context 'with a type argument' do
+ ['issue', :issue, 'merge_request', :merge_request].each do |type|
+ context "set to #{type}" do
+ it 'links to correct page' do
+ expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.to_reference}/#{type.to_s.pluralize}\?label_name=#{label.name}">.*</a>}
+ end
+ end
end
end
@@ -66,5 +63,10 @@ describe LabelsHelper do
it 'uses dark text on light backgrounds' do
expect(text_color_for_bg('#EEEEEE')).to eq('#333333')
end
+
+ it 'supports RGB triplets' do
+ expect(text_color_for_bg('#FFF')).to eq '#333333'
+ expect(text_color_for_bg('#000')).to eq '#FFFFFF'
+ end
end
end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index fd7107779f6..cf632f594c7 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -2,10 +2,8 @@ require 'rails_helper'
describe PageLayoutHelper do
describe 'page_description' do
- it 'defaults to value returned by page_description_default helper' do
- allow(helper).to receive(:page_description_default).and_return('Foo')
-
- expect(helper.page_description).to eq 'Foo'
+ it 'defaults to nil' do
+ expect(helper.page_description).to eq nil
end
it 'returns the last-pushed description' do
@@ -42,58 +40,32 @@ describe PageLayoutHelper do
end
end
- describe 'page_description_default' do
- it 'uses Project description when available' do
- project = double(description: 'Project Description')
- helper.instance_variable_set(:@project, project)
-
- expect(helper.page_description_default).to eq 'Project Description'
- end
-
- it 'uses brand_title when Project description is nil' do
- project = double(description: nil)
- helper.instance_variable_set(:@project, project)
-
- expect(helper).to receive(:brand_title).and_return('Brand Title')
- expect(helper.page_description_default).to eq 'Brand Title'
- end
-
- it 'falls back to brand_title' do
- allow(helper).to receive(:brand_title).and_return('Brand Title')
-
- expect(helper.page_description_default).to eq 'Brand Title'
- end
- end
-
describe 'page_image' do
it 'defaults to the GitLab logo' do
expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
end
- context 'with @project' do
- it 'uses Project avatar if available' do
- project = double(avatar_url: 'http://example.com/uploads/avatar.png')
- helper.instance_variable_set(:@project, project)
+ %w(project user group).each do |type|
+ context "with @#{type} assigned" do
+ it "uses #{type.titlecase} avatar if available" do
+ object = double(avatar_url: 'http://example.com/uploads/avatar.png')
+ assign(type, object)
- expect(helper.page_image).to eq project.avatar_url
- end
+ expect(helper.page_image).to eq object.avatar_url
+ end
- it 'falls back to the default' do
- project = double(avatar_url: nil)
- helper.instance_variable_set(:@project, project)
+ it 'falls back to the default when avatar_url is nil' do
+ object = double(avatar_url: nil)
+ assign(type, object)
- expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+ expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+ end
end
- end
-
- context 'with @user' do
- it 'delegates to avatar_icon helper' do
- user = double('User')
- helper.instance_variable_set(:@user, user)
-
- expect(helper).to receive(:avatar_icon).with(user)
- helper.page_image
+ context "with no assignments" do
+ it 'falls back to the default' do
+ expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+ end
end
end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index ebe9c29d91c..601b6915e27 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -42,9 +42,9 @@ describe SearchHelper do
expect(search_autocomplete_opts(project.name).size).to eq(1)
end
- it "includes the public group" do
- group = create(:group, public: true)
- expect(search_autocomplete_opts(group.name).size).to eq(1)
+ it "should not include the public group" do
+ group = create(:group)
+ expect(search_autocomplete_opts(group.name).size).to eq(0)
end
context "with a current project" do
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index aafc24397a9..cd7596a763d 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -58,7 +58,7 @@ describe VisibilityLevelHelper do
describe "skip_level?" do
describe "forks" do
- let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:project) { create(:project, :internal) }
let(:fork_project) { create(:forked_project_with_submodules) }
before do
@@ -74,7 +74,7 @@ describe VisibilityLevelHelper do
end
describe "non-forked project" do
- let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:project) { create(:project, :internal) }
it "skips levels" do
expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
@@ -84,7 +84,7 @@ describe VisibilityLevelHelper do
end
describe "Snippet" do
- let(:snippet) { create(:snippet, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:snippet) { create(:snippet, :internal) }
it "skips levels" do
expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb
new file mode 100644
index 00000000000..e58f2c80e95
--- /dev/null
+++ b/spec/initializers/settings_spec.rb
@@ -0,0 +1,44 @@
+require_relative '../../config/initializers/1_settings'
+
+describe Settings, lib: true do
+
+ describe '#host_without_www' do
+ context 'URL with protocol' do
+ it 'returns the host' do
+ expect(Settings.host_without_www('http://foo.com')).to eq 'foo.com'
+ expect(Settings.host_without_www('http://www.foo.com')).to eq 'foo.com'
+ expect(Settings.host_without_www('http://secure.foo.com')).to eq 'secure.foo.com'
+ expect(Settings.host_without_www('http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
+
+ expect(Settings.host_without_www('https://foo.com')).to eq 'foo.com'
+ expect(Settings.host_without_www('https://www.foo.com')).to eq 'foo.com'
+ expect(Settings.host_without_www('https://secure.foo.com')).to eq 'secure.foo.com'
+ expect(Settings.host_without_www('https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'secure.gravatar.com'
+ end
+ end
+
+ context 'URL without protocol' do
+ it 'returns the host' do
+ expect(Settings.host_without_www('foo.com')).to eq 'foo.com'
+ expect(Settings.host_without_www('www.foo.com')).to eq 'foo.com'
+ expect(Settings.host_without_www('secure.foo.com')).to eq 'secure.foo.com'
+ expect(Settings.host_without_www('www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
+ end
+
+ context 'URL with user/port' do
+ it 'returns the host' do
+ expect(Settings.host_without_www('bob:pass@foo.com:8080')).to eq 'foo.com'
+ expect(Settings.host_without_www('bob:pass@www.foo.com:8080')).to eq 'foo.com'
+ expect(Settings.host_without_www('bob:pass@secure.foo.com:8080')).to eq 'secure.foo.com'
+ expect(Settings.host_without_www('bob:pass@www.gravatar.com:8080/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
+
+ expect(Settings.host_without_www('http://bob:pass@foo.com:8080')).to eq 'foo.com'
+ expect(Settings.host_without_www('http://bob:pass@www.foo.com:8080')).to eq 'foo.com'
+ expect(Settings.host_without_www('http://bob:pass@secure.foo.com:8080')).to eq 'secure.foo.com'
+ expect(Settings.host_without_www('http://bob:pass@www.gravatar.com:8080/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
+ end
+ end
+ end
+ end
+
+end
diff --git a/spec/javascripts/behaviors/autosize_spec.js.coffee b/spec/javascripts/behaviors/autosize_spec.js.coffee
new file mode 100644
index 00000000000..7fc1d19c35f
--- /dev/null
+++ b/spec/javascripts/behaviors/autosize_spec.js.coffee
@@ -0,0 +1,11 @@
+#= require behaviors/autosize
+
+describe 'Autosize behavior', ->
+ beforeEach ->
+ fixture.set('<textarea class="js-autosize" style="resize: vertical"></textarea>')
+
+ it 'does not overwrite the resize property', ->
+ load()
+ expect($('textarea')).toHaveCss(resize: 'vertical')
+
+ load = -> $(document).trigger('page:load')
diff --git a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
index b80a28a33ea..e3788bee813 100644
--- a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
+++ b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
@@ -1,6 +1,6 @@
-%form{ action: '/foo' }
- %input.js-quick-submit{ type: 'text' }
- %textarea.js-quick-submit
+%form.js-quick-submit{ action: '/foo' }
+ %input{ type: 'text' }
+ %textarea
%input{ type: 'submit'} Submit
%button.btn{ type: 'submit' } Submit
diff --git a/spec/javascripts/fixtures/project_title.html.haml b/spec/javascripts/fixtures/project_title.html.haml
new file mode 100644
index 00000000000..e5850b62659
--- /dev/null
+++ b/spec/javascripts/fixtures/project_title.html.haml
@@ -0,0 +1,7 @@
+%h1.title
+ %a
+ GitLab Org
+ %a.project-item-select-holder{href: "/gitlab-org/gitlab-test"}
+ GitLab Test
+ %input#project_path.project-item-select.js-projects-dropdown.ajax-project-select{type: "hidden", name: "project_path", "data-include-groups" => "false"}
+ %i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle
diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json
new file mode 100644
index 00000000000..84e8d0ba1e4
--- /dev/null
+++ b/spec/javascripts/fixtures/projects.json
@@ -0,0 +1 @@
+[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/h5bp/html5-boilerplate","name":"Html5 Boilerplate","name_with_namespace":"H5bp / Html5 Boilerplate","path":"html5-boilerplate","path_with_namespace":"h5bp/html5-boilerplate","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/twitter/typeahead-js","name":"Typeahead.Js","name_with_namespace":"Twitter / Typeahead.Js","path":"typeahead-js","path_with_namespace":"twitter/typeahead-js","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/twitter/flight","name":"Flight","name_with_namespace":"Twitter / Flight","path":"flight","path_with_namespace":"twitter/flight","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}]
diff --git a/spec/javascripts/fixtures/zen_mode.html.haml b/spec/javascripts/fixtures/zen_mode.html.haml
index e867e4de2b9..1701652c61e 100644
--- a/spec/javascripts/fixtures/zen_mode.html.haml
+++ b/spec/javascripts/fixtures/zen_mode.html.haml
@@ -1,9 +1,8 @@
.zennable
- %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
.zen-backdrop
- %textarea#note_note.js-gfm-input.markdown-area{placeholder: 'Leave a comment'}
- %a.zen-enter-link{tabindex: '-1'}
+ %textarea#note_note.js-gfm-input.markdown-area
+ %a.js-zen-enter(tabindex="-1" href="#")
%i.fa.fa-expand
- Edit in fullscreen
- %a.zen-leave-link
+ Edit in fullscreen
+ %a.js-zen-leave(tabindex="-1" href="#")
%i.fa.fa-compress
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index 7e67c778861..86ba9dd8e96 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -26,10 +26,10 @@ describe 'reopen/close issue', ->
fixture.load('issues_show.html')
@issue = new Issue()
it 'closes an issue', ->
- $.ajax = (obj) ->
- expect(obj.type).toBe('PUT')
- expect(obj.url).toBe('http://gitlab.com/issues/6/close')
- obj.success saved: true
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PUT')
+ expect(req.url).toBe('http://gitlab.com/issues/6/close')
+ req.success saved: true
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
@@ -44,12 +44,12 @@ describe 'reopen/close issue', ->
expect($('div.status-box-closed')).toBeVisible()
expect($('div.status-box-open')).toBeHidden()
- it 'fails to closes an issue with success:false', ->
+ it 'fails to close an issue with success:false', ->
- $.ajax = (obj) ->
- expect(obj.type).toBe('PUT')
- expect(obj.url).toBe('http://goesnowhere.nothing/whereami')
- obj.success saved: false
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PUT')
+ expect(req.url).toBe('http://goesnowhere.nothing/whereami')
+ req.success saved: false
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
@@ -69,10 +69,10 @@ describe 'reopen/close issue', ->
it 'fails to closes an issue with HTTP error', ->
- $.ajax = (obj) ->
- expect(obj.type).toBe('PUT')
- expect(obj.url).toBe('http://goesnowhere.nothing/whereami')
- obj.error()
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PUT')
+ expect(req.url).toBe('http://goesnowhere.nothing/whereami')
+ req.error()
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
@@ -91,10 +91,10 @@ describe 'reopen/close issue', ->
expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.')
it 'reopens an issue', ->
- $.ajax = (obj) ->
- expect(obj.type).toBe('PUT')
- expect(obj.url).toBe('http://gitlab.com/issues/6/reopen')
- obj.success saved: true
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PUT')
+ expect(req.url).toBe('http://gitlab.com/issues/6/reopen')
+ req.success saved: true
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee
new file mode 100644
index 00000000000..47c7b7febe3
--- /dev/null
+++ b/spec/javascripts/project_title_spec.js.coffee
@@ -0,0 +1,46 @@
+#= require select2
+#= require api
+#= require project_select
+#= require project
+
+window.gon = {}
+window.gon.api_version = 'v3'
+
+describe 'Project Title', ->
+ fixture.preload('project_title.html')
+ fixture.preload('projects.json')
+
+ beforeEach ->
+ fixture.load('project_title.html')
+ @project = new Project()
+
+ spyOn(@project, 'changeProject').and.callFake (url) ->
+ window.current_project_url = url
+
+ describe 'project list', ->
+ beforeEach =>
+ @projects_data = fixture.load('projects.json')[0]
+
+ spyOn(jQuery, 'ajax').and.callFake (req) =>
+ expect(req.url).toBe('/api/v3/projects.json')
+ d = $.Deferred()
+ d.resolve @projects_data
+ d.promise()
+
+ it 'to show on toggle click', =>
+ $('.js-projects-dropdown-toggle').click()
+
+ expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(true)
+ expect($('.ajax-project-dropdown li').length).toBe(@projects_data.length)
+
+ it 'hide dropdown', ->
+ $("#select2-drop-mask").click()
+
+ expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
+
+ it 'change project when clicking item', ->
+ $('.js-projects-dropdown-toggle').click()
+ $('.ajax-project-dropdown li:nth-child(2)').trigger('mouseup')
+
+ expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
+ expect(window.current_project_url).toBe('http://localhost:3000/h5bp/html5-boilerplate')
diff --git a/spec/javascripts/zen_mode_spec.js.coffee b/spec/javascripts/zen_mode_spec.js.coffee
index 4cb3836755f..b790fce01ed 100644
--- a/spec/javascripts/zen_mode_spec.js.coffee
+++ b/spec/javascripts/zen_mode_spec.js.coffee
@@ -15,14 +15,6 @@ describe 'ZenMode', ->
# Set this manually because we can't actually scroll the window
@zen.scroll_position = 456
- # Ohmmmmmmm
- enterZen = ->
- $('.zen-toggle-comment').prop('checked', true).trigger('change')
-
- # Wh- what was that?!
- exitZen = ->
- $('.zen-toggle-comment').prop('checked', false).trigger('change')
-
describe 'on enter', ->
it 'pauses Mousetrap', ->
spyOn(Mousetrap, 'pause')
@@ -35,16 +27,14 @@ describe 'ZenMode', ->
expect('textarea').not.toHaveAttr('style')
describe 'in use', ->
- beforeEach ->
- enterZen()
+ beforeEach -> enterZen()
it 'exits on Escape', ->
- $(document).trigger(jQuery.Event('keydown', {keyCode: 27}))
- expect($('.zen-toggle-comment').prop('checked')).toBe(false)
+ escapeKeydown()
+ expect($('.zen-backdrop')).not.toHaveClass('fullscreen')
describe 'on exit', ->
- beforeEach ->
- enterZen()
+ beforeEach -> enterZen()
it 'unpauses Mousetrap', ->
spyOn(Mousetrap, 'unpause')
@@ -52,6 +42,10 @@ describe 'ZenMode', ->
expect(Mousetrap.unpause).toHaveBeenCalled()
it 'restores the scroll position', ->
- spyOn(@zen, 'restoreScroll')
+ spyOn(@zen, 'scrollTo')
exitZen()
- expect(@zen.restoreScroll).toHaveBeenCalledWith(456)
+ expect(@zen.scrollTo).toHaveBeenCalled()
+
+enterZen = -> $('a.js-zen-enter').click() # Ohmmmmmmm
+exitZen = -> $('a.js-zen-leave').click()
+escapeKeydown = -> $('textarea').trigger($.Event('keydown', {keyCode: 27}))
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index 473534ba68a..63a32d9d455 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -21,7 +21,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
let(:reference) { commit.id }
# Let's test a variety of commit SHA sizes just to be paranoid
- [6, 8, 12, 18, 20, 32, 40].each do |size|
+ [7, 8, 12, 18, 20, 32, 40].each do |size|
it "links to a valid reference of #{size} characters" do
doc = reference_filter("See #{reference[0...size]}")
@@ -35,7 +35,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
doc = reference_filter("See #{commit.id}")
expect(doc.text).to eq "See #{commit.short_id}"
- doc = reference_filter("See #{commit.id[0...6]}")
+ doc = reference_filter("See #{commit.id[0...7]}")
expect(doc.text).to eq "See #{commit.short_id}"
end
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index cf314058158..b5b38cf0c8c 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -14,7 +14,7 @@ describe Banzai::Filter::EmojiFilter, lib: true do
it 'replaces supported emoji' do
doc = filter('<p>:heart:</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/2764.png'
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png'
end
it 'ignores unsupported emoji' do
@@ -25,7 +25,7 @@ describe Banzai::Filter::EmojiFilter, lib: true do
it 'correctly encodes the URL' do
doc = filter('<p>:+1:</p>')
- expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/emoji/1F44D.png'
+ expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png'
end
it 'matches at the start of a string' do
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
new file mode 100644
index 00000000000..5e23c5c319a
--- /dev/null
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Banzai::Filter::GollumTagsFilter, lib: true do
+ include FilterSpecHelper
+
+ let(:project) { create(:project) }
+ let(:user) { double }
+ let(:project_wiki) { ProjectWiki.new(project, user) }
+
+ describe 'validation' do
+ it 'ensure that a :project_wiki key exists in context' do
+ expect { filter("See [[images/image.jpg]]", {}) }.to raise_error ArgumentError, "Missing context keys for Banzai::Filter::GollumTagsFilter: :project_wiki"
+ end
+ end
+
+ context 'linking internal images' do
+ it 'creates img tag if image exists' do
+ file = Gollum::File.new(project_wiki.wiki)
+ expect(file).to receive(:path).and_return('images/image.jpg')
+ expect(project_wiki).to receive(:find_file).with('images/image.jpg').and_return(file)
+
+ tag = '[[images/image.jpg]]'
+ doc = filter("See #{tag}", project_wiki: project_wiki)
+
+ expect(doc.at_css('img')['src']).to eq "#{project_wiki.wiki_base_path}/images/image.jpg"
+ end
+
+ it 'does not creates img tag if image does not exist' do
+ expect(project_wiki).to receive(:find_file).with('images/image.jpg').and_return(nil)
+
+ tag = '[[images/image.jpg]]'
+ doc = filter("See #{tag}", project_wiki: project_wiki)
+
+ expect(doc.css('img').size).to eq 0
+ end
+ end
+
+ context 'linking external images' do
+ it 'creates img tag for valid URL' do
+ tag = '[[http://example.com/image.jpg]]'
+ doc = filter("See #{tag}", project_wiki: project_wiki)
+
+ expect(doc.at_css('img')['src']).to eq "http://example.com/image.jpg"
+ end
+
+ it 'does not creates img tag for invalid URL' do
+ tag = '[[http://example.com/image.pdf]]'
+ doc = filter("See #{tag}", project_wiki: project_wiki)
+
+ expect(doc.css('img').size).to eq 0
+ end
+ end
+
+ context 'linking external resources' do
+ it "the created link's text will be equal to the resource's text" do
+ tag = '[[http://example.com]]'
+ doc = filter("See #{tag}", project_wiki: project_wiki)
+
+ expect(doc.at_css('a').text).to eq 'http://example.com'
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+
+ it "the created link's text will be link-text" do
+ tag = '[[link-text|http://example.com/pdfs/gollum.pdf]]'
+ doc = filter("See #{tag}", project_wiki: project_wiki)
+
+ expect(doc.at_css('a').text).to eq 'link-text'
+ expect(doc.at_css('a')['href']).to eq 'http://example.com/pdfs/gollum.pdf'
+ end
+ end
+
+ context 'linking internal resources' do
+ it "the created link's text will be equal to the resource's text" do
+ tag = '[[wiki-slug]]'
+ doc = filter("See #{tag}", project_wiki: project_wiki)
+
+ expect(doc.at_css('a').text).to eq 'wiki-slug'
+ expect(doc.at_css('a')['href']).to eq 'wiki-slug'
+ end
+
+ it "the created link's text will be link-text" do
+ tag = '[[link-text|wiki-slug]]'
+ doc = filter("See #{tag}", project_wiki: project_wiki)
+
+ expect(doc.at_css('a').text).to eq 'link-text'
+ expect(doc.at_css('a')['href']).to eq 'wiki-slug'
+ end
+ end
+
+ context 'table of contents' do
+ it 'replaces [[<em>TOC</em>]] with ToC result' do
+ doc = described_class.call("<p>[[<em>TOC</em>]]</p>", { project_wiki: project_wiki }, { toc: "FOO" })
+
+ expect(doc.to_html).to eq("FOO")
+ end
+
+ it 'handles an empty ToC result' do
+ input = "<p>[[<em>TOC</em>]]</p>"
+ doc = described_class.call(input, project_wiki: project_wiki)
+
+ expect(doc.to_html).to eq ''
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index b46ccc47605..e2d21f53b7e 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -111,7 +111,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
context 'String-based multi-word references in quotes' do
let(:label) { create(:label, name: 'gfm references', project: project) }
- let(:reference) { label.to_reference(:name) }
+ let(:reference) { label.to_reference(format: :name) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
@@ -176,4 +176,29 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
expect(result[:references][:label]).to eq [label]
end
end
+
+ describe 'cross project label references' do
+ let(:another_project) { create(:empty_project, :public) }
+ let(:project_name) { another_project.name_with_namespace }
+ let(:label) { create(:label, project: another_project, color: '#00ff00') }
+ let(:reference) { label.to_reference(project) }
+
+ let!(:result) { reference_filter("See #{reference}") }
+
+ it 'points to referenced project issues page' do
+ expect(result.css('a').first.attr('href'))
+ .to eq urls.namespace_project_issues_url(another_project.namespace,
+ another_project,
+ label_name: label.name)
+ end
+
+ it 'has valid color' do
+ expect(result.css('a span').first.attr('style'))
+ .to match /background-color: #00ff00/
+ end
+
+ it 'contains cross project content' do
+ expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}"
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
new file mode 100644
index 00000000000..ebf3d7489b5
--- /dev/null
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
+ include FilterSpecHelper
+
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ it 'requires project context' do
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+ end
+
+ %w(pre code a style).each do |elem|
+ it "ignores valid references contained inside '#{elem}' element" do
+ exp = act = "<#{elem}>milestone #{milestone.to_reference}</#{elem}>"
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'internal reference' do
+ # Convert the Markdown link to only the URL, since these tests aren't run through the regular Markdown pipeline.
+ # Milestone reference behavior in the full Markdown pipeline is tested elsewhere.
+ let(:reference) { milestone.to_reference.gsub(/\[([^\]]+)\]\(([^)]+)\)/, '\2') }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_milestone_url(project.namespace, project, milestone)
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("milestone (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(milestone.title)}<\/a>\.\)/)
+ end
+
+ it 'includes a title attribute' do
+ doc = reference_filter("milestone #{reference}")
+ expect(doc.css('a').first.attr('title')).to eq "Milestone: #{milestone.title}"
+ end
+
+ it 'escapes the title attribute' do
+ milestone.update_attribute(:title, %{"></a>whatever<a title="})
+
+ doc = reference_filter("milestone #{reference}")
+ expect(doc.text).to eq "milestone #{milestone.title}"
+ end
+
+ it 'includes default classes' do
+ doc = reference_filter("milestone #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone'
+ end
+
+ it 'includes a data-project attribute' do
+ doc = reference_filter("milestone #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-milestone attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-milestone')
+ expect(link.attr('data-milestone')).to eq milestone.id.to_s
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("milestone #{reference}")
+ expect(result[:references][:milestone]).to eq [milestone]
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb
index e9bb388e361..9acf6304bcb 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/redactor_filter_spec.rb
@@ -44,8 +44,78 @@ describe Banzai::Filter::RedactorFilter, lib: true do
end
end
- context "for user references" do
+ context 'with data-issue' do
+ context 'for confidential issues' do
+ it 'removes references for non project members' do
+ non_member = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: non_member)
+
+ expect(doc.css('a').length).to eq 0
+ end
+
+ it 'allows references for author' do
+ author = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project, author: author)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: author)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'allows references for assignee' do
+ assignee = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project, assignee: assignee)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: assignee)
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'allows references for project members' do
+ member = create(:user)
+ project = create(:empty_project, :public)
+ project.team << [member, :developer]
+ issue = create(:issue, :confidential, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: member)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'allows references for admin' do
+ admin = create(:admin)
+ project = create(:empty_project, :public)
+ issue = create(:issue, :confidential, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: admin)
+
+ expect(doc.css('a').length).to eq 1
+ end
+ end
+
+ it 'allows references for non confidential issues' do
+ user = create(:user)
+ project = create(:empty_project, :public)
+ issue = create(:issue, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 1
+ end
+ end
+
+ context "for user references" do
context 'with data-group' do
it 'removes unpermitted Group references' do
user = create(:user)
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 0b3e5ecbc9f..0e6685f0ffb 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -92,6 +92,14 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
end
+ it 'rebuilds relative URL for a file in the repository root' do
+ relative_link = link('../README.md')
+ doc = filter(relative_link, requested_path: 'doc/some-file.md')
+
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/README.md"
+ end
+
it 'rebuilds relative URL for a file in the repo with an anchor' do
doc = filter(link('README.md#section'))
expect(doc.at_css('a')['href']).
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 760d60a4190..27ce312b11c 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -75,6 +75,11 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(filter(act).to_html).to eq exp
end
+ it 'allows `abbr` elements' do
+ exp = act = %q{<abbr title="HyperText Markup Language">HTML</abbr>}
+ expect(filter(act).to_html).to eq exp
+ end
+
it 'removes `rel` attribute from `a` elements' do
act = %q{<a href="#" rel="nofollow">Link</a>}
exp = %q{<a href="#">Link</a>}
@@ -144,20 +149,54 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
output: '<a href="java"></a>'
},
+ 'protocol-based JS injection: invalid URL char' => {
+ input: '<img src=java\script:alert("XSS")>',
+ output: '<img>'
+ },
+
'protocol-based JS injection: spaces and entities' => {
input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
output: '<a href="">foo</a>'
},
+
+ 'protocol whitespace' => {
+ input: '<a href=" http://example.com/"></a>',
+ output: '<a href="http://example.com/"></a>'
+ }
}
protocols.each do |name, data|
- it "handles #{name}" do
+ it "disallows #{name}" do
doc = filter(data[:input])
expect(doc.to_html).to eq data[:output]
end
end
+ it 'disallows data links' do
+ input = '<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">XSS</a>'
+ output = filter(input)
+
+ expect(output.to_html).to eq '<a>XSS</a>'
+ end
+
+ it 'disallows vbscript links' do
+ input = '<a href="vbscript:alert(document.domain)">XSS</a>'
+ output = filter(input)
+
+ expect(output.to_html).to eq '<a>XSS</a>'
+ end
+
+ it 'disallows invalid URIs' do
+ expect(Addressable::URI).to receive(:parse).with('foo://example.com').
+ and_raise(Addressable::URI::InvalidURIError)
+
+ input = '<a href="foo://example.com">Foo</a>'
+ output = filter(input)
+
+ expect(output.to_html).to eq '<a>Foo</a>'
+ end
+
it 'allows non-standard anchor schemes' do
exp = %q{<a href="irc://irc.freenode.net/git">IRC</a>}
act = filter(exp)
@@ -172,26 +211,4 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(act.to_html).to eq exp
end
end
-
- context 'when inline_sanitization is true' do
- it 'uses a stricter whitelist' do
- doc = filter('<h1>Description</h1>', inline_sanitization: true)
- expect(doc.to_html.strip).to eq 'Description'
- end
-
- %w(pre code img ol ul li).each do |elem|
- it "removes '#{elem}' elements" do
- act = "<#{elem}>Description</#{elem}>"
- expect(filter(act, inline_sanitization: true).to_html.strip).
- to eq 'Description'
- end
- end
-
- %w(b i strong em a ins del sup sub p).each do |elem|
- it "still allows '#{elem}' elements" do
- exp = act = "<#{elem}>Description</#{elem}>"
- expect(filter(act, inline_sanitization: true).to_html).to eq exp
- end
- end
- end
end
diff --git a/spec/lib/banzai/filter/task_list_filter_spec.rb b/spec/lib/banzai/filter/task_list_filter_spec.rb
index f2e3a44478d..569cbc885c7 100644
--- a/spec/lib/banzai/filter/task_list_filter_spec.rb
+++ b/spec/lib/banzai/filter/task_list_filter_spec.rb
@@ -7,4 +7,10 @@ describe Banzai::Filter::TaskListFilter, lib: true do
exp = act = %(<ul><li>Item</li></ul>)
expect(filter(act).to_html).to eq exp
end
+
+ it 'applies `task-list` to single-item task lists' do
+ act = filter('<ul><li>[ ] Task 1</li></ul>')
+
+ expect(act.to_html).to start_with '<ul class="task-list">'
+ end
end
diff --git a/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb b/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb
new file mode 100644
index 00000000000..fe70eada7eb
--- /dev/null
+++ b/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+describe Banzai::Filter::YamlFrontMatterFilter, lib: true do
+ include FilterSpecHelper
+
+ it 'allows for `encoding:` before the frontmatter' do
+ content = <<-MD.strip_heredoc
+ # encoding: UTF-8
+ ---
+ foo: foo
+ ---
+
+ # Header
+
+ Content
+ MD
+
+ output = filter(content)
+
+ expect(output).not_to match 'encoding'
+ end
+
+ it 'converts YAML frontmatter to a fenced code block' do
+ content = <<-MD.strip_heredoc
+ ---
+ bar: :bar_symbol
+ ---
+
+ # Header
+
+ Content
+ MD
+
+ output = filter(content)
+
+ aggregate_failures do
+ expect(output).not_to include '---'
+ expect(output).to include "```yaml\nbar: :bar_symbol\n```"
+ end
+ end
+
+ context 'on content without frontmatter' do
+ it 'returns the content unmodified' do
+ content = <<-MD.strip_heredoc
+ # This is some Markdown
+
+ It has no YAML frontmatter to parse.
+ MD
+
+ expect(filter(content)).to eq content
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter_array_spec.rb b/spec/lib/banzai/filter_array_spec.rb
new file mode 100644
index 00000000000..ea84005e7f8
--- /dev/null
+++ b/spec/lib/banzai/filter_array_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Banzai::FilterArray do
+ describe '#insert_after' do
+ it 'inserts an element after a provided element' do
+ filters = described_class.new(%w(a b c))
+
+ filters.insert_after('b', '1')
+
+ expect(filters).to eq %w(a b 1 c)
+ end
+
+ it 'inserts an element at the end when the provided element does not exist' do
+ filters = described_class.new(%w(a b c))
+
+ filters.insert_after('d', '1')
+
+ expect(filters).to eq %w(a b c 1)
+ end
+ end
+
+ describe '#insert_before' do
+ it 'inserts an element before a provided element' do
+ filters = described_class.new(%w(a b c))
+
+ filters.insert_before('b', '1')
+
+ expect(filters).to eq %w(a 1 b c)
+ end
+
+ it 'inserts an element at the beginning when the provided element does not exist' do
+ filters = described_class.new(%w(a b c))
+
+ filters.insert_before('d', '1')
+
+ expect(filters).to eq %w(1 a b c)
+ end
+ end
+end
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
new file mode 100644
index 00000000000..76f42071810
--- /dev/null
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+describe Banzai::Pipeline::DescriptionPipeline do
+ def parse(html)
+ # When we pass HTML to Redcarpet, it gets wrapped in `p` tags...
+ # ...except when we pass it pre-wrapped text. Rabble rabble.
+ unwrap = !html.start_with?('<p>')
+
+ output = described_class.to_html(html, project: spy)
+
+ output.gsub!(%r{\A<p>(.*)</p>(.*)\z}, '\1\2') if unwrap
+
+ output
+ end
+
+ it 'uses a limited whitelist' do
+ doc = parse('# Description')
+
+ expect(doc.strip).to eq 'Description'
+ end
+
+ %w(pre code img ol ul li).each do |elem|
+ it "removes '#{elem}' elements" do
+ act = "<#{elem}>Description</#{elem}>"
+
+ expect(parse(act).strip).to eq 'Description'
+ end
+ end
+
+ %w(b i strong em a ins del sup sub p).each do |elem|
+ it "still allows '#{elem}' elements" do
+ exp = act = "<#{elem}>Description</#{elem}>"
+
+ expect(parse(act).strip).to eq exp
+ end
+ end
+end
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
new file mode 100644
index 00000000000..3e25406e498
--- /dev/null
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+describe Banzai::Pipeline::WikiPipeline do
+ describe 'TableOfContents' do
+ it 'replaces the tag with the TableOfContentsFilter result' do
+ markdown = <<-MD.strip_heredoc
+ [[_TOC_]]
+
+ ## Header
+
+ Foo
+ MD
+
+ result = described_class.call(markdown, project: spy, project_wiki: double)
+
+ aggregate_failures do
+ expect(result[:output].text).not_to include '[['
+ expect(result[:output].text).not_to include 'TOC'
+ expect(result[:output].to_html).to include(result[:toc])
+ end
+ end
+
+ it 'is case-sensitive' do
+ markdown = <<-MD.strip_heredoc
+ [[_toc_]]
+
+ # Header 1
+
+ Foo
+ MD
+
+ output = described_class.to_html(markdown, project: spy, project_wiki: double)
+
+ expect(output).to include('[[<em>toc</em>]]')
+ end
+
+ it 'handles an empty pipeline result' do
+ # No Markdown headers in this doc, so `result[:toc]` will be empty
+ markdown = <<-MD.strip_heredoc
+ [[_TOC_]]
+
+ Foo
+ MD
+
+ output = described_class.to_html(markdown, project: spy, project_wiki: double)
+
+ aggregate_failures do
+ expect(output).not_to include('<ul>')
+ expect(output).not_to include('[[<em>TOC</em>]]')
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/querying_spec.rb b/spec/lib/banzai/querying_spec.rb
new file mode 100644
index 00000000000..27da2a7439c
--- /dev/null
+++ b/spec/lib/banzai/querying_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe Banzai::Querying do
+ describe '.css' do
+ it 'optimizes queries for elements with classes' do
+ document = double(:document)
+
+ expect(document).to receive(:xpath).with(/^descendant::a/)
+
+ described_class.css(document, 'a.gfm')
+ end
+ end
+end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index d15100fc6d8..fab6412d29f 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -336,7 +336,7 @@ module Ci
describe "Caches" do
it "returns cache when defined globally" do
config = YAML.dump({
- cache: { paths: ["logs/", "binaries/"], untracked: true },
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
rspec: {
script: "rspec"
}
@@ -348,13 +348,14 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
+ key: 'key',
)
end
it "returns cache when defined in a job" do
config = YAML.dump({
rspec: {
- cache: { paths: ["logs/", "binaries/"], untracked: true },
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'key' },
script: "rspec"
}
})
@@ -365,15 +366,16 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
+ key: 'key',
)
end
it "overwrite cache when defined for a job and globally" do
config = YAML.dump({
- cache: { paths: ["logs/", "binaries/"], untracked: true },
+ cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' },
rspec: {
script: "rspec",
- cache: { paths: ["test/"], untracked: false },
+ cache: { paths: ["test/"], untracked: false, key: 'local' },
}
})
@@ -383,6 +385,7 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["test/"],
untracked: false,
+ key: 'local',
)
end
end
@@ -394,7 +397,7 @@ module Ci
services: ["mysql"],
before_script: ["pwd"],
rspec: {
- artifacts: { paths: ["logs/", "binaries/"], untracked: true },
+ artifacts: { paths: ["logs/", "binaries/"], untracked: true, name: "custom_name" },
script: "rspec"
}
})
@@ -414,6 +417,7 @@ module Ci
image: "ruby:2.1",
services: ["mysql"],
artifacts: {
+ name: "custom_name",
paths: ["logs/", "binaries/"],
untracked: true
}
@@ -424,6 +428,112 @@ module Ci
end
end
+ describe "Dependencies" do
+ let(:config) do
+ {
+ build1: { stage: 'build', script: 'test' },
+ build2: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', script: 'test', dependencies: dependencies },
+ test2: { stage: 'test', script: 'test' },
+ deploy: { stage: 'test', script: 'test' }
+ }
+ end
+
+ subject { GitlabCiYamlProcessor.new(YAML.dump(config)) }
+
+ context 'no dependencies' do
+ let(:dependencies) { }
+
+ it { expect { subject }.to_not raise_error }
+ end
+
+ context 'dependencies to builds' do
+ let(:dependencies) { [:build1, :build2] }
+
+ it { expect { subject }.to_not raise_error }
+ end
+
+ context 'undefined dependency' do
+ let(:dependencies) { [:undefined] }
+
+ it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: undefined dependency: undefined') }
+ end
+
+ context 'dependencies to deploy' do
+ let(:dependencies) { [:deploy] }
+
+ it { expect { subject }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'test1 job: dependency deploy is not defined in prior stages') }
+ end
+ end
+
+ describe "Hidden jobs" do
+ let(:config) do
+ YAML.dump({
+ '.hidden_job' => { script: 'test' },
+ 'normal_job' => { script: 'test' }
+ })
+ end
+
+ let(:config_processor) { GitlabCiYamlProcessor.new(config) }
+
+ subject { config_processor.builds_for_stage_and_ref("test", "master") }
+
+ it "doesn't create jobs that starts with dot" do
+ expect(subject.size).to eq(1)
+ expect(subject.first).to eq({
+ except: nil,
+ stage: "test",
+ stage_idx: 1,
+ name: :normal_job,
+ only: nil,
+ commands: "\ntest",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ end
+ end
+
+ describe "YAML Alias/Anchor" do
+ it "is correctly supported for jobs" do
+ config = <<EOT
+job1: &JOBTMPL
+ script: execute-script-for-job
+
+job2: *JOBTMPL
+EOT
+
+ config_processor = GitlabCiYamlProcessor.new(config)
+
+ expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(2)
+ expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
+ except: nil,
+ stage: "test",
+ stage_idx: 1,
+ name: :job1,
+ only: nil,
+ commands: "\nexecute-script-for-job",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ expect(config_processor.builds_for_stage_and_ref("test", "master").second).to eq({
+ except: nil,
+ stage: "test",
+ stage_idx: 1,
+ name: :job2,
+ only: nil,
+ commands: "\nexecute-script-for-job",
+ tag_list: [],
+ options: {},
+ when: "on_success",
+ allow_failure: false
+ })
+ end
+ end
+
describe "Error handling" do
it "fails to parse YAML" do
expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
@@ -587,6 +697,13 @@ module Ci
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
end
+ it "returns errors if job artifacts:name is not an a string" do
+ config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { name: 1 } } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string")
+ end
+
it "returns errors if job artifacts:untracked is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } })
expect do
@@ -615,6 +732,20 @@ module Ci
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings")
end
+ it "returns errors if cache:key is not a string" do
+ config = YAML.dump({ cache: { key: 1 }, rspec: { script: "test" } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:key parameter should be a string")
+ end
+
+ it "returns errors if job cache:key is not an a string" do
+ config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { key: 1 } } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:key parameter should be a string")
+ end
+
it "returns errors if job cache:untracked is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } })
expect do
@@ -628,6 +759,13 @@ module Ci
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings")
end
+
+ it "returns errors if job dependencies is not an array of strings" do
+ config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", dependencies: "string" } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings")
+ end
end
end
end
diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb
new file mode 100644
index 00000000000..47f3df6e3ce
--- /dev/null
+++ b/spec/lib/ci/status_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+describe Ci::Status do
+ describe '.get_status' do
+ subject { described_class.get_status(statuses) }
+
+ shared_examples 'build status summary' do
+ context 'all successful' do
+ let(:statuses) { Array.new(2) { create(type, status: :success) } }
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'at least one failed' do
+ let(:statuses) do
+ [create(type, status: :success), create(type, status: :failed)]
+ end
+
+ it { is_expected.to eq 'failed' }
+ end
+
+ context 'at least one running' do
+ let(:statuses) do
+ [create(type, status: :success), create(type, status: :running)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'at least one pending' do
+ let(:statuses) do
+ [create(type, status: :success), create(type, status: :pending)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+
+ context 'success and failed but allowed to fail' do
+ let(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :failed, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'one failed but allowed to fail' do
+ let(:statuses) { [create(type, status: :failed, allow_failure: true)] }
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'success and canceled' do
+ let(:statuses) do
+ [create(type, status: :success), create(type, status: :canceled)]
+ end
+ it { is_expected.to eq 'failed' }
+ end
+
+ context 'all canceled' do
+ let(:statuses) do
+ [create(type, status: :canceled), create(type, status: :canceled)]
+ end
+ it { is_expected.to eq 'canceled' }
+ end
+
+ context 'success and canceled but allowed to fail' do
+ let(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :canceled, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'success' }
+ end
+
+ context 'one finished and second running but allowed to fail' do
+ let(:statuses) do
+ [create(type, status: :success),
+ create(type, status: :running, allow_failure: true)]
+ end
+
+ it { is_expected.to eq 'running' }
+ end
+ end
+
+ context 'ci build statuses' do
+ let(:type) { :ci_build }
+ it_behaves_like 'build status summary'
+ end
+
+ context 'generic commit statuses' do
+ let(:type) { :generic_commit_status }
+ it_behaves_like 'build status summary'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb
new file mode 100644
index 00000000000..9858935180a
--- /dev/null
+++ b/spec/lib/gitlab/akismet_helper_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Gitlab::AkismetHelper, type: :helper do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
+ current_application_settings.akismet_enabled = true
+ current_application_settings.akismet_api_key = '12345'
+ end
+
+ describe '#check_for_spam?' do
+ it 'returns true for non-member' do
+ expect(helper.check_for_spam?(project, user)).to eq(true)
+ end
+
+ it 'returns false for member' do
+ project.team << [user, :guest]
+ expect(helper.check_for_spam?(project, user)).to eq(false)
+ end
+ end
+
+ describe '#is_spam?' do
+ it 'returns true for spam' do
+ environment = {
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'HTTP_USER_AGENT' => 'Test User Agent'
+ }
+
+ allow_any_instance_of(::Akismet::Client).to receive(:check).and_return([true, true])
+ expect(helper.is_spam?(environment, user, 'Is this spam?')).to eq(true)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 6beb21c6d2b..736bf787208 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -42,22 +42,6 @@ module Gitlab
end
end
- context "with project in context" do
-
- let(:context) { { project: create(:project) } }
-
- it "should filter converted input via HTML pipeline and return result" do
- filtered_html = '<b>ASCII</b>'
-
- allow(Asciidoctor).to receive(:convert).and_return(html)
- expect(Banzai).to receive(:render)
- .with(html, context.merge(pipeline: :asciidoc))
- .and_return(filtered_html)
-
- expect( render('foo', context) ).to eql filtered_html
- end
- end
-
def render(*args)
described_class.render(*args)
end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
new file mode 100644
index 00000000000..c413132abe5
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe Gitlab::BitbucketImport::Importer, lib: true do
+ before do
+ Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket")
+ end
+
+ let(:statuses) do
+ [
+ "open",
+ "resolved",
+ "on hold",
+ "invalid",
+ "duplicate",
+ "wontfix",
+ "closed" # undocumented status
+ ]
+ end
+ let(:sample_issues_statuses) do
+ issues = []
+
+ statuses.map.with_index do |status, index|
+ issues << {
+ local_id: index,
+ status: status,
+ title: "Issue #{index}",
+ content: "Some content to issue #{index}"
+ }
+ end
+
+ issues
+ end
+
+ let(:project_identifier) { 'namespace/repo' }
+ let(:data) do
+ {
+ bb_session: {
+ bitbucket_access_token: "123456",
+ bitbucket_access_token_secret: "secret"
+ }
+ }
+ end
+ let(:project) do
+ create(
+ :project,
+ import_source: project_identifier,
+ import_data: ProjectImportData.new(data: data)
+ )
+ end
+ let(:importer) { Gitlab::BitbucketImport::Importer.new(project) }
+ let(:issues_statuses_sample_data) do
+ {
+ count: sample_issues_statuses.count,
+ issues: sample_issues_statuses
+ }
+ end
+
+ context 'issues statuses' do
+ before do
+ stub_request(
+ :get,
+ "https://bitbucket.org/api/1.0/repositories/#{project_identifier}"
+ ).to_return(status: 200, body: { has_issues: true }.to_json)
+
+ stub_request(
+ :get,
+ "https://bitbucket.org/api/1.0/repositories/#{project_identifier}/issues?limit=50&sort=utc_created_on&start=0"
+ ).to_return(status: 200, body: issues_statuses_sample_data.to_json)
+
+ sample_issues_statuses.each_with_index do |issue, index|
+ stub_request(
+ :get,
+ "https://bitbucket.org/api/1.0/repositories/#{project_identifier}/issues/#{issue[:local_id]}/comments"
+ ).to_return(
+ status: 200,
+ body: [{ author_info: { username: "username" }, utc_created_on: index }].to_json
+ )
+ end
+ end
+
+ it 'map statuses to open or closed' do
+ importer.execute
+
+ expect(project.issues.where(state: "closed").size).to eq(5)
+ expect(project.issues.where(state: "opened").size).to eq(2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
new file mode 100644
index 00000000000..89245761b6f
--- /dev/null
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::Blame, lib: true do
+ let(:project) { create(:project) }
+ let(:path) { 'files/ruby/popen.rb' }
+ let(:commit) { project.commit('master') }
+ let(:blob) { project.repository.blob_at(commit.id, path) }
+
+ describe "#groups" do
+ let(:subject) { described_class.new(blob, commit).groups(highlight: false) }
+
+ it 'groups lines properly' do
+ expect(subject.count).to eq(18)
+ expect(subject[0][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+ expect(subject[0][:lines]).to eq(["require 'fileutils'", "require 'open3'", ""])
+
+ expect(subject[1][:commit].sha).to eq('874797c3a73b60d2187ed6e2fcabd289ff75171e')
+ expect(subject[1][:lines]).to eq(["module Popen", " extend self"])
+
+ expect(subject[-1][:commit].sha).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+ expect(subject[-1][:lines]).to eq([" end", "end"])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/build_data_builder_spec.rb b/spec/lib/gitlab/build_data_builder_spec.rb
index 839b30f1ff4..38be9448794 100644
--- a/spec/lib/gitlab/build_data_builder_spec.rb
+++ b/spec/lib/gitlab/build_data_builder_spec.rb
@@ -14,6 +14,7 @@ describe 'Gitlab::BuildDataBuilder' do
it { expect(data[:tag]).to eq(build.tag) }
it { expect(data[:build_id]).to eq(build.id) }
it { expect(data[:build_status]).to eq(build.status) }
+ it { expect(data[:build_allow_failure]).to eq(false) }
it { expect(data[:project_id]).to eq(build.project.id) }
it { expect(data[:project_name]).to eq(build.project.name_with_namespace) }
end
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
new file mode 100644
index 00000000000..acca0b08bab
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -0,0 +1,173 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
+ let(:entries) do
+ { 'path/' => {},
+ 'path/dir_1/' => {},
+ 'path/dir_1/file_1' => { size: 10 },
+ 'path/dir_1/file_b' => { size: 10 },
+ 'path/dir_1/subdir/' => {},
+ 'path/dir_1/subdir/subfile' => { size: 10 },
+ 'path/second_dir' => {},
+ 'path/second_dir/dir_3/file_2' => { size: 10 },
+ 'path/second_dir/dir_3/file_3'=> { size: 10 },
+ 'another_directory/'=> {},
+ 'another_file' => {},
+ '/file/with/absolute_path' => {} }
+ end
+
+ def path(example)
+ entry(example.metadata[:path])
+ end
+
+ def entry(path)
+ described_class.new(path, entries)
+ end
+
+ describe '/file/with/absolute_path', path: '/file/with/absolute_path' do
+ subject { |example| path(example) }
+
+ it { is_expected.to be_file }
+ it { is_expected.to have_parent }
+
+ describe '#basename' do
+ subject { |example| path(example).basename }
+ it { is_expected.to eq 'absolute_path' }
+ end
+ end
+
+ describe 'path/dir_1/', path: 'path/dir_1/' do
+ subject { |example| path(example) }
+ it { is_expected.to have_parent }
+ it { is_expected.to be_directory }
+
+ describe '#basename' do
+ subject { |example| path(example).basename }
+ it { is_expected.to eq 'dir_1/' }
+ end
+
+ describe '#name' do
+ subject { |example| path(example).name }
+ it { is_expected.to eq 'dir_1' }
+ end
+
+ describe '#parent' do
+ subject { |example| path(example).parent }
+ it { is_expected.to eq entry('path/') }
+ end
+
+ describe '#children' do
+ subject { |example| path(example).children }
+
+ it { is_expected.to all(be_an_instance_of described_class) }
+ it do
+ is_expected.to contain_exactly entry('path/dir_1/file_1'),
+ entry('path/dir_1/file_b'),
+ entry('path/dir_1/subdir/')
+ end
+ end
+
+ describe '#files' do
+ subject { |example| path(example).files }
+
+ it { is_expected.to all(be_file) }
+ it { is_expected.to all(be_an_instance_of described_class) }
+ it do
+ is_expected.to contain_exactly entry('path/dir_1/file_1'),
+ entry('path/dir_1/file_b')
+ end
+ end
+
+ describe '#directories' do
+ context 'without options' do
+ subject { |example| path(example).directories }
+
+ it { is_expected.to all(be_directory) }
+ it { is_expected.to all(be_an_instance_of described_class) }
+ it { is_expected.to contain_exactly entry('path/dir_1/subdir/') }
+ end
+
+ context 'with option parent: true' do
+ subject { |example| path(example).directories(parent: true) }
+
+ it { is_expected.to all(be_directory) }
+ it { is_expected.to all(be_an_instance_of described_class) }
+ it do
+ is_expected.to contain_exactly entry('path/dir_1/subdir/'),
+ entry('path/')
+ end
+ end
+
+ describe '#nodes' do
+ subject { |example| path(example).nodes }
+ it { is_expected.to eq 2 }
+ end
+
+ describe '#exists?' do
+ subject { |example| path(example).exists? }
+ it { is_expected.to be true }
+ end
+
+ describe '#empty?' do
+ subject { |example| path(example).empty? }
+ it { is_expected.to be false }
+ end
+
+ describe '#total_size' do
+ subject { |example| path(example).total_size }
+ it { is_expected.to eq(30) }
+ end
+ end
+ end
+
+ describe 'empty path', path: '' do
+ subject { |example| path(example) }
+ it { is_expected.to_not have_parent }
+
+ describe '#children' do
+ subject { |example| path(example).children }
+ it { expect(subject.count).to eq 3 }
+ end
+
+ end
+
+ describe 'path/dir_1/subdir/subfile', path: 'path/dir_1/subdir/subfile' do
+ describe '#nodes' do
+ subject { |example| path(example).nodes }
+ it { is_expected.to eq 4 }
+ end
+ end
+
+ describe 'non-existent/', path: 'non-existent/' do
+ describe '#empty?' do
+ subject { |example| path(example).empty? }
+ it { is_expected.to be true }
+ end
+
+ describe '#exists?' do
+ subject { |example| path(example).exists? }
+ it { is_expected.to be false }
+ end
+ end
+
+ describe 'another_directory/', path: 'another_directory/' do
+ describe '#empty?' do
+ subject { |example| path(example).empty? }
+ it { is_expected.to be true }
+ end
+ end
+
+ describe '#metadata' do
+ let(:entries) do
+ { 'path/' => { name: '/path/' },
+ 'path/file1' => { name: '/path/file1' },
+ 'path/file2' => { name: '/path/file2' } }
+ end
+
+ subject do
+ described_class.new('path/file1', entries).metadata[:name]
+ end
+
+ it { is_expected.to eq '/path/file1' }
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
new file mode 100644
index 00000000000..eea01f91879
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Artifacts::Metadata do
+ def metadata(path = '', **opts)
+ described_class.new(metadata_file_path, path, **opts)
+ end
+
+ let(:metadata_file_path) do
+ Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
+ end
+
+ context 'metadata file exists' do
+ describe '#find_entries! empty string' do
+ subject { metadata('').find_entries! }
+
+ it 'matches correct paths' do
+ expect(subject.keys).to contain_exactly 'ci_artifacts.txt',
+ 'other_artifacts_0.1.2/',
+ 'rails_sample.jpg',
+ 'tests_encoding/'
+ end
+
+ it 'matches metadata for every path' do
+ expect(subject.keys.count).to eq 4
+ end
+
+ it 'return Hashes for each metadata' do
+ expect(subject.values).to all(be_kind_of(Hash))
+ end
+ end
+
+ describe '#find_entries! other_artifacts_0.1.2/' do
+ subject { metadata('other_artifacts_0.1.2/').find_entries! }
+
+ it 'matches correct paths' do
+ expect(subject.keys).
+ to contain_exactly 'other_artifacts_0.1.2/',
+ 'other_artifacts_0.1.2/doc_sample.txt',
+ 'other_artifacts_0.1.2/another-subdirectory/'
+ end
+ end
+
+ describe '#find_entries! other_artifacts_0.1.2/another-subdirectory/' do
+ subject { metadata('other_artifacts_0.1.2/another-subdirectory/').find_entries! }
+
+ it 'matches correct paths' do
+ expect(subject.keys).
+ to contain_exactly 'other_artifacts_0.1.2/another-subdirectory/',
+ 'other_artifacts_0.1.2/another-subdirectory/empty_directory/',
+ 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif'
+ end
+ end
+
+ describe '#find_entries! recursively for other_artifacts_0.1.2/' do
+ subject { metadata('other_artifacts_0.1.2/', recursive: true).find_entries! }
+
+ it 'matches correct paths' do
+ expect(subject.keys).
+ to contain_exactly 'other_artifacts_0.1.2/',
+ 'other_artifacts_0.1.2/doc_sample.txt',
+ 'other_artifacts_0.1.2/another-subdirectory/',
+ 'other_artifacts_0.1.2/another-subdirectory/empty_directory/',
+ 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif'
+ end
+ end
+
+ describe '#to_entry' do
+ subject { metadata('').to_entry }
+ it { is_expected.to be_an_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) }
+ end
+
+ describe '#full_version' do
+ subject { metadata('').full_version }
+ it { is_expected.to eq 'GitLab Build Artifacts Metadata 0.0.1' }
+ end
+
+ describe '#version' do
+ subject { metadata('').version }
+ it { is_expected.to eq '0.0.1' }
+ end
+
+ describe '#errors' do
+ subject { metadata('').errors }
+ it { is_expected.to eq({}) }
+ end
+ end
+
+ context 'metadata file does not exist' do
+ let(:metadata_file_path) { '' }
+
+ describe '#find_entries!' do
+ it 'raises error' do
+ expect { metadata.find_entries! }.to raise_error(Errno::ENOENT)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 99288da1e43..844fd79c991 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -11,6 +11,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
subject { described_class.new(project, project.creator) }
before do
+ project.team << [project.creator, :developer]
project2.team << [project.creator, :master]
end
@@ -135,6 +136,17 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
message = "resolve #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
+
+ context 'with an external issue tracker reference' do
+ it 'extracts the referenced issue' do
+ jira_project = create(:jira_project, name: 'JIRA_EXT1')
+ jira_issue = ExternalIssue.new("#{jira_project.name}-1", project: jira_project)
+ closing_issue_extractor = described_class.new jira_project
+ message = "Resolve #{jira_issue.to_reference}"
+
+ expect(closing_issue_extractor.closed_by_message(message)).to eq([jira_issue])
+ end
+ end
end
context "with a cross-project reference" do
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 8461e8ce50d..d0a447753b7 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -1,5 +1,9 @@
require 'spec_helper'
+class MigrationTest
+ include Gitlab::Database
+end
+
describe Gitlab::Database, lib: true do
# These are just simple smoke tests to check if the methods work (regardless
# of what they may return).
@@ -14,4 +18,52 @@ describe Gitlab::Database, lib: true do
it { is_expected.to satisfy { |val| val == true || val == false } }
end
+
+ describe '.version' do
+ context "on mysql" do
+ it "extracts the version number" do
+ allow(described_class).to receive(:database_version).
+ and_return("5.7.12-standard")
+
+ expect(described_class.version).to eq '5.7.12-standard'
+ end
+ end
+
+ context "on postgresql" do
+ it "extracts the version number" do
+ allow(described_class).to receive(:database_version).
+ and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
+
+ expect(described_class.version).to eq '9.4.4'
+ end
+ end
+ end
+
+ describe '#true_value' do
+ it 'returns correct value for PostgreSQL' do
+ expect(described_class).to receive(:postgresql?).and_return(true)
+
+ expect(MigrationTest.new.true_value).to eq "'t'"
+ end
+
+ it 'returns correct value for MySQL' do
+ expect(described_class).to receive(:postgresql?).and_return(false)
+
+ expect(MigrationTest.new.true_value).to eq 1
+ end
+ end
+
+ describe '#false_value' do
+ it 'returns correct value for PostgreSQL' do
+ expect(described_class).to receive(:postgresql?).and_return(true)
+
+ expect(MigrationTest.new.false_value).to eq "'f'"
+ end
+
+ it 'returns correct value for MySQL' do
+ expect(described_class).to receive(:postgresql?).and_return(false)
+
+ expect(MigrationTest.new.false_value).to eq 0
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index c7cdf8691d6..a0cbef6e6a4 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Diff::File, lib: true do
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.diffs.first }
- let(:diff_file) { Gitlab::Diff::File.new(diff) }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
describe :diff_lines do
let(:diff_lines) { diff_file.diff_lines }
@@ -18,4 +18,18 @@ describe Gitlab::Diff::File, lib: true do
describe :mode_changed? do
it { expect(diff_file.mode_changed?).to be_falsey }
end
+
+ describe '#too_large?' do
+ it 'returns true for a file that is too large' do
+ expect(diff).to receive(:too_large?).and_return(true)
+
+ expect(diff_file.too_large?).to eq(true)
+ end
+
+ it 'returns false for a file that is small enough' do
+ expect(diff).to receive(:too_large?).and_return(false)
+
+ expect(diff_file.too_large?).to eq(false)
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
new file mode 100644
index 00000000000..d19bf4ac84b
--- /dev/null
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::Highlight, lib: true do
+ include RepoHelpers
+
+ let(:project) { create(:project) }
+ let(:commit) { project.commit(sample_commit.id) }
+ let(:diff) { commit.diffs.first }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
+
+ describe '#highlight' do
+ context "with a diff file" do
+ let(:subject) { Gitlab::Diff::Highlight.new(diff_file).highlight }
+
+ it 'should return Gitlab::Diff::Line elements' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
+ end
+
+ it 'should not modify "match" lines' do
+ expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
+ expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
+ end
+
+ it 'highlights and marks unchanged lines' do
+ code = %Q{ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n}
+
+ expect(subject[2].text).to eq(code)
+ end
+
+ it 'highlights and marks removed lines' do
+ code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
+
+ expect(subject[4].text).to eq(code)
+ end
+
+ it 'highlights and marks added lines' do
+ code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
+
+ expect(subject[5].text).to eq(code)
+ end
+ end
+
+ context "with diff lines" do
+ let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines).highlight }
+
+ it 'should return Gitlab::Diff::Line elements' do
+ expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
+ end
+
+ it 'should not modify "match" lines' do
+ expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
+ expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
+ end
+
+ it 'marks unchanged lines' do
+ code = %Q{ def popen(cmd, path=nil)}
+
+ expect(subject[2].text).to eq(code)
+ expect(subject[2].text).not_to be_html_safe
+ end
+
+ it 'marks removed lines' do
+ code = %Q{- raise "System commands must be given as an array of strings"}
+
+ expect(subject[4].text).to eq(code)
+ expect(subject[4].text).not_to be_html_safe
+ end
+
+ it 'marks added lines' do
+ code = %Q{+ raise <span class='idiff left right'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
+
+ expect(subject[5].text).to eq(code)
+ expect(subject[5].text).to be_html_safe
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
new file mode 100644
index 00000000000..ea5c31011f0
--- /dev/null
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::InlineDiffMarker, lib: true do
+ describe '#inline_diffs' do
+
+ context "when the rich text is html safe" do
+ let(:raw) { "abc 'def'" }
+ let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>}.html_safe }
+ let(:inline_diffs) { [2..5] }
+ let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{<span class="abc">ab<span class='idiff left'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff right'>&#39;d</span>ef&#39;</span>})
+ expect(subject).to be_html_safe
+ end
+ end
+
+ context "when the text text is not html safe" do
+ let(:raw) { "abc 'def'" }
+ let(:inline_diffs) { [2..5] }
+ let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw).mark(inline_diffs) }
+
+ it 'marks the inline diffs' do
+ expect(subject).to eq(%{ab<span class='idiff left right'>c &#39;d</span>ef&#39;})
+ expect(subject).to be_html_safe
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
new file mode 100644
index 00000000000..95a993d26cf
--- /dev/null
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::InlineDiff, lib: true do
+ describe '.for_lines' do
+ let(:diff) do
+ <<eos
+ class Test
+- def initialize(test = true)
++ def initialize(test = false)
+ @test = test
+ end
+ end
+eos
+ end
+
+ let(:subject) { described_class.for_lines(diff.lines) }
+
+ it 'finds all inline diffs' do
+ expect(subject[0]).to be_nil
+ expect(subject[1]).to eq([25..27])
+ expect(subject[2]).to eq([25..28])
+ expect(subject[3]).to be_nil
+ expect(subject[4]).to be_nil
+ expect(subject[5]).to be_nil
+ end
+ end
+
+ describe "#inline_diffs" do
+ let(:old_line) { "XXX def initialize(test = true)" }
+ let(:new_line) { "YYY def initialize(test = false)" }
+ let(:subject) { described_class.new(old_line, new_line, offset: 3).inline_diffs }
+
+ it "finds the inline diff" do
+ old_diffs, new_diffs = subject
+
+ expect(old_diffs).to eq([26..28])
+ expect(new_diffs).to eq([26..29])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
new file mode 100644
index 00000000000..1c5bbc47120
--- /dev/null
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::ParallelDiff, lib: true do
+ include RepoHelpers
+
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:commit) { project.commit(sample_commit.id) }
+ let(:diffs) { commit.diffs }
+ let(:diff) { diffs.first }
+ let(:diff_refs) { [commit.parent, commit] }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
+ subject { described_class.new(diff_file) }
+
+ let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") }
+
+ describe '#parallelize' do
+ it 'should return an array of arrays containing the parsed diff' do
+ expect(subject.parallelize).to match_array(parallel_diff_result_array)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index ba577bd28e5..cdff063a9ed 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -47,7 +47,7 @@ eos
end
before do
- @lines = parser.parse(diff.lines)
+ @lines = parser.parse(diff.lines).to_a
end
it { expect(@lines.size).to eq(30) }
@@ -86,8 +86,13 @@ eos
it { expect(line.type).to eq(nil) }
it { expect(line.old_pos).to eq(24) }
it { expect(line.new_pos).to eq(31) }
- it { expect(line.text).to eq(' @cmd_output &lt;&lt; stderr.read') }
+ it { expect(line.text).to eq(' @cmd_output << stderr.read') }
end
end
end
+
+ context 'when lines is empty' do
+ it { expect(parser.parse([])).to eq([]) }
+ it { expect(parser.parse(nil)).to eq([]) }
+ end
end
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index 56ae2a8d121..b2d7a799810 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -72,7 +72,7 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#compare_timeout' do
subject { message.compare_timeout }
- it { is_expected.to eq compare.timeout }
+ it { is_expected.to eq compare.diffs.overflow? }
end
describe '#reverse_compare?' do
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index b535413bbd4..abe179cd4af 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -42,7 +42,7 @@ describe Gitlab::Email::Receiver, lib: true do
context "when the email was auto generated" do
let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
-
+
it "raises an AutoGeneratedEmailError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::AutoGeneratedEmailError)
end
@@ -90,7 +90,7 @@ describe Gitlab::Email::Receiver, lib: true do
context "when the reply is blank" do
let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
-
+
it "raises an EmptyEmailError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
end
@@ -107,13 +107,16 @@ describe Gitlab::Email::Receiver, lib: true do
end
context "when everything is fine" do
+ let(:markdown) { "![image](uploads/image.png)" }
+
before do
allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return(
[
{
url: "uploads/image.png",
is_image: true,
- alt: "image"
+ alt: "image",
+ markdown: markdown
}
]
)
@@ -132,7 +135,7 @@ describe Gitlab::Email::Receiver, lib: true do
note = noteable.notes.last
- expect(note.note).to include("![image](uploads/image.png)")
+ expect(note.note).to include(markdown)
end
end
end
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
new file mode 100644
index 00000000000..fbdb7ea34ac
--- /dev/null
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::ExclusiveLease do
+ it 'cannot obtain twice before the lease has expired' do
+ lease = Gitlab::ExclusiveLease.new(unique_key, timeout: 3600)
+ expect(lease.try_obtain).to eq(true)
+ expect(lease.try_obtain).to eq(false)
+ end
+
+ it 'can obtain after the lease has expired' do
+ timeout = 1
+ lease = Gitlab::ExclusiveLease.new(unique_key, timeout: timeout)
+ lease.try_obtain # start the lease
+ sleep(2 * timeout) # lease should have expired now
+ expect(lease.try_obtain).to eq(true)
+ end
+
+ def unique_key
+ SecureRandom.hex(10)
+ end
+end
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
new file mode 100644
index 00000000000..a324a82e69f
--- /dev/null
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::CommentFormatter, lib: true do
+ let(:project) { create(:project) }
+ let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') }
+ let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') }
+ let(:base_data) do
+ {
+ body: "I'm having a problem with this.",
+ user: octocat,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+ end
+
+ subject(:comment) { described_class.new(project, raw_data)}
+
+ describe '#attributes' do
+ context 'when do not reference a portion of the diff' do
+ let(:raw_data) { OpenStruct.new(base_data) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ project: project,
+ note: "*Created by: octocat*\n\nI'm having a problem with this.",
+ commit_id: nil,
+ line_code: nil,
+ author_id: project.creator_id,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(comment.attributes).to eq(expected)
+ end
+ end
+
+ context 'when on a portion of the diff' do
+ let(:diff_data) do
+ {
+ body: 'Great stuff',
+ commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
+ diff_hunk: '@@ -16,33 +16,40 @@ public class Connection : IConnection...',
+ path: 'file1.txt',
+ position: 1
+ }
+ end
+
+ let(:raw_data) { OpenStruct.new(base_data.merge(diff_data)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ project: project,
+ note: "*Created by: octocat*\n\nGreat stuff",
+ commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
+ line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_0_1',
+ author_id: project.creator_id,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(comment.attributes).to eq(expected)
+ end
+ end
+
+ context 'when author is a GitLab user' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+
+ it 'returns project#creator_id as author_id when is not a GitLab user' do
+ expect(comment.attributes.fetch(:author_id)).to eq project.creator_id
+ end
+
+ it 'returns GitLab user id as author_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
new file mode 100644
index 00000000000..fd05428b322
--- /dev/null
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -0,0 +1,139 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::IssueFormatter, lib: true do
+ let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
+ let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+ let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
+
+ let(:base_data) do
+ {
+ number: 1347,
+ state: 'open',
+ title: 'Found a bug',
+ body: "I'm having a problem with this.",
+ assignee: nil,
+ user: octocat,
+ comments: 0,
+ pull_request: nil,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil
+ }
+ end
+
+ subject(:issue) { described_class.new(project, raw_data)}
+
+ describe '#attributes' do
+ context 'when issue is open' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'open')) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ project: project,
+ title: 'Found a bug',
+ description: "*Created by: octocat*\n\nI'm having a problem with this.",
+ state: 'opened',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(issue.attributes).to eq(expected)
+ end
+ end
+
+ context 'when issue is closed' do
+ let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', closed_at: closed_at)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ project: project,
+ title: 'Found a bug',
+ description: "*Created by: octocat*\n\nI'm having a problem with this.",
+ state: 'closed',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: closed_at
+ }
+
+ expect(issue.attributes).to eq(expected)
+ end
+ end
+
+ context 'when it is assigned to someone' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(assignee: octocat)) }
+
+ it 'returns nil as assignee_id when is not a GitLab user' do
+ expect(issue.attributes.fetch(:assignee_id)).to be_nil
+ end
+
+ it 'returns GitLab user id as assignee_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(issue.attributes.fetch(:assignee_id)).to eq gl_user.id
+ end
+ end
+
+ context 'when author is a GitLab user' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+
+ it 'returns project#creator_id as author_id when is not a GitLab user' do
+ expect(issue.attributes.fetch(:author_id)).to eq project.creator_id
+ end
+
+ it 'returns GitLab user id as author_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(issue.attributes.fetch(:author_id)).to eq gl_user.id
+ end
+ end
+ end
+
+ describe '#has_comments?' do
+ context 'when number of comments is greater than zero' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(comments: 1)) }
+
+ it 'returns true' do
+ expect(issue.has_comments?).to eq true
+ end
+ end
+
+ context 'when number of comments is equal to zero' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(comments: 0)) }
+
+ it 'returns false' do
+ expect(issue.has_comments?).to eq false
+ end
+ end
+ end
+
+ describe '#number' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) }
+
+ it 'returns pull request number' do
+ expect(issue.number).to eq 1347
+ end
+ end
+
+ describe '#valid?' do
+ context 'when mention a pull request' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(pull_request: OpenStruct.new)) }
+
+ it 'returns false' do
+ expect(issue.valid?).to eq false
+ end
+ end
+
+ context 'when does not mention a pull request' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(pull_request: nil)) }
+
+ it 'returns true' do
+ expect(issue.valid?).to eq true
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
new file mode 100644
index 00000000000..e49dcb42342
--- /dev/null
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -0,0 +1,185 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
+ let(:project) { create(:project) }
+ let(:repository) { OpenStruct.new(id: 1, fork: false) }
+ let(:source_repo) { repository }
+ let(:source_branch) { OpenStruct.new(ref: 'feature', repo: source_repo) }
+ let(:target_repo) { repository }
+ let(:target_branch) { OpenStruct.new(ref: 'master', repo: target_repo) }
+ let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+ let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
+ let(:base_data) do
+ {
+ number: 1347,
+ state: 'open',
+ title: 'New feature',
+ body: 'Please pull these awesome changes',
+ head: source_branch,
+ base: target_branch,
+ assignee: nil,
+ user: octocat,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ merged_at: nil
+ }
+ end
+
+ subject(:pull_request) { described_class.new(project, raw_data)}
+
+ describe '#attributes' do
+ context 'when pull request is open' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'open')) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ title: 'New feature',
+ description: "*Created by: octocat*\n\nPlease pull these awesome changes",
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master',
+ state: 'opened',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(pull_request.attributes).to eq(expected)
+ end
+ end
+
+ context 'when pull request is closed' do
+ let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', closed_at: closed_at)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ title: 'New feature',
+ description: "*Created by: octocat*\n\nPlease pull these awesome changes",
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master',
+ state: 'closed',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: closed_at
+ }
+
+ expect(pull_request.attributes).to eq(expected)
+ end
+ end
+
+ context 'when pull request is merged' do
+ let(:merged_at) { DateTime.strptime('2011-01-28T13:01:12Z') }
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', merged_at: merged_at)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ title: 'New feature',
+ description: "*Created by: octocat*\n\nPlease pull these awesome changes",
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master',
+ state: 'merged',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: merged_at
+ }
+
+ expect(pull_request.attributes).to eq(expected)
+ end
+ end
+
+ context 'when it is assigned to someone' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(assignee: octocat)) }
+
+ it 'returns nil as assignee_id when is not a GitLab user' do
+ expect(pull_request.attributes.fetch(:assignee_id)).to be_nil
+ end
+
+ it 'returns GitLab user id as assignee_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(pull_request.attributes.fetch(:assignee_id)).to eq gl_user.id
+ end
+ end
+
+ context 'when author is a GitLab user' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+
+ it 'returns project#creator_id as author_id when is not a GitLab user' do
+ expect(pull_request.attributes.fetch(:author_id)).to eq project.creator_id
+ end
+
+ it 'returns GitLab user id as author_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
+ end
+ end
+ end
+
+ describe '#number' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) }
+
+ it 'returns pull request number' do
+ expect(pull_request.number).to eq 1347
+ end
+ end
+
+ describe '#valid?' do
+ let(:invalid_branch) { OpenStruct.new(ref: 'invalid-branch') }
+
+ context 'when source, and target repositories are the same' do
+ context 'and source and target branches exists' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) }
+
+ it 'returns true' do
+ expect(pull_request.valid?).to eq true
+ end
+ end
+
+ context 'and source branch doesn not exists' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: invalid_branch, base: target_branch)) }
+
+ it 'returns false' do
+ expect(pull_request.valid?).to eq false
+ end
+ end
+
+ context 'and target branch doesn not exists' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) }
+
+ it 'returns false' do
+ expect(pull_request.valid?).to eq false
+ end
+ end
+ end
+
+ context 'when source repo is a fork' do
+ let(:source_repo) { OpenStruct.new(id: 2, fork: true) }
+ let(:raw_data) { OpenStruct.new(base_data) }
+
+ it 'returns false' do
+ expect(pull_request.valid?).to eq false
+ end
+ end
+
+ context 'when target repo is a fork' do
+ let(:target_repo) { OpenStruct.new(id: 2, fork: true) }
+ let(:raw_data) { OpenStruct.new(base_data) }
+
+ it 'returns false' do
+ expect(pull_request.valid?).to eq false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
new file mode 100644
index 00000000000..aed2aa39e3a
--- /dev/null
+++ b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::WikiFormatter, lib: true do
+ let(:project) do
+ create(:project, namespace: create(:namespace, path: 'gitlabhq'),
+ import_url: 'https://xxx@github.com/gitlabhq/sample.gitlabhq.git')
+ end
+
+ subject(:wiki) { described_class.new(project)}
+
+ describe '#path_with_namespace' do
+ it 'appends .wiki to project path' do
+ expect(wiki.path_with_namespace).to eq 'gitlabhq/gitlabhq.wiki'
+ end
+ end
+
+ describe '#import_url' do
+ it 'returns URL of the wiki repository' do
+ expect(wiki.import_url).to eq 'https://xxx@github.com/gitlabhq/sample.gitlabhq.wiki.git'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
new file mode 100644
index 00000000000..1620eb6c60a
--- /dev/null
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::Highlight, lib: true do
+ include RepoHelpers
+
+ let(:project) { create(:project) }
+ let(:commit) { project.commit(sample_commit.id) }
+
+ describe '.highlight_lines' do
+ let(:lines) do
+ Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
+ end
+
+ it 'should properly highlight all the lines' do
+ expect(lines[4]).to eq(%Q{<span id="LC5" class="line"> <span class="kp">extend</span> <span class="nb">self</span></span>\n})
+ expect(lines[21]).to eq(%Q{<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
+ expect(lines[26]).to eq(%Q{<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
+ end
+ end
+
+end
diff --git a/spec/lib/gitlab/inline_diff_spec.rb b/spec/lib/gitlab/inline_diff_spec.rb
deleted file mode 100644
index c690c195112..00000000000
--- a/spec/lib/gitlab/inline_diff_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::InlineDiff, lib: true do
- describe '#processing' do
- let(:diff) do
- <<eos
---- a/test.rb
-+++ b/test.rb
-@@ -1,6 +1,6 @@
- class Test
- def cleanup_string(input)
- return nil if input.nil?
-- input.sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip
-+ input.to_s.sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip
- end
- end
-eos
- end
-
- let(:expected) do
- ["--- a/test.rb\n",
- "+++ b/test.rb\n",
- "@@ -1,6 +1,6 @@\n",
- " class Test\n",
- " def cleanup_string(input)\n",
- " return nil if input.nil?\n",
- "- input.#!idiff-start!##!idiff-finish!#sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip\n",
- "+ input.#!idiff-start!#to_s.#!idiff-finish!#sub(/[\\r\\n].+/,'').sub(/\\\\[rn].+/, '').strip\n",
- " end\n",
- " end\n"]
- end
-
- let(:subject) { Gitlab::InlineDiff.processing(diff.lines) }
-
- it 'should retain backslashes' do
- expect(subject).to eq(expected)
- end
- end
-end
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index a628d0c0157..32a19bf344b 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -13,64 +13,58 @@ describe Gitlab::LDAP::Access, lib: true do
end
it { is_expected.to be_falsey }
-
+
it 'should block user in GitLab' do
access.allowed?
expect(user).to be_blocked
+ expect(user).to be_ldap_blocked
end
end
context 'when the user is found' do
before do
- allow(Gitlab::LDAP::Person).
- to receive(:find_by_dn).and_return(:ldap_user)
+ allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(:ldap_user)
end
context 'and the user is disabled via active directory' do
before do
- allow(Gitlab::LDAP::Person).
- to receive(:disabled_via_active_directory?).and_return(true)
+ allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(true)
end
it { is_expected.to be_falsey }
- it "should block user in GitLab" do
+ it 'should block user in GitLab' do
access.allowed?
expect(user).to be_blocked
+ expect(user).to be_ldap_blocked
end
end
context 'and has no disabled flag in active diretory' do
before do
- user.block
-
- allow(Gitlab::LDAP::Person).
- to receive(:disabled_via_active_directory?).and_return(false)
+ allow(Gitlab::LDAP::Person).to receive(:disabled_via_active_directory?).and_return(false)
end
it { is_expected.to be_truthy }
context 'when auto-created users are blocked' do
-
before do
- allow_any_instance_of(Gitlab::LDAP::Config).
- to receive(:block_auto_created_users).and_return(true)
+ user.block
end
- it "does not unblock user in GitLab" do
+ it 'does not unblock user in GitLab' do
access.allowed?
expect(user).to be_blocked
+ expect(user).not_to be_ldap_blocked # this block is handled by omniauth not by our internal logic
end
end
- context "when auto-created users are not blocked" do
-
+ context 'when auto-created users are not blocked' do
before do
- allow_any_instance_of(Gitlab::LDAP::Config).
- to receive(:block_auto_created_users).and_return(false)
+ user.ldap_block
end
- it "should unblock user in GitLab" do
+ it 'should unblock user in GitLab' do
access.allowed?
expect(user).not_to be_blocked
end
@@ -80,8 +74,7 @@ describe Gitlab::LDAP::Access, lib: true do
context 'without ActiveDirectory enabled' do
before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
- allow_any_instance_of(Gitlab::LDAP::Config).
- to receive(:active_directory).and_return(false)
+ allow_any_instance_of(Gitlab::LDAP::Config).to receive(:active_directory).and_return(false)
end
it { is_expected.to be_truthy }
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 1e755259dae..03199a2523e 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -37,7 +37,7 @@ describe Gitlab::LDAP::User, lib: true do
end
it "dont marks existing ldap user as changed" do
- create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain')
+ create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain', ldap_email: true)
expect(ldap_user.changed?).to be_falsey
end
end
@@ -110,6 +110,32 @@ describe Gitlab::LDAP::User, lib: true do
end
end
+ describe 'updating email' do
+ context "when LDAP sets an email" do
+ it "has a real email" do
+ expect(ldap_user.gl_user.email).to eq(info[:email])
+ end
+
+ it "has ldap_email set to true" do
+ expect(ldap_user.gl_user.ldap_email?).to be(true)
+ end
+ end
+
+ context "when LDAP doesn't set an email" do
+ before do
+ info.delete(:email)
+ end
+
+ it "has a temp email" do
+ expect(ldap_user.gl_user.temp_oauth_email?).to be(true)
+ end
+
+ it "has ldap_email set to false" do
+ expect(ldap_user.gl_user.ldap_email?).to be(false)
+ end
+ end
+ end
+
describe 'blocking' do
def configure_block(value)
allow_any_instance_of(Gitlab::LDAP::Config).
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index a7eab9d11cc..ad4290c43bb 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -48,6 +48,9 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
+ expect(transaction).to receive(:increment).
+ with(:method_duration, a_kind_of(Numeric))
+
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy.foo')
@@ -63,6 +66,16 @@ describe Gitlab::Metrics::Instrumentation do
@dummy.foo
end
+
+ it 'generates a method with the correct arity when using methods without arguments' do
+ dummy = Class.new do
+ def self.test; end
+ end
+
+ described_class.instrument_method(dummy, :test)
+
+ expect(dummy.method(:test).arity).to eq(0)
+ end
end
describe 'with metrics disabled' do
@@ -102,6 +115,9 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
+ expect(transaction).to receive(:increment).
+ with(:method_duration, a_kind_of(Numeric))
+
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy#bar')
diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb
index aa76315c79c..f718d536130 100644
--- a/spec/lib/gitlab/metrics/metric_spec.rb
+++ b/spec/lib/gitlab/metrics/metric_spec.rb
@@ -37,9 +37,6 @@ describe Gitlab::Metrics::Metric do
it 'includes the tags' do
expect(hash[:tags]).to be_an_instance_of(Hash)
-
- expect(hash[:tags][:hostname]).to be_an_instance_of(String)
- expect(hash[:tags][:process_type]).to be_an_instance_of(String)
end
it 'includes the values' do
diff --git a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb
deleted file mode 100644
index 2b681c9fe34..00000000000
--- a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Metrics::ObfuscatedSQL do
- describe '#to_s' do
- it 'replaces newlines with a space' do
- sql = described_class.new("SELECT x\nFROM y")
-
- expect(sql.to_s).to eq('SELECT x FROM y')
- end
-
- describe 'using single values' do
- it 'replaces a single integer' do
- sql = described_class.new('SELECT x FROM y WHERE a = 10')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
-
- it 'replaces a single float' do
- sql = described_class.new('SELECT x FROM y WHERE a = 10.5')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
-
- it 'replaces a single quoted string' do
- sql = described_class.new("SELECT x FROM y WHERE a = 'foo'")
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
-
- if Gitlab::Database.mysql?
- it 'replaces a double quoted string' do
- sql = described_class.new('SELECT x FROM y WHERE a = "foo"')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
- end
-
- it 'replaces a single regular expression' do
- sql = described_class.new('SELECT x FROM y WHERE a = /foo/')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
-
- it 'replaces regular expressions using escaped slashes' do
- sql = described_class.new('SELECT x FROM y WHERE a = /foo\/bar/')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
- end
-
- describe 'using consecutive values' do
- it 'replaces multiple integers' do
- sql = described_class.new('SELECT x FROM y WHERE z IN (10, 20, 30)')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (3 values)')
- end
-
- it 'replaces multiple floats' do
- sql = described_class.new('SELECT x FROM y WHERE z IN (1.5, 2.5, 3.5)')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (3 values)')
- end
-
- it 'replaces multiple single quoted strings' do
- sql = described_class.new("SELECT x FROM y WHERE z IN ('foo', 'bar')")
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
- end
-
- if Gitlab::Database.mysql?
- it 'replaces multiple double quoted strings' do
- sql = described_class.new('SELECT x FROM y WHERE z IN ("foo", "bar")')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
- end
- end
-
- it 'replaces multiple regular expressions' do
- sql = described_class.new('SELECT x FROM y WHERE z IN (/foo/, /bar/)')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
- end
- end
-
- if Gitlab::Database.postgresql?
- it 'replaces double quotes' do
- sql = described_class.new('SELECT "x" FROM "y"')
-
- expect(sql.to_s).to eq('SELECT x FROM y')
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index a143fe4cfcd..b99be4e1060 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -40,9 +40,9 @@ describe Gitlab::Metrics::RackMiddleware do
expect(transaction).to be_an_instance_of(Gitlab::Metrics::Transaction)
end
- it 'tags the transaction with the request method and URI' do
- expect(transaction.tags[:request_method]).to eq('GET')
- expect(transaction.tags[:request_uri]).to eq('/foo')
+ it 'stores the request method and URI in the transaction as values' do
+ expect(transaction.values[:request_method]).to eq('GET')
+ expect(transaction.values[:request_uri]).to eq('/foo')
end
end
@@ -57,7 +57,7 @@ describe Gitlab::Metrics::RackMiddleware do
middleware.tag_controller(transaction, env)
- expect(transaction.tags[:action]).to eq('TestController#show')
+ expect(transaction.action).to eq('TestController#show')
end
end
end
diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb
index 51a941c48cd..38da77adc9f 100644
--- a/spec/lib/gitlab/metrics/sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/sampler_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Metrics::Sampler do
describe '#start' do
it 'gathers a sample at a given interval' do
- expect(sampler).to receive(:sleep).with(5)
+ expect(sampler).to receive(:sleep).with(a_kind_of(Numeric))
expect(sampler).to receive(:sample)
expect(sampler).to receive(:loop).and_yield
@@ -51,8 +51,8 @@ describe Gitlab::Metrics::Sampler do
expect(Gitlab::Metrics::System).to receive(:memory_usage).
and_return(9000)
- expect(Gitlab::Metrics::Metric).to receive(:new).
- with('memory_usage', value: 9000).
+ expect(sampler).to receive(:add_metric).
+ with(/memory_usage/, value: 9000).
and_call_original
sampler.sample_memory_usage
@@ -64,8 +64,8 @@ describe Gitlab::Metrics::Sampler do
expect(Gitlab::Metrics::System).to receive(:file_descriptor_count).
and_return(4)
- expect(Gitlab::Metrics::Metric).to receive(:new).
- with('file_descriptors', value: 4).
+ expect(sampler).to receive(:add_metric).
+ with(/file_descriptors/, value: 4).
and_call_original
sampler.sample_file_descriptors
@@ -74,8 +74,8 @@ describe Gitlab::Metrics::Sampler do
describe '#sample_objects' do
it 'adds a metric containing the amount of allocated objects' do
- expect(Gitlab::Metrics::Metric).to receive(:new).
- with('object_counts', an_instance_of(Hash), an_instance_of(Hash)).
+ expect(sampler).to receive(:add_metric).
+ with(/object_counts/, an_instance_of(Hash), an_instance_of(Hash)).
at_least(:once).
and_call_original
@@ -87,11 +87,53 @@ describe Gitlab::Metrics::Sampler do
it 'adds a metric containing garbage collection statistics' do
expect(GC::Profiler).to receive(:total_time).and_return(0.24)
- expect(Gitlab::Metrics::Metric).to receive(:new).
- with('gc_statistics', an_instance_of(Hash)).
+ expect(sampler).to receive(:add_metric).
+ with(/gc_statistics/, an_instance_of(Hash)).
and_call_original
sampler.sample_gc
end
end
+
+ describe '#add_metric' do
+ it 'prefixes the series name for a Rails process' do
+ expect(sampler).to receive(:sidekiq?).and_return(false)
+
+ expect(Gitlab::Metrics::Metric).to receive(:new).
+ with('rails_cats', { value: 10 }, {}).
+ and_call_original
+
+ sampler.add_metric('cats', value: 10)
+ end
+
+ it 'prefixes the series name for a Sidekiq process' do
+ expect(sampler).to receive(:sidekiq?).and_return(true)
+
+ expect(Gitlab::Metrics::Metric).to receive(:new).
+ with('sidekiq_cats', { value: 10 }, {}).
+ and_call_original
+
+ sampler.add_metric('cats', value: 10)
+ end
+ end
+
+ describe '#sleep_interval' do
+ it 'returns a Numeric' do
+ expect(sampler.sleep_interval).to be_a_kind_of(Numeric)
+ end
+
+ # Testing random behaviour is very hard, so treat this test as a basic smoke
+ # test instead of a very accurate behaviour/unit test.
+ it 'does not return the same interval twice in a row' do
+ last = nil
+
+ 100.times do
+ interval = sampler.sleep_interval
+
+ expect(interval).to_not eq(last)
+
+ last = interval
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
index 5882e7d81c7..e520a968999 100644
--- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -5,22 +5,15 @@ describe Gitlab::Metrics::SidekiqMiddleware do
describe '#call' do
it 'tracks the transaction' do
- worker = Class.new.new
+ worker = double(:worker, class: double(:class, name: 'TestWorker'))
+
+ expect(Gitlab::Metrics::Transaction).to receive(:new).
+ with('TestWorker#perform').
+ and_call_original
expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
middleware.call(worker, 'test', :test) { nil }
end
end
-
- describe '#tag_worker' do
- it 'adds the worker class and action to the transaction' do
- trans = Gitlab::Metrics::Transaction.new
- worker = double(:worker, class: double(:class, name: 'TestWorker'))
-
- expect(trans).to receive(:add_tag).with(:action, 'TestWorker#perform')
-
- middleware.tag_worker(trans, worker)
- end
- end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
index c6cd584663f..0695c5ce096 100644
--- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -14,19 +14,15 @@ describe Gitlab::Metrics::Subscribers::ActionView do
before do
allow(subscriber).to receive(:current_transaction).and_return(transaction)
-
- allow(Gitlab::Metrics).to receive(:last_relative_application_frame).
- and_return(['app/views/x.html.haml', 4])
end
describe '#render_template' do
it 'tracks rendering of a template' do
values = { duration: 2.1 }
- tags = {
- view: 'app/views/x.html.haml',
- file: 'app/views/x.html.haml',
- line: 4
- }
+ tags = { view: 'app/views/x.html.haml' }
+
+ expect(transaction).to receive(:increment).
+ with(:view_duration, 2.1)
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, values, tags)
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index 05b6cc14716..7bc070a4d09 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -2,31 +2,34 @@ require 'spec_helper'
describe Gitlab::Metrics::Subscribers::ActiveRecord do
let(:transaction) { Gitlab::Metrics::Transaction.new }
-
- let(:subscriber) { described_class.new }
+ let(:subscriber) { described_class.new }
let(:event) do
double(:event, duration: 0.2,
payload: { sql: 'SELECT * FROM users WHERE id = 10' })
end
- before do
- allow(subscriber).to receive(:current_transaction).and_return(transaction)
+ describe '#sql' do
+ describe 'without a current transaction' do
+ it 'simply returns' do
+ expect_any_instance_of(Gitlab::Metrics::Transaction).
+ to_not receive(:increment)
- allow(Gitlab::Metrics).to receive(:last_relative_application_frame).
- and_return(['app/models/foo.rb', 4])
- end
+ subscriber.sql(event)
+ end
+ end
- describe '#sql' do
- it 'tracks the execution of a SQL query' do
- sql = 'SELECT * FROM users WHERE id = ?'
- values = { duration: 0.2 }
- tags = { sql: sql, file: 'app/models/foo.rb', line: 4 }
+ describe 'with a current transaction' do
+ it 'increments the :sql_duration value' do
+ expect(subscriber).to receive(:current_transaction).
+ at_least(:once).
+ and_return(transaction)
- expect(transaction).to receive(:add_metric).
- with(described_class::SERIES, values, tags)
+ expect(transaction).to receive(:increment).
+ with(:sql_duration, 0.2)
- subscriber.sql(event)
+ subscriber.sql(event)
+ end
end
end
end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 6862fc9e2d1..1d5a51a157e 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -11,6 +11,14 @@ describe Gitlab::Metrics::Transaction do
end
end
+ describe '#allocated_memory' do
+ it 'returns the allocated memory in bytes' do
+ transaction.run { 'a' * 32 }
+
+ expect(transaction.allocated_memory).to be_a_kind_of(Numeric)
+ end
+ end
+
describe '#run' do
it 'yields the supplied block' do
expect { |b| transaction.run(&b) }.to yield_control
@@ -30,14 +38,45 @@ describe Gitlab::Metrics::Transaction do
end
describe '#add_metric' do
- it 'adds a metric tagged with the transaction UUID' do
+ it 'adds a metric to the transaction' do
expect(Gitlab::Metrics::Metric).to receive(:new).
- with('foo', { number: 10 }, { transaction_id: transaction.uuid })
+ with('rails_foo', { number: 10 }, {})
transaction.add_metric('foo', number: 10)
end
end
+ describe '#increment' do
+ it 'increments a counter' do
+ transaction.increment(:time, 1)
+ transaction.increment(:time, 2)
+
+ values = { duration: 0.0, time: 3, allocated_memory: a_kind_of(Numeric) }
+
+ expect(transaction).to receive(:add_metric).
+ with('transactions', values, {})
+
+ transaction.track_self
+ end
+ end
+
+ describe '#set' do
+ it 'sets a value' do
+ transaction.set(:number, 10)
+
+ values = {
+ duration: 0.0,
+ number: 10,
+ allocated_memory: a_kind_of(Numeric)
+ }
+
+ expect(transaction).to receive(:add_metric).
+ with('transactions', values, {})
+
+ transaction.track_self
+ end
+ end
+
describe '#add_tag' do
it 'adds a tag' do
transaction.add_tag(:foo, 'bar')
@@ -57,8 +96,13 @@ describe Gitlab::Metrics::Transaction do
describe '#track_self' do
it 'adds a metric for the transaction itself' do
+ values = {
+ duration: transaction.duration,
+ allocated_memory: a_kind_of(Numeric)
+ }
+
expect(transaction).to receive(:add_metric).
- with(described_class::SERIES, { duration: transaction.duration }, {})
+ with('transactions', values, {})
transaction.track_self
end
@@ -73,5 +117,22 @@ describe Gitlab::Metrics::Transaction do
transaction.submit
end
+
+ it 'adds the action as a tag for every metric' do
+ transaction.action = 'Foo#bar'
+ transaction.track_self
+
+ hash = {
+ series: 'rails_transactions',
+ tags: { action: 'Foo#bar' },
+ values: { duration: 0.0, allocated_memory: a_kind_of(Numeric) },
+ timestamp: an_instance_of(Fixnum)
+ }
+
+ expect(Gitlab::Metrics).to receive(:submit_metrics).
+ with([hash])
+
+ transaction.submit
+ end
end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 6c0682cac4d..0ec8a6dc5cb 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -1,15 +1,9 @@
require 'spec_helper'
describe Gitlab::Metrics do
- describe '.pool_size' do
- it 'returns a Fixnum' do
- expect(described_class.pool_size).to be_an_instance_of(Fixnum)
- end
- end
-
- describe '.timeout' do
- it 'returns a Fixnum' do
- expect(described_class.timeout).to be_an_instance_of(Fixnum)
+ describe '.settings' do
+ it 'returns a Hash' do
+ expect(described_class.settings).to be_an_instance_of(Hash)
end
end
@@ -19,21 +13,6 @@ describe Gitlab::Metrics do
end
end
- describe '.hostname' do
- it 'returns a String containing the hostname' do
- expect(described_class.hostname).to eq(Socket.gethostname)
- end
- end
-
- describe '.last_relative_application_frame' do
- it 'returns an Array containing a file path and line number' do
- file, line = described_class.last_relative_application_frame
-
- expect(line).to eq(__LINE__ - 2)
- expect(file).to eq('spec/lib/gitlab/metrics_spec.rb')
- end
- end
-
describe '#submit_metrics' do
it 'prepares and writes the metrics to InfluxDB' do
connection = double(:connection)
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
new file mode 100644
index 00000000000..117a15264da
--- /dev/null
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Gitlab::Middleware::Go, lib: true do
+ let(:app) { double(:app) }
+ let(:middleware) { described_class.new(app) }
+
+ describe '#call' do
+ describe 'when go-get=0' do
+ it 'skips go-import generation' do
+ env = { 'rack.input' => '',
+ 'QUERY_STRING' => 'go-get=0' }
+ expect(app).to receive(:call).with(env).and_return('no-go')
+ middleware.call(env)
+ end
+ end
+
+ describe 'when go-get=1' do
+ it 'returns a document' do
+ env = { 'rack.input' => '',
+ 'QUERY_STRING' => 'go-get=1',
+ 'PATH_INFO' => '/group/project/path' }
+ resp = middleware.call(env)
+ expect(resp[0]).to eq(200)
+ expect(resp[1]['Content-Type']).to eq('text/html')
+ expected_body = "<!DOCTYPE html><html><head><meta content='localhost/group/project git http://localhost/group/project.git' name='go-import'></head></html>\n"
+ expect(resp[2].body).to eq([expected_body])
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb
index 6cbdae737f4..da652677443 100644
--- a/spec/lib/gitlab/note_data_builder_spec.rb
+++ b/spec/lib/gitlab/note_data_builder_spec.rb
@@ -16,58 +16,80 @@ describe 'Gitlab::NoteDataBuilder', lib: true do
end
describe 'When asking for a note on commit' do
- let(:note) { create(:note_on_commit) }
+ let(:note) { create(:note_on_commit, project: project) }
it 'returns the note and commit-specific data' do
expect(data).to have_key(:commit)
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on commit diff' do
- let(:note) { create(:note_on_commit_diff) }
+ let(:note) { create(:note_on_commit_diff, project: project) }
it 'returns the note and commit-specific data' do
expect(data).to have_key(:commit)
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on issue' do
let(:issue) { create(:issue, created_at: fixed_time, updated_at: fixed_time) }
- let(:note) { create(:note_on_issue, noteable_id: issue.id) }
+ let(:note) { create(:note_on_issue, noteable_id: issue.id, project: project) }
it 'returns the note and issue-specific data' do
expect(data).to have_key(:issue)
- expect(data[:issue]).to eq(issue.hook_attrs)
+ expect(data[:issue].except('updated_at')).to eq(issue.hook_attrs.except('updated_at'))
+ expect(data[:issue]['updated_at']).to be > issue.hook_attrs['updated_at']
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on merge request' do
let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
- let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) }
+ let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id, project: project) }
it 'returns the note and merge request data' do
expect(data).to have_key(:merge_request)
- expect(data[:merge_request]).to eq(merge_request.hook_attrs)
+ expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
+ expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on merge request diff' do
let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
- let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) }
+ let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id, project: project) }
it 'returns the note and merge request diff data' do
expect(data).to have_key(:merge_request)
- expect(data[:merge_request]).to eq(merge_request.hook_attrs)
+ expect(data[:merge_request].except('updated_at')).to eq(merge_request.hook_attrs.except('updated_at'))
+ expect(data[:merge_request]['updated_at']).to be > merge_request.hook_attrs['updated_at']
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe 'When asking for a note on project snippet' do
let!(:snippet) { create(:project_snippet, created_at: fixed_time, updated_at: fixed_time) }
- let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) }
+ let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id, project: project) }
it 'returns the note and project snippet data' do
expect(data).to have_key(:snippet)
- expect(data[:snippet]).to eq(snippet.hook_attrs)
+ expect(data[:snippet].except('updated_at')).to eq(snippet.hook_attrs.except('updated_at'))
+ expect(data[:snippet]['updated_at']).to be > snippet.hook_attrs['updated_at']
end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 925bc442a90..3a769acfdc0 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -41,7 +41,20 @@ describe Gitlab::OAuth::User, lib: true do
describe 'signup' do
shared_examples "to verify compliance with allow_single_sign_on" do
- context "with allow_single_sign_on enabled" do
+ context "with new allow_single_sign_on enabled syntax" do
+ before { stub_omniauth_config(allow_single_sign_on: ['twitter']) }
+
+ it "creates a user from Omniauth" do
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'twitter'
+ end
+ end
+
+ context "with old allow_single_sign_on enabled syntax" do
before { stub_omniauth_config(allow_single_sign_on: true) }
it "creates a user from Omniauth" do
@@ -54,7 +67,14 @@ describe Gitlab::OAuth::User, lib: true do
end
end
- context "with allow_single_sign_on disabled (Default)" do
+ context "with new allow_single_sign_on disabled syntax" do
+ before { stub_omniauth_config(allow_single_sign_on: []) }
+ it "throws an error" do
+ expect{ oauth_user.save }.to raise_error StandardError
+ end
+ end
+
+ context "with old allow_single_sign_on disabled (Default)" do
before { stub_omniauth_config(allow_single_sign_on: false) }
it "throws an error" do
expect{ oauth_user.save }.to raise_error StandardError
@@ -135,7 +155,7 @@ describe Gitlab::OAuth::User, lib: true do
describe 'blocking' do
let(:provider) { 'twitter' }
- before { stub_omniauth_config(allow_single_sign_on: true) }
+ before { stub_omniauth_config(allow_single_sign_on: ['twitter']) }
context 'signup with omniauth only' do
context 'dont block on create' do
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index efc2e5f4ef1..db0ff95b4f5 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -1,11 +1,12 @@
require 'spec_helper'
describe Gitlab::ProjectSearchResults, lib: true do
+ let(:user) { create(:user) }
let(:project) { create(:project) }
let(:query) { 'hello world' }
describe 'initialize with empty ref' do
- let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, '') }
+ let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, '') }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to be_nil }
@@ -14,10 +15,74 @@ describe Gitlab::ProjectSearchResults, lib: true do
describe 'initialize with ref' do
let(:ref) { 'refs/heads/test' }
- let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, ref) }
+ let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, ref) }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to eq(ref) }
it { expect(results.query).to eq('hello world') }
end
+
+ describe 'confidential issues' do
+ let(:query) { 'issue' }
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
+ let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
+
+ it 'should not list project confidential issues for non project members' do
+ results = described_class.new(non_member, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(results.issues_count).to eq 1
+ end
+
+ it 'should list project confidential issues for author' do
+ results = described_class.new(author, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(results.issues_count).to eq 2
+ end
+
+ it 'should list project confidential issues for assignee' do
+ results = described_class.new(assignee, project.id, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(results.issues_count).to eq 2
+ end
+
+ it 'should list project confidential issues for project members' do
+ project.team << [member, :developer]
+
+ results = described_class.new(member, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(results.issues_count).to eq 3
+ end
+
+ it 'should list all project issues for admin' do
+ results = described_class.new(admin, project, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(results.issues_count).to eq 3
+ end
+ end
end
diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb
index 3ef61685398..961022b9d12 100644
--- a/spec/lib/gitlab/push_data_builder_spec.rb
+++ b/spec/lib/gitlab/push_data_builder_spec.rb
@@ -1,34 +1,32 @@
require 'spec_helper'
-describe 'Gitlab::PushDataBuilder', lib: true do
+describe Gitlab::PushDataBuilder, lib: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
- describe :build_sample do
- let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+ describe '.build_sample' do
+ let(:data) { described_class.build_sample(project, user) }
it { expect(data).to be_a(Hash) }
it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
it { expect(data[:ref]).to eq('refs/heads/master') }
it { expect(data[:commits].size).to eq(3) }
- it { expect(data[:repository][:git_http_url]).to eq(project.http_url_to_repo) }
- it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) }
- it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) }
it { expect(data[:total_commits_count]).to eq(3) }
it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) }
it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
it { expect(data[:commits].first[:removed]).to eq([]) }
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
- describe :build do
+ describe '.build' do
let(:data) do
- Gitlab::PushDataBuilder.build(project,
- user,
- Gitlab::Git::BLANK_SHA,
- '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
- 'refs/tags/v1.1.0')
+ described_class.build(project, user, Gitlab::Git::BLANK_SHA,
+ '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
+ 'refs/tags/v1.1.0')
end
it { expect(data).to be_a(Hash) }
@@ -38,5 +36,10 @@ describe 'Gitlab::PushDataBuilder', lib: true do
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
it { expect(data[:commits]).to be_empty }
it { expect(data[:total_commits_count]).to be_zero }
+
+ it 'does not raise an error when given nil commits' do
+ expect { described_class.build(spy, spy, spy, spy, spy, nil) }.
+ not_to raise_error
+ end
end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 7d963795e17..65af37e24f1 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::ReferenceExtractor, lib: true do
let(:project) { create(:project) }
+
subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
it 'accesses valid user objects' do
@@ -41,6 +42,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
end
it 'accesses valid issue objects' do
+ project.team << [project.creator, :developer]
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index d67ee423b9b..c51b10bdc69 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -21,4 +21,12 @@ describe Gitlab::Regex, lib: true do
it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) }
it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
end
+
+ describe 'file name regex' do
+ it { expect('foo@bar').to match(Gitlab::Regex.file_name_regex) }
+ end
+
+ describe 'file path regex' do
+ it { expect('foo@/bar').to match(Gitlab::Regex.file_path_regex) }
+ end
end
diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb
new file mode 100644
index 00000000000..de7cd99d49d
--- /dev/null
+++ b/spec/lib/gitlab/saml/user_spec.rb
@@ -0,0 +1,271 @@
+require 'spec_helper'
+
+describe Gitlab::Saml::User, lib: true do
+ let(:saml_user) { described_class.new(auth_hash) }
+ let(:gl_user) { saml_user.gl_user }
+ let(:uid) { 'my-uid' }
+ let(:provider) { 'saml' }
+ let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash) }
+ let(:info_hash) do
+ {
+ name: 'John',
+ email: 'john@mail.com'
+ }
+ end
+ let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
+
+ describe '#save' do
+ def stub_omniauth_config(messages)
+ allow(Gitlab.config.omniauth).to receive_messages(messages)
+ end
+
+ def stub_ldap_config(messages)
+ allow(Gitlab::LDAP::Config).to receive_messages(messages)
+ end
+
+ describe 'account exists on server' do
+ before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) }
+ context 'and should bind with SAML' do
+ let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') }
+ it 'adds the SAML identity to the existing user' do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to eq existing_user
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'saml'
+ end
+ end
+ end
+
+ describe 'no account exists on server' do
+ shared_examples 'to verify compliance with allow_single_sign_on' do
+ context 'with allow_single_sign_on enabled' do
+ before { stub_omniauth_config(allow_single_sign_on: ['saml']) }
+
+ it 'creates a user from SAML' do
+ saml_user.save
+
+ expect(gl_user).to be_valid
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'saml'
+ end
+ end
+
+ context 'with allow_single_sign_on default (["saml"])' do
+ before { stub_omniauth_config(allow_single_sign_on: ['saml']) }
+ it 'should not throw an error' do
+ expect{ saml_user.save }.not_to raise_error
+ end
+ end
+
+ context 'with allow_single_sign_on disabled' do
+ before { stub_omniauth_config(allow_single_sign_on: false) }
+ it 'should throw an error' do
+ expect{ saml_user.save }.to raise_error StandardError
+ end
+ end
+ end
+
+ context 'with auto_link_ldap_user disabled (default)' do
+ before { stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) }
+ include_examples 'to verify compliance with allow_single_sign_on'
+ end
+
+ context 'with auto_link_ldap_user enabled' do
+ before { stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) }
+
+ context 'and no LDAP provider defined' do
+ before { stub_ldap_config(providers: []) }
+
+ include_examples 'to verify compliance with allow_single_sign_on'
+ end
+
+ context 'and at least one LDAP provider is defined' do
+ before { stub_ldap_config(providers: %w(ldapmain)) }
+
+ context 'and a corresponding LDAP person' do
+ before do
+ allow(ldap_user).to receive(:uid) { uid }
+ allow(ldap_user).to receive(:username) { uid }
+ allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] }
+ allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
+ allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
+ end
+
+ context 'and no account for the LDAP user' do
+
+ it 'creates a user with dual LDAP and SAML identities' do
+ saml_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql uid
+ expect(gl_user.email).to eql 'johndoe@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: uid }
+ ])
+ end
+ end
+
+ context 'and LDAP user has an account already' do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+ it "adds the omniauth identity to the LDAP account" do
+ saml_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql 'john'
+ expect(gl_user.email).to eql 'john@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'saml', extern_uid: uid }
+ ])
+ end
+ end
+ end
+
+ context 'and no corresponding LDAP person' do
+ before { allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) }
+
+ include_examples 'to verify compliance with allow_single_sign_on'
+ end
+ end
+ end
+
+ end
+
+ describe 'blocking' do
+ before { stub_omniauth_config({ allow_saml_sign_up: true, auto_link_saml_user: true }) }
+
+ context 'signup with SAML only' do
+ context 'dont block on create' do
+ before { stub_omniauth_config(block_auto_created_users: false) }
+
+ it 'should not block the user' do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create' do
+ before { stub_omniauth_config(block_auto_created_users: true) }
+
+ it 'should block user' do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to be_blocked
+ end
+ end
+ end
+
+ context 'signup with linked omniauth and LDAP account' do
+ before do
+ stub_omniauth_config(auto_link_ldap_user: true)
+ allow(ldap_user).to receive(:uid) { uid }
+ allow(ldap_user).to receive(:username) { uid }
+ allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] }
+ allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
+ allow(saml_user).to receive(:ldap_person).and_return(ldap_user)
+ end
+
+ context "and no account for the LDAP user" do
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to be_blocked
+ end
+ end
+ end
+
+ context 'and LDAP user has an account already' do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+ end
+ end
+
+
+ context 'sign-in' do
+ before do
+ saml_user.save
+ saml_user.gl_user.activate
+ end
+
+ context 'dont block on create' do
+ before { stub_omniauth_config(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create' do
+ before { stub_omniauth_config(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'dont block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
+
+ it do
+ saml_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
new file mode 100644
index 00000000000..f4afe597e8d
--- /dev/null
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -0,0 +1,144 @@
+require 'spec_helper'
+
+describe Gitlab::SearchResults do
+ let(:user) { create(:user) }
+ let!(:project) { create(:project, name: 'foo') }
+ let!(:issue) { create(:issue, project: project, title: 'foo') }
+
+ let!(:merge_request) do
+ create(:merge_request, source_project: project, title: 'foo')
+ end
+
+ let!(:milestone) { create(:milestone, project: project, title: 'foo') }
+ let(:results) { described_class.new(user, Project.all, 'foo') }
+
+ describe '#total_count' do
+ it 'returns the total amount of search hits' do
+ expect(results.total_count).to eq(4)
+ end
+ end
+
+ describe '#projects_count' do
+ it 'returns the total amount of projects' do
+ expect(results.projects_count).to eq(1)
+ end
+ end
+
+ describe '#issues_count' do
+ it 'returns the total amount of issues' do
+ expect(results.issues_count).to eq(1)
+ end
+ end
+
+ describe '#merge_requests_count' do
+ it 'returns the total amount of merge requests' do
+ expect(results.merge_requests_count).to eq(1)
+ end
+ end
+
+ describe '#milestones_count' do
+ it 'returns the total amount of milestones' do
+ expect(results.milestones_count).to eq(1)
+ end
+ end
+
+ describe '#empty?' do
+ it 'returns true when there are no search results' do
+ allow(results).to receive(:total_count).and_return(0)
+
+ expect(results.empty?).to eq(true)
+ end
+
+ it 'returns false when there are search results' do
+ expect(results.empty?).to eq(false)
+ end
+ end
+
+ describe 'confidential issues' do
+ let(:project_1) { create(:empty_project) }
+ let(:project_2) { create(:empty_project) }
+ let(:project_3) { create(:empty_project) }
+ let(:project_4) { create(:empty_project) }
+ let(:query) { 'issue' }
+ let(:limit_projects) { Project.where(id: [project_1.id, project_2.id, project_3.id]) }
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let!(:issue) { create(:issue, project: project_1, title: 'Issue 1') }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project_1, title: 'Security issue 1', author: author) }
+ let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project_1, assignee: assignee) }
+ let!(:security_issue_3) { create(:issue, :confidential, project: project_2, title: 'Security issue 3', author: author) }
+ let!(:security_issue_4) { create(:issue, :confidential, project: project_3, title: 'Security issue 4', assignee: assignee) }
+ let!(:security_issue_5) { create(:issue, :confidential, project: project_4, title: 'Security issue 5') }
+
+ it 'should not list confidential issues for non project members' do
+ results = described_class.new(non_member, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(issues).not_to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 1
+ end
+
+ it 'should list confidential issues for author' do
+ results = described_class.new(author, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).not_to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 3
+ end
+
+ it 'should list confidential issues for assignee' do
+ results = described_class.new(assignee, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).not_to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).not_to include security_issue_3
+ expect(issues).to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 3
+ end
+
+ it 'should list confidential issues for project members' do
+ project_1.team << [member, :developer]
+ project_2.team << [member, :developer]
+
+ results = described_class.new(member, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).not_to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 4
+ end
+
+ it 'should list all issues for admin' do
+ results = described_class.new(admin, limit_projects, query)
+ issues = results.objects('issues')
+
+ expect(issues).to include issue
+ expect(issues).to include security_issue_1
+ expect(issues).to include security_issue_2
+ expect(issues).to include security_issue_3
+ expect(issues).to include security_issue_4
+ expect(issues).not_to include security_issue_5
+ expect(results.issues_count).to eq 5
+ end
+ end
+end
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
new file mode 100644
index 00000000000..e86b9ef6a63
--- /dev/null
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Gitlab::SnippetSearchResults do
+ let!(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
+
+ let(:results) { described_class.new(Snippet.all, 'foo') }
+
+ describe '#total_count' do
+ it 'returns the total amount of search hits' do
+ expect(results.total_count).to eq(2)
+ end
+ end
+
+ describe '#snippet_titles_count' do
+ it 'returns the amount of matched snippet titles' do
+ expect(results.snippet_titles_count).to eq(1)
+ end
+ end
+
+ describe '#snippet_blobs_count' do
+ it 'returns the amount of matched snippet blobs' do
+ expect(results.snippet_blobs_count).to eq(1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
new file mode 100644
index 00000000000..d940bf05061
--- /dev/null
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Gitlab::Workhorse, lib: true do
+ let(:project) { create(:project) }
+ let(:subject) { Gitlab::Workhorse }
+
+ describe "#send_git_archive" do
+ context "when the repository doesn't have an archive file path" do
+ before do
+ allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
+ end
+
+ it "raises an error" do
+ expect { subject.send_git_archive(project, "master", "zip") }.to raise_error(RuntimeError)
+ end
+ end
+ end
+end
diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb
new file mode 100644
index 00000000000..eb433c38873
--- /dev/null
+++ b/spec/mailers/abuse_report_mailer_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+describe AbuseReportMailer do
+ include EmailSpec::Matchers
+
+ describe '.notify' do
+ context 'with admin_notification_email set' do
+ before do
+ stub_application_setting(admin_notification_email: 'admin@example.com')
+ end
+
+ it 'sends to the admin_notification_email' do
+ report = create(:abuse_report)
+
+ mail = described_class.notify(report.id)
+
+ expect(mail).to deliver_to 'admin@example.com'
+ end
+
+ it 'includes the user in the subject' do
+ report = create(:abuse_report)
+
+ mail = described_class.notify(report.id)
+
+ expect(mail).to have_subject "#{report.user.name} (#{report.user.username}) was reported for abuse"
+ end
+ end
+
+ context 'with no admin_notification_email set' do
+ it 'returns early' do
+ stub_application_setting(admin_notification_email: nil)
+
+ expect { described_class.notify(spy).deliver_now }.
+ not_to change { ActionMailer::Base.deliveries.count }
+ end
+ end
+ end
+end
diff --git a/spec/mailers/emails/builds_spec.rb b/spec/mailers/emails/builds_spec.rb
new file mode 100644
index 00000000000..0df89938e97
--- /dev/null
+++ b/spec/mailers/emails/builds_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+require 'email_spec'
+require 'mailers/shared/notify'
+
+describe Notify do
+ include EmailSpec::Matchers
+
+ include_context 'gitlab email notification'
+
+ describe 'build notification email' do
+ let(:build) { create(:ci_build) }
+ let(:project) { build.project }
+
+ shared_examples 'build email' do
+ it 'contains name of project' do
+ is_expected.to have_body_text build.project_name
+ end
+
+ it 'contains link to project' do
+ is_expected.to have_body_text namespace_project_path(project.namespace, project)
+ end
+ end
+
+ shared_examples 'an email with X-GitLab headers containing build details' do
+ it 'has X-GitLab-Build* headers' do
+ is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
+ is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
+ end
+ end
+
+ describe 'build success' do
+ subject { Notify.build_success_email(build.id, 'wow@example.com') }
+ before { build.success }
+
+ it_behaves_like 'build email'
+ it_behaves_like 'an email with X-GitLab headers containing build details'
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+
+ it 'has header indicating build status' do
+ is_expected.to have_header 'X-GitLab-Build-Status', 'success'
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /Build success for/
+ end
+ end
+
+ describe 'build fail' do
+ subject { Notify.build_fail_email(build.id, 'wow@example.com') }
+ before { build.drop }
+
+ it_behaves_like 'build email'
+ it_behaves_like 'an email with X-GitLab headers containing build details'
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+
+ it 'has header indicating build status' do
+ is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /Build failed for/
+ end
+ end
+ end
+end
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
new file mode 100644
index 00000000000..c6758ccad39
--- /dev/null
+++ b/spec/mailers/emails/profile_spec.rb
@@ -0,0 +1,111 @@
+require 'spec_helper'
+require 'email_spec'
+require 'mailers/shared/notify'
+
+describe Notify do
+ include EmailSpec::Matchers
+ include_context 'gitlab email notification'
+
+ describe 'profile notifications' do
+ describe 'for new users, the email' do
+ let(:example_site_path) { root_path }
+ let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
+ let(:token) { 'kETLwRaayvigPq_x3SNM' }
+
+ subject { Notify.new_user_email(new_user.id, token) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'a new user email'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'contains the password text' do
+ is_expected.to have_body_text /Click here to set your password/
+ end
+
+ it 'includes a link for user to set password' do
+ params = "reset_password_token=#{token}"
+ is_expected.to have_body_text(
+ %r{http://localhost(:\d+)?/users/password/edit\?#{params}}
+ )
+ end
+
+ it 'explains the reset link expiration' do
+ is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
+ is_expected.to have_body_text(new_user_password_url)
+ is_expected.to have_body_text(/\?user_email=.*%40.*/)
+ end
+ end
+
+ describe 'for users that signed up, the email' do
+ let(:example_site_path) { root_path }
+ let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
+
+ subject { Notify.new_user_email(new_user.id) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'a new user email'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'should not contain the new user\'s password' do
+ is_expected.not_to have_body_text /password/
+ end
+ end
+
+ describe 'user added ssh key' do
+ let(:key) { create(:personal_key) }
+
+ subject { Notify.new_ssh_key_email(key.id) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'is sent to the new user' do
+ is_expected.to deliver_to key.user.email
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /^SSH key was added to your account$/i
+ end
+
+ it 'contains the new ssh key title' do
+ is_expected.to have_body_text /#{key.title}/
+ end
+
+ it 'includes a link to ssh keys page' do
+ is_expected.to have_body_text /#{profile_keys_path}/
+ end
+
+ context 'with SSH key that does not exist' do
+ it { expect { Notify.new_ssh_key_email('foo') }.not_to raise_error }
+ end
+ end
+
+ describe 'user added email' do
+ let(:email) { create(:email) }
+
+ subject { Notify.new_email_email(email.id) }
+
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'is sent to the new user' do
+ is_expected.to deliver_to email.user.email
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /^Email was added to your account$/i
+ end
+
+ it 'contains the new email address' do
+ is_expected.to have_body_text /#{email.email}/
+ end
+
+ it 'includes a link to emails page' do
+ is_expected.to have_body_text /#{profile_emails_path}/
+ end
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 154901a2fbc..f910424d85b 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -1,203 +1,13 @@
require 'spec_helper'
require 'email_spec'
+require 'mailers/shared/notify'
describe Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
include RepoHelpers
- new_user_address = 'newguy@example.com'
-
- let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
- let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
- let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
- let(:recipient) { create(:user, email: 'recipient@example.com') }
- let(:project) { create(:project) }
- let(:build) { create(:ci_build) }
-
- before(:each) do
- ActionMailer::Base.deliveries.clear
- email = recipient.emails.create(email: "notifications@example.com")
- recipient.update_attribute(:notification_email, email.email)
- end
-
- shared_examples 'a multiple recipients email' do
- it 'is sent to the given recipient' do
- is_expected.to deliver_to recipient.notification_email
- end
- end
-
- shared_examples 'an email sent from GitLab' do
- it 'is sent from GitLab' do
- sender = subject.header[:from].addrs[0]
- expect(sender.display_name).to eq(gitlab_sender_display_name)
- expect(sender.address).to eq(gitlab_sender)
- end
-
- it 'has a Reply-To address' do
- reply_to = subject.header[:reply_to].addresses
- expect(reply_to).to eq([gitlab_sender_reply_to])
- end
- end
-
- shared_examples 'an email starting a new thread' do |message_id_prefix|
- it 'has a discussion identifier' do
- is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
- is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
- end
- end
-
- shared_examples 'an answer to an existing thread' do |thread_id_prefix|
- it 'has a subject that begins with Re: ' do
- is_expected.to have_subject /^Re: /
- end
-
- it 'has headers that reference an existing thread' do
- is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
- is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
- is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
- is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
- end
- end
-
- shared_examples 'a new user email' do |user_email, site_path|
- it 'is sent to the new user' do
- is_expected.to deliver_to user_email
- end
-
- it 'has the correct subject' do
- is_expected.to have_subject /^Account was created for you$/i
- end
-
- it 'contains the new user\'s login name' do
- is_expected.to have_body_text /#{user_email}/
- end
-
- it 'includes a link to the site' do
- is_expected.to have_body_text /#{site_path}/
- end
- end
-
- shared_examples 'it should have Gmail Actions links' do
- it { is_expected.to have_body_text /ViewAction/ }
- end
-
- shared_examples 'it should not have Gmail Actions links' do
- it { is_expected.to_not have_body_text /ViewAction/ }
- end
-
- shared_examples 'it should show Gmail Actions View Issue link' do
- it_behaves_like 'it should have Gmail Actions links'
-
- it { is_expected.to have_body_text /View Issue/ }
- end
-
- shared_examples 'it should show Gmail Actions View Merge request link' do
- it_behaves_like 'it should have Gmail Actions links'
-
- it { is_expected.to have_body_text /View Merge request/ }
- end
-
- shared_examples 'it should show Gmail Actions View Commit link' do
- it_behaves_like 'it should have Gmail Actions links'
-
- it { is_expected.to have_body_text /View Commit/ }
- end
-
- describe 'for new users, the email' do
- let(:example_site_path) { root_path }
- let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) }
-
- token = 'kETLwRaayvigPq_x3SNM'
-
- subject { Notify.new_user_email(new_user.id, token) }
-
- it_behaves_like 'an email sent from GitLab'
- it_behaves_like 'a new user email', new_user_address
- it_behaves_like 'it should not have Gmail Actions links'
-
- it 'contains the password text' do
- is_expected.to have_body_text /Click here to set your password/
- end
-
- it 'includes a link for user to set password' do
- params = "reset_password_token=#{token}"
- is_expected.to have_body_text(
- %r{http://localhost(:\d+)?/users/password/edit\?#{params}}
- )
- end
-
- it 'explains the reset link expiration' do
- is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/)
- is_expected.to have_body_text(new_user_password_url)
- is_expected.to have_body_text(/\?user_email=.*%40.*/)
- end
- end
-
-
- describe 'for users that signed up, the email' do
- let(:example_site_path) { root_path }
- let(:new_user) { create(:user, email: new_user_address, password: "securePassword") }
-
- subject { Notify.new_user_email(new_user.id) }
-
- it_behaves_like 'an email sent from GitLab'
- it_behaves_like 'a new user email', new_user_address
- it_behaves_like 'it should not have Gmail Actions links'
-
- it 'should not contain the new user\'s password' do
- is_expected.not_to have_body_text /password/
- end
- end
-
- describe 'user added ssh key' do
- let(:key) { create(:personal_key) }
-
- subject { Notify.new_ssh_key_email(key.id) }
-
- it_behaves_like 'an email sent from GitLab'
- it_behaves_like 'it should not have Gmail Actions links'
-
- it 'is sent to the new user' do
- is_expected.to deliver_to key.user.email
- end
-
- it 'has the correct subject' do
- is_expected.to have_subject /^SSH key was added to your account$/i
- end
-
- it 'contains the new ssh key title' do
- is_expected.to have_body_text /#{key.title}/
- end
-
- it 'includes a link to ssh keys page' do
- is_expected.to have_body_text /#{profile_keys_path}/
- end
- end
-
- describe 'user added email' do
- let(:email) { create(:email) }
-
- subject { Notify.new_email_email(email.id) }
-
- it_behaves_like 'it should not have Gmail Actions links'
-
- it 'is sent to the new user' do
- is_expected.to deliver_to email.user.email
- end
-
- it 'has the correct subject' do
- is_expected.to have_subject /^Email was added to your account$/i
- end
-
- it 'contains the new email address' do
- is_expected.to have_body_text /#{email.email}/
- end
-
- it 'includes a link to emails page' do
- is_expected.to have_body_text /#{profile_emails_path}/
- end
- end
+ include_context 'gitlab email notification'
context 'for a project' do
describe 'items that are assignable, the email' do
@@ -227,6 +37,7 @@ describe Notify do
it_behaves_like 'an assignee email'
it_behaves_like 'an email starting a new thread', 'issue'
it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/
@@ -235,6 +46,17 @@ describe Notify do
it 'contains a link to the new issue' do
is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
+
+ context 'when enabled email_author_in_body' do
+ before do
+ allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
+ end
+
+ it 'contains a link to note author' do
+ is_expected.to have_body_text issue.author_name
+ is_expected.to have_body_text /wrote\:/
+ end
+ end
end
describe 'that are new with a description' do
@@ -253,6 +75,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread', 'issue'
it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like "an unsubscribeable thread"
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -277,12 +100,41 @@ describe Notify do
end
end
+ describe 'that have been relabeled' do
+ subject { Notify.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) }
+
+ it_behaves_like 'a multiple recipients email'
+ it_behaves_like 'an answer to an existing thread', 'issue'
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'an email with a labels subscriptions link in its footer'
+
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
+ end
+
+ it 'contains the names of the added labels' do
+ is_expected.to have_body_text /foo, bar, and baz/
+ end
+
+ it 'contains a link to the issue' do
+ is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
+ end
+ end
+
describe 'status changed' do
let(:status) { 'closed' }
subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
it_behaves_like 'an answer to an existing thread', 'issue'
it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'an unsubscribeable thread'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -319,6 +171,7 @@ describe Notify do
it_behaves_like 'an assignee email'
it_behaves_like 'an email starting a new thread', 'merge_request'
it_behaves_like 'it should show Gmail Actions View Merge request link'
+ it_behaves_like "an unsubscribeable thread"
it 'has the correct subject' do
is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
@@ -339,12 +192,24 @@ describe Notify do
it 'has the correct message-id set' do
is_expected.to have_header 'Message-ID', "<merge_request_#{merge_request.id}@#{Gitlab.config.gitlab.host}>"
end
+
+ context 'when enabled email_author_in_body' do
+ before do
+ allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
+ end
+
+ it 'contains a link to note author' do
+ is_expected.to have_body_text merge_request.author_name
+ is_expected.to have_body_text /wrote\:/
+ end
+ end
end
describe 'that are new with a description' do
subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) }
it_behaves_like 'it should show Gmail Actions View Merge request link'
+ it_behaves_like "an unsubscribeable thread"
it 'contains the description' do
is_expected.to have_body_text /#{merge_request_with_description.description}/
@@ -357,6 +222,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread', 'merge_request'
it_behaves_like 'it should show Gmail Actions View Merge request link'
+ it_behaves_like "an unsubscribeable thread"
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -381,12 +247,41 @@ describe Notify do
end
end
+ describe 'that have been relabeled' do
+ subject { Notify.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) }
+
+ it_behaves_like 'a multiple recipients email'
+ it_behaves_like 'an answer to an existing thread', 'merge_request'
+ it_behaves_like 'it should show Gmail Actions View Merge request link'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'an email with a labels subscriptions link in its footer'
+
+ it 'is sent as the author' do
+ sender = subject.header[:from].addrs[0]
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ end
+
+ it 'contains the names of the added labels' do
+ is_expected.to have_body_text /foo, bar, and baz/
+ end
+
+ it 'contains a link to the merge request' do
+ is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
+ end
+ end
+
describe 'status changed' do
let(:status) { 'reopened' }
subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) }
it_behaves_like 'an answer to an existing thread', 'merge_request'
it_behaves_like 'it should show Gmail Actions View Merge request link'
+ it_behaves_like "an unsubscribeable thread"
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -417,6 +312,7 @@ describe Notify do
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread', 'merge_request'
it_behaves_like 'it should show Gmail Actions View Merge request link'
+ it_behaves_like "an unsubscribeable thread"
it 'is sent as the merge author' do
sender = subject.header[:from].addrs[0]
@@ -446,6 +342,7 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
it 'has the correct subject' do
is_expected.to have_subject /Project was moved/
@@ -468,6 +365,7 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
it 'has the correct subject' do
is_expected.to have_subject /Access to project was granted/
@@ -506,6 +404,21 @@ describe Notify do
it 'contains the message from the note' do
is_expected.to have_body_text /#{note.note}/
end
+
+ it 'not contains note author' do
+ is_expected.not_to have_body_text /wrote\:/
+ end
+
+ context 'when enabled email_author_in_body' do
+ before do
+ allow(current_application_settings).to receive(:email_author_in_body).and_return(true)
+ end
+
+ it 'contains a link to note author' do
+ is_expected.to have_body_text note.author_name
+ is_expected.to have_body_text /wrote\:/
+ end
+ end
end
describe 'on a commit' do
@@ -518,6 +431,7 @@ describe Notify do
it_behaves_like 'a note email'
it_behaves_like 'an answer to an existing thread', 'commit'
it_behaves_like 'it should show Gmail Actions View Commit link'
+ it_behaves_like "a user cannot unsubscribe through footer link"
it 'has the correct subject' do
is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/
@@ -538,6 +452,7 @@ describe Notify do
it_behaves_like 'a note email'
it_behaves_like 'an answer to an existing thread', 'merge_request'
it_behaves_like 'it should show Gmail Actions View Merge request link'
+ it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
@@ -558,6 +473,7 @@ describe Notify do
it_behaves_like 'a note email'
it_behaves_like 'an answer to an existing thread', 'issue'
it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'an unsubscribeable thread'
it 'has the correct subject' do
is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
@@ -579,6 +495,7 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
it 'has the correct subject' do
is_expected.to have_subject /Access to group was granted/
@@ -607,6 +524,7 @@ describe Notify do
subject { ActionMailer::Base.deliveries.last }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like "a user cannot unsubscribe through footer link"
it 'is sent to the new user' do
is_expected.to deliver_to 'new-email@mail.com'
@@ -629,6 +547,9 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) }
it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+ it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -657,6 +578,9 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+ it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -684,6 +608,9 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) }
it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+ it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -707,6 +634,9 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+ it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -734,6 +664,9 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) }
it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+ it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -839,6 +772,9 @@ describe Notify do
subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) }
it_behaves_like 'it should show Gmail Actions View Commit link'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+ it_behaves_like 'an email with X-GitLab headers containing project details'
+ it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
@@ -867,31 +803,4 @@ describe Notify do
end
end
- describe 'build success' do
- before { build.success }
-
- subject { Notify.build_success_email(build.id, 'wow@example.com') }
-
- it 'has the correct subject' do
- should have_subject /Build success for/
- end
-
- it 'contains name of project' do
- should have_body_text build.project_name
- end
- end
-
- describe 'build fail' do
- before { build.drop }
-
- subject { Notify.build_fail_email(build.id, 'wow@example.com') }
-
- it 'has the correct subject' do
- should have_subject /Build failed for/
- end
-
- it 'contains name of project' do
- should have_body_text build.project_name
- end
- end
end
diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb
new file mode 100644
index 00000000000..6019af544d3
--- /dev/null
+++ b/spec/mailers/shared/notify.rb
@@ -0,0 +1,121 @@
+shared_context 'gitlab email notification' do
+ let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
+ let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
+ let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to }
+ let(:recipient) { create(:user, email: 'recipient@example.com') }
+ let(:project) { create(:project) }
+ let(:new_user_address) { 'newguy@example.com' }
+
+ before do
+ ActionMailer::Base.deliveries.clear
+ email = recipient.emails.create(email: "notifications@example.com")
+ recipient.update_attribute(:notification_email, email.email)
+ end
+end
+
+shared_examples 'a multiple recipients email' do
+ it 'is sent to the given recipient' do
+ is_expected.to deliver_to recipient.notification_email
+ end
+end
+
+shared_examples 'an email sent from GitLab' do
+ it 'is sent from GitLab' do
+ sender = subject.header[:from].addrs[0]
+ expect(sender.display_name).to eq(gitlab_sender_display_name)
+ expect(sender.address).to eq(gitlab_sender)
+ end
+
+ it 'has a Reply-To address' do
+ reply_to = subject.header[:reply_to].addresses
+ expect(reply_to).to eq([gitlab_sender_reply_to])
+ end
+end
+
+shared_examples 'an email that contains a header with author username' do
+ it 'has X-GitLab-Author header containing author\'s username' do
+ is_expected.to have_header 'X-GitLab-Author', user.username
+ end
+end
+
+shared_examples 'an email with X-GitLab headers containing project details' do
+ it 'has X-GitLab-Project* headers' do
+ is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
+ is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
+ is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
+ end
+end
+
+shared_examples 'an email starting a new thread' do |message_id_prefix|
+ include_examples 'an email with X-GitLab headers containing project details'
+
+ it 'has a discussion identifier' do
+ is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ end
+end
+
+shared_examples 'an answer to an existing thread' do |thread_id_prefix|
+ include_examples 'an email with X-GitLab headers containing project details'
+
+ it 'has a subject that begins with Re: ' do
+ is_expected.to have_subject /^Re: /
+ end
+
+ it 'has headers that reference an existing thread' do
+ is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
+ is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ end
+end
+
+shared_examples 'a new user email' do
+ it 'is sent to the new user' do
+ is_expected.to deliver_to new_user_address
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /^Account was created for you$/i
+ end
+
+ it 'contains the new user\'s login name' do
+ is_expected.to have_body_text /#{new_user_address}/
+ end
+end
+
+shared_examples 'it should have Gmail Actions links' do
+ it { is_expected.to have_body_text /ViewAction/ }
+end
+
+shared_examples 'it should not have Gmail Actions links' do
+ it { is_expected.to_not have_body_text /ViewAction/ }
+end
+
+shared_examples 'it should show Gmail Actions View Issue link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Issue/ }
+end
+
+shared_examples 'it should show Gmail Actions View Merge request link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Merge request/ }
+end
+
+shared_examples 'it should show Gmail Actions View Commit link' do
+ it_behaves_like 'it should have Gmail Actions links'
+
+ it { is_expected.to have_body_text /View Commit/ }
+end
+
+shared_examples 'an unsubscribeable thread' do
+ it { is_expected.to have_body_text /unsubscribe/ }
+end
+
+shared_examples 'a user cannot unsubscribe through footer link' do
+ it { is_expected.not_to have_body_text /unsubscribe/ }
+end
+
+shared_examples 'an email with a labels subscriptions link in its footer' do
+ it { is_expected.to have_body_text /label subscriptions/ }
+end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index d45319b25d4..ac12ab6c757 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -13,7 +13,8 @@
require 'rails_helper'
RSpec.describe AbuseReport, type: :model do
- subject { create(:abuse_report) }
+ subject { create(:abuse_report) }
+ let(:user) { create(:user) }
it { expect(subject).to be_valid }
@@ -26,6 +27,36 @@ RSpec.describe AbuseReport, type: :model do
it { is_expected.to validate_presence_of(:reporter) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:message) }
- it { is_expected.to validate_uniqueness_of(:user_id) }
+ it { is_expected.to validate_uniqueness_of(:user_id).with_message('has already been reported') }
+ end
+
+ describe '#remove_user' do
+ it 'blocks the user' do
+ expect { subject.remove_user(deleted_by: user) }.to change { subject.user.blocked? }.to(true)
+ end
+
+ it 'lets a worker delete the user' do
+ expect(DeleteUserWorker).to receive(:perform_async).with(user.id, subject.user.id,
+ delete_solo_owned_groups: true)
+
+ subject.remove_user(deleted_by: user)
+ end
+ end
+
+ describe '#notify' do
+ it 'delivers' do
+ expect(AbuseReportMailer).to receive(:notify).with(subject.id).
+ and_return(spy)
+
+ subject.notify
+ end
+
+ it 'returns early when not persisted' do
+ report = build(:abuse_report)
+
+ expect(AbuseReportMailer).not_to receive(:notify)
+
+ report.notify
+ end
end
end
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
new file mode 100644
index 00000000000..c5658bd26e1
--- /dev/null
+++ b/spec/models/appearance_spec.rb
@@ -0,0 +1,10 @@
+require 'rails_helper'
+
+RSpec.describe Appearance, type: :model do
+ subject { create(:appearance) }
+
+ it { is_expected.to be_valid }
+
+ it { is_expected.to validate_presence_of(:title) }
+ it { is_expected.to validate_presence_of(:description) }
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 35d8220ae54..b1764d7ac09 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -2,32 +2,47 @@
#
# Table name: application_settings
#
-# id :integer not null, primary key
-# default_projects_limit :integer
-# signup_enabled :boolean
-# signin_enabled :boolean
-# gravatar_enabled :boolean
-# sign_in_text :text
-# created_at :datetime
-# updated_at :datetime
-# home_page_url :string(255)
-# default_branch_protection :integer default(2)
-# twitter_sharing_enabled :boolean default(TRUE)
-# restricted_visibility_levels :text
-# version_check_enabled :boolean default(TRUE)
-# max_attachment_size :integer default(10), not null
-# default_project_visibility :integer
-# default_snippet_visibility :integer
-# restricted_signup_domains :text
-# user_oauth_applications :boolean default(TRUE)
-# after_sign_out_path :string(255)
-# session_expire_delay :integer default(10080), not null
-# import_sources :text
-# help_page_text :text
-# admin_notification_email :string(255)
-# shared_runners_enabled :boolean default(TRUE), not null
-# max_artifacts_size :integer default(100), not null
-# runners_registration_token :string(255)
+# id :integer not null, primary key
+# default_projects_limit :integer
+# signup_enabled :boolean
+# signin_enabled :boolean
+# gravatar_enabled :boolean
+# sign_in_text :text
+# created_at :datetime
+# updated_at :datetime
+# home_page_url :string(255)
+# default_branch_protection :integer default(2)
+# twitter_sharing_enabled :boolean default(TRUE)
+# restricted_visibility_levels :text
+# version_check_enabled :boolean default(TRUE)
+# max_attachment_size :integer default(10), not null
+# default_project_visibility :integer
+# default_snippet_visibility :integer
+# restricted_signup_domains :text
+# user_oauth_applications :boolean default(TRUE)
+# after_sign_out_path :string(255)
+# session_expire_delay :integer default(10080), not null
+# import_sources :text
+# help_page_text :text
+# admin_notification_email :string(255)
+# shared_runners_enabled :boolean default(TRUE), not null
+# max_artifacts_size :integer default(100), not null
+# runners_registration_token :string
+# require_two_factor_authentication :boolean default(FALSE)
+# two_factor_grace_period :integer default(48)
+# metrics_enabled :boolean default(FALSE)
+# metrics_host :string default("localhost")
+# metrics_username :string
+# metrics_password :string
+# metrics_pool_size :integer default(16)
+# metrics_timeout :integer default(10)
+# metrics_method_call_threshold :integer default(10)
+# recaptcha_enabled :boolean default(FALSE)
+# recaptcha_site_key :string
+# recaptcha_private_key :string
+# metrics_port :integer default(8089)
+# sentry_enabled :boolean default(FALSE)
+# sentry_dsn :string
#
require 'spec_helper'
@@ -51,6 +66,18 @@ describe ApplicationSetting, models: true do
it { is_expected.to allow_value(http).for(:after_sign_out_path) }
it { is_expected.to allow_value(https).for(:after_sign_out_path) }
it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
+
+ it { is_expected.to validate_presence_of(:max_attachment_size) }
+
+ it do
+ is_expected.to validate_numericality_of(:max_attachment_size)
+ .only_integer
+ .is_greater_than(0)
+ end
+
+ it_behaves_like 'an object with email-formated attributes', :admin_notification_email do
+ subject { setting }
+ end
end
context 'restricted signup domains' do
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
new file mode 100644
index 00000000000..78e95c8fac5
--- /dev/null
+++ b/spec/models/blob_spec.rb
@@ -0,0 +1,81 @@
+require 'rails_helper'
+
+describe Blob do
+ describe '.decorate' do
+ it 'returns NilClass when given nil' do
+ expect(described_class.decorate(nil)).to be_nil
+ end
+ end
+
+ describe '#svg?' do
+ it 'is falsey when not text' do
+ git_blob = double(text?: false)
+
+ expect(described_class.decorate(git_blob)).not_to be_svg
+ end
+
+ it 'is falsey when no language is detected' do
+ git_blob = double(text?: true, language: nil)
+
+ expect(described_class.decorate(git_blob)).not_to be_svg
+ end
+
+ it' is falsey when language is not SVG' do
+ git_blob = double(text?: true, language: double(name: 'XML'))
+
+ expect(described_class.decorate(git_blob)).not_to be_svg
+ end
+
+ it 'is truthy when language is SVG' do
+ git_blob = double(text?: true, language: double(name: 'SVG'))
+
+ expect(described_class.decorate(git_blob)).to be_svg
+ end
+ end
+
+ describe '#to_partial_path' do
+ def stubbed_blob(overrides = {})
+ overrides.reverse_merge!(
+ image?: false,
+ language: nil,
+ lfs_pointer?: false,
+ svg?: false,
+ text?: false
+ )
+
+ described_class.decorate(double).tap do |blob|
+ allow(blob).to receive_messages(overrides)
+ end
+ end
+
+ it 'handles LFS pointers' do
+ blob = stubbed_blob(lfs_pointer?: true)
+
+ expect(blob.to_partial_path).to eq 'download'
+ end
+
+ it 'handles SVGs' do
+ blob = stubbed_blob(text?: true, svg?: true)
+
+ expect(blob.to_partial_path).to eq 'image'
+ end
+
+ it 'handles images' do
+ blob = stubbed_blob(image?: true)
+
+ expect(blob.to_partial_path).to eq 'image'
+ end
+
+ it 'handles text' do
+ blob = stubbed_blob(text?: true)
+
+ expect(blob.to_partial_path).to eq 'text'
+ end
+
+ it 'defaults to download' do
+ blob = stubbed_blob
+
+ expect(blob.to_partial_path).to eq 'download'
+ end
+ end
+end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index e4cac105110..f6f84db57e6 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -6,7 +6,6 @@
# message :text not null
# starts_at :datetime
# ends_at :datetime
-# alert_type :integer
# created_at :datetime
# updated_at :datetime
# color :string(255)
@@ -16,6 +15,8 @@
require 'spec_helper'
describe BroadcastMessage, models: true do
+ include ActiveSupport::Testing::TimeHelpers
+
subject { create(:broadcast_message) }
it { is_expected.to be_valid }
@@ -35,20 +36,79 @@ describe BroadcastMessage, models: true do
it { is_expected.not_to allow_value('000').for(:font) }
end
- describe :current do
+ describe '.current' do
it "should return last message if time match" do
- broadcast_message = create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow)
- expect(BroadcastMessage.current).to eq(broadcast_message)
+ message = create(:broadcast_message)
+
+ expect(BroadcastMessage.current).to eq message
end
it "should return nil if time not come" do
- create(:broadcast_message, starts_at: Time.now.tomorrow, ends_at: Time.now + 2.days)
+ create(:broadcast_message, :future)
+
expect(BroadcastMessage.current).to be_nil
end
it "should return nil if time has passed" do
- create(:broadcast_message, starts_at: Time.now - 2.days, ends_at: Time.now.yesterday)
+ create(:broadcast_message, :expired)
+
expect(BroadcastMessage.current).to be_nil
end
end
+
+ describe '#active?' do
+ it 'is truthy when started and not ended' do
+ message = build(:broadcast_message)
+
+ expect(message).to be_active
+ end
+
+ it 'is falsey when ended' do
+ message = build(:broadcast_message, :expired)
+
+ expect(message).not_to be_active
+ end
+
+ it 'is falsey when not started' do
+ message = build(:broadcast_message, :future)
+
+ expect(message).not_to be_active
+ end
+ end
+
+ describe '#started?' do
+ it 'is truthy when starts_at has passed' do
+ message = build(:broadcast_message)
+
+ travel_to(3.days.from_now) do
+ expect(message).to be_started
+ end
+ end
+
+ it 'is falsey when starts_at is in the future' do
+ message = build(:broadcast_message)
+
+ travel_to(3.days.ago) do
+ expect(message).not_to be_started
+ end
+ end
+ end
+
+ describe '#ended?' do
+ it 'is truthy when ends_at has passed' do
+ message = build(:broadcast_message)
+
+ travel_to(3.days.from_now) do
+ expect(message).to be_ended
+ end
+ end
+
+ it 'is falsey when ends_at is in the future' do
+ message = build(:broadcast_message)
+
+ travel_to(3.days.ago) do
+ expect(message).not_to be_ended
+ end
+ end
+ end
end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 1c22e3cb7c4..b7457808040 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -1,32 +1,7 @@
-# == Schema Information
-#
-# Table name: builds
-#
-# id :integer not null, primary key
-# project_id :integer
-# status :string(255)
-# finished_at :datetime
-# trace :text
-# created_at :datetime
-# updated_at :datetime
-# started_at :datetime
-# runner_id :integer
-# commit_id :integer
-# coverage :float
-# commands :text
-# job_id :integer
-# name :string(255)
-# deploy :boolean default(FALSE)
-# options :text
-# allow_failure :boolean default(FALSE), not null
-# stage :string(255)
-# trigger_request_id :integer
-#
-
require 'spec_helper'
describe Ci::Build, models: true do
- let(:project) { FactoryGirl.create :empty_project }
+ let(:project) { FactoryGirl.create :project }
let(:commit) { FactoryGirl.create :ci_commit, project: project }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
@@ -34,7 +9,7 @@ describe Ci::Build, models: true do
it { is_expected.to respond_to :trace_html }
- describe :first_pending do
+ describe '#first_pending' do
let(:first) { FactoryGirl.create :ci_build, commit: commit, status: 'pending', created_at: Date.yesterday }
let(:second) { FactoryGirl.create :ci_build, commit: commit, status: 'pending' }
before { first; second }
@@ -44,7 +19,7 @@ describe Ci::Build, models: true do
it('returns with the first pending build') { is_expected.to eq(first) }
end
- describe :create_from do
+ describe '#create_from' do
before do
build.status = 'success'
build.save
@@ -58,7 +33,7 @@ describe Ci::Build, models: true do
end
end
- describe :ignored? do
+ describe '#ignored?' do
subject { build.ignored? }
context 'if build is not allowed to fail' do
@@ -94,7 +69,7 @@ describe Ci::Build, models: true do
end
end
- describe :trace do
+ describe '#trace' do
subject { build.trace_html }
it { is_expected.to be_empty }
@@ -126,7 +101,7 @@ describe Ci::Build, models: true do
# it { is_expected.to eq(commit.project.timeout) }
# end
- describe :options do
+ describe '#options' do
let(:options) do
{
image: "ruby:2.1",
@@ -147,25 +122,25 @@ describe Ci::Build, models: true do
# it { is_expected.to eq(project.allow_git_fetch) }
# end
- describe :project do
+ describe '#project' do
subject { build.project }
it { is_expected.to eq(commit.project) }
end
- describe :project_id do
+ describe '#project_id' do
subject { build.project_id }
it { is_expected.to eq(commit.project_id) }
end
- describe :project_name do
+ describe '#project_name' do
subject { build.project_name }
it { is_expected.to eq(project.name) }
end
- describe :extract_coverage do
+ describe '#extract_coverage' do
context 'valid content & regex' do
subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }
@@ -197,7 +172,7 @@ describe Ci::Build, models: true do
end
end
- describe :variables do
+ describe '#variables' do
context 'returns variables' do
subject { build.variables }
@@ -267,8 +242,8 @@ describe Ci::Build, models: true do
end
end
- describe :can_be_served? do
- let(:runner) { FactoryGirl.create :ci_specific_runner }
+ describe '#can_be_served?' do
+ let(:runner) { FactoryGirl.create :ci_runner }
before { build.project.runners << runner }
@@ -302,7 +277,7 @@ describe Ci::Build, models: true do
end
end
- describe :any_runners_online? do
+ describe '#any_runners_online?' do
subject { build.any_runners_online? }
context 'when no runners' do
@@ -310,7 +285,7 @@ describe Ci::Build, models: true do
end
context 'if there are runner' do
- let(:runner) { FactoryGirl.create :ci_specific_runner }
+ let(:runner) { FactoryGirl.create :ci_runner }
before do
build.project.runners << runner
@@ -337,8 +312,8 @@ describe Ci::Build, models: true do
end
end
- describe :show_warning? do
- subject { build.show_warning? }
+ describe '#stuck?' do
+ subject { build.stuck? }
%w(pending).each do |state|
context "if commit_status.status is #{state}" do
@@ -347,7 +322,7 @@ describe Ci::Build, models: true do
it { is_expected.to be_truthy }
context "and there are specific runner" do
- let(:runner) { FactoryGirl.create :ci_specific_runner, contacted_at: 1.second.ago }
+ let(:runner) { FactoryGirl.create :ci_runner, contacted_at: 1.second.ago }
before do
build.project.runners << runner
@@ -368,22 +343,34 @@ describe Ci::Build, models: true do
end
end
- describe :download_url do
- subject { build.download_url }
+ describe '#artifacts?' do
+ subject { build.artifacts? }
- it "should be nil if artifact doesn't exist" do
- build.update_attributes(artifacts_file: nil)
- is_expected.to be_nil
+ context 'artifacts archive does not exist' do
+ before { build.update_attributes(artifacts_file: nil) }
+ it { is_expected.to be_falsy }
end
- it 'should be nil if artifact exist' do
- gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif')
- build.update_attributes(artifacts_file: gif)
- is_expected.to_not be_nil
+ context 'artifacts archive exists' do
+ let(:build) { create(:ci_build, :artifacts) }
+ it { is_expected.to be_truthy }
+ end
+ end
+
+
+ describe '#artifacts_metadata?' do
+ subject { build.artifacts_metadata? }
+ context 'artifacts metadata does not exist' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'artifacts archive is a zip file and metadata exists' do
+ let(:build) { create(:ci_build, :artifacts) }
+ it { is_expected.to be_truthy }
end
end
- describe :repo_url do
+ describe '#repo_url' do
let(:build) { FactoryGirl.create :ci_build }
let(:project) { build.project }
@@ -397,6 +384,30 @@ describe Ci::Build, models: true do
it { is_expected.to include(project.web_url[7..-1]) }
end
+ describe '#depends_on_builds' do
+ let!(:build) { FactoryGirl.create :ci_build, commit: commit, name: 'build', stage_idx: 0, stage: 'build' }
+ let!(:rspec_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rspec', stage_idx: 1, stage: 'test' }
+ let!(:rubocop_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rubocop', stage_idx: 1, stage: 'test' }
+ let!(:staging) { FactoryGirl.create :ci_build, commit: commit, name: 'staging', stage_idx: 2, stage: 'deploy' }
+
+ it 'to have no dependents if this is first build' do
+ expect(build.depends_on_builds).to be_empty
+ end
+
+ it 'to have one dependent if this is test' do
+ expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id)
+ end
+
+ it 'to have all builds from build and test stage if this is last' do
+ expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id)
+ end
+
+ it 'to have retried builds instead the original ones' do
+ retried_rspec = Ci::Build.retry(rspec_test)
+ expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
+ end
+ end
+
def create_mr(build, commit, factory: :merge_request, created_at: Time.now)
FactoryGirl.create(factory,
source_project_id: commit.gl_project_id,
@@ -405,7 +416,7 @@ describe Ci::Build, models: true do
created_at: created_at)
end
- describe :merge_request do
+ describe '#merge_request' do
context 'when a MR has a reference to the commit' do
before do
@merge_request = create_mr(build, commit, factory: :merge_request)
@@ -458,6 +469,103 @@ describe Ci::Build, models: true do
expect(@build2.merge_request.id).to eq(@merge_request.id)
end
end
+ end
+
+ describe 'build erasable' do
+ shared_examples 'erasable' do
+ it 'should remove artifact file' do
+ expect(build.artifacts_file.exists?).to be_falsy
+ end
+
+ it 'should remove artifact metadata file' do
+ expect(build.artifacts_metadata.exists?).to be_falsy
+ end
+
+ it 'should erase build trace in trace file' do
+ expect(build.trace).to be_empty
+ end
+
+ it 'should set erased to true' do
+ expect(build.erased?).to be true
+ end
+
+ it 'should set erase date' do
+ expect(build.erased_at).to_not be_falsy
+ end
+ end
+
+ context 'build is not erasable' do
+ let!(:build) { create(:ci_build) }
+
+ describe '#erase' do
+ subject { build.erase }
+ it { is_expected.to be false }
+ end
+
+ describe '#erasable?' do
+ subject { build.erasable? }
+ it { is_expected.to eq false }
+ end
+ end
+
+ context 'build is erasable' do
+ let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
+
+ describe '#erase' do
+ before { build.erase(erased_by: user) }
+
+ context 'erased by user' do
+ let!(:user) { create(:user, username: 'eraser') }
+
+ include_examples 'erasable'
+
+ it 'should record user who erased a build' do
+ expect(build.erased_by).to eq user
+ end
+ end
+
+ context 'erased by system' do
+ let(:user) { nil }
+
+ include_examples 'erasable'
+
+ it 'should not set user who erased a build' do
+ expect(build.erased_by).to be_nil
+ end
+ end
+ end
+
+ describe '#erasable?' do
+ subject { build.erasable? }
+ it { is_expected.to eq true }
+ end
+
+ describe '#erased?' do
+ let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
+ subject { build.erased? }
+
+ context 'build has not been erased' do
+ it { is_expected.to be false }
+ end
+
+ context 'build has been erased' do
+ before { build.erase }
+
+ it { is_expected.to be true }
+ end
+ end
+
+ context 'metadata and build trace are not available' do
+ let!(:build) { create(:ci_build, :success, :artifacts) }
+ before { build.remove_artifacts_metadata! }
+
+ describe '#erase' do
+ it 'should not raise error' do
+ expect { build.erase }.to_not raise_error
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
new file mode 100644
index 00000000000..36d10636ae9
--- /dev/null
+++ b/spec/models/ci/build_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Ci::Build, models: true do
+ let(:build) { create(:ci_build) }
+ let(:test_trace) { 'This is a test' }
+
+ describe '#trace' do
+ it 'obfuscates project runners token' do
+ allow(build).to receive(:raw_trace).and_return("Test: #{build.project.runners_token}")
+
+ expect(build.trace).to eq("Test: xxxxxx")
+ end
+
+ it 'empty project runners token' do
+ allow(build).to receive(:raw_trace).and_return(test_trace)
+ # runners_token can't normally be set to nil
+ allow(build.project).to receive(:runners_token).and_return(nil)
+
+ expect(build.trace).to eq(test_trace)
+ end
+ end
+end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index b193e16e7f8..412842337ba 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -13,7 +13,7 @@
# tag :boolean default(FALSE)
# yaml_errors :text
# committed_at :datetime
-# project_id :integer
+# gl_project_id :integer
#
require 'spec_helper'
@@ -32,50 +32,6 @@ describe Ci::Commit, models: true do
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
- describe :ordered do
- let(:project) { FactoryGirl.create :empty_project }
-
- it 'returns ordered list of commits' do
- commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
- commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
- expect(project.ci_commits.ordered).to eq([commit2, commit1])
- end
-
- it 'returns commits ordered by committed_at and id, with nulls last' do
- commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
- commit2 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
- commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
- commit4 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
- expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1])
- end
- end
-
- describe :last_build do
- subject { commit.last_build }
- before do
- @first = FactoryGirl.create :ci_build, commit: commit, created_at: Date.yesterday
- @second = FactoryGirl.create :ci_build, commit: commit
- end
-
- it { is_expected.to be_a(Ci::Build) }
- it('returns with the most recently created build') { is_expected.to eq(@second) }
- end
-
- describe :retry do
- before do
- @first = FactoryGirl.create :ci_build, commit: commit, created_at: Date.yesterday
- @second = FactoryGirl.create :ci_build, commit: commit
- end
-
- it "creates only a new build" do
- expect(commit.builds.count(:all)).to eq 2
- expect(commit.statuses.count(:all)).to eq 2
- commit.retry
- expect(commit.builds.count(:all)).to eq 3
- expect(commit.statuses.count(:all)).to eq 3
- end
- end
-
describe :valid_commit_sha do
context 'commit.sha can not start with 00000000' do
before do
@@ -247,6 +203,35 @@ describe Ci::Commit, models: true do
end
end
+
+ context 'custom stage with first job allowed to fail' do
+ let(:yaml) do
+ {
+ stages: ['clean', 'test'],
+ clean_job: {
+ stage: 'clean',
+ allow_failure: true,
+ script: 'BUILD',
+ },
+ test_job: {
+ stage: 'test',
+ script: 'TEST',
+ },
+ }
+ end
+
+ before do
+ stub_ci_commit_yaml_file(YAML.dump(yaml))
+ create_builds
+ end
+
+ it 'properly schedules builds' do
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:drop)
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending', 'failed')
+ end
+ end
+
context 'properly creates builds when "when" is defined' do
let(:yaml) do
{
diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb
index da8491357a5..000a732db77 100644
--- a/spec/models/ci/runner_project_spec.rb
+++ b/spec/models/ci/runner_project_spec.rb
@@ -2,11 +2,12 @@
#
# Table name: ci_runner_projects
#
-# id :integer not null, primary key
-# runner_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
+# id :integer not null, primary key
+# runner_id :integer not null
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 232760dfeba..25e9e5eca48 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -39,7 +39,7 @@ describe Ci::Runner, models: true do
describe :assign_to do
let!(:project) { FactoryGirl.create :empty_project }
- let!(:shared_runner) { FactoryGirl.create(:ci_shared_runner) }
+ let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) }
before { shared_runner.assign_to(project) }
@@ -52,15 +52,15 @@ describe Ci::Runner, models: true do
subject { Ci::Runner.online }
before do
- @runner1 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.year.ago)
- @runner2 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago)
+ @runner1 = FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.year.ago)
+ @runner2 = FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago)
end
it { is_expected.to eq([@runner2])}
end
describe :online? do
- let(:runner) { FactoryGirl.create(:ci_shared_runner) }
+ let(:runner) { FactoryGirl.create(:ci_runner, :shared) }
subject { runner.online? }
@@ -84,7 +84,7 @@ describe Ci::Runner, models: true do
end
describe :status do
- let(:runner) { FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) }
+ let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) }
subject { runner.status }
@@ -115,7 +115,7 @@ describe Ci::Runner, models: true do
describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do
- runner = FactoryGirl.create(:ci_specific_runner)
+ runner = FactoryGirl.create(:ci_runner)
project = FactoryGirl.create(:empty_project)
project1 = FactoryGirl.create(:empty_project)
project.runners << runner
@@ -125,11 +125,39 @@ describe Ci::Runner, models: true do
end
it "returns true" do
- runner = FactoryGirl.create(:ci_specific_runner)
+ runner = FactoryGirl.create(:ci_runner)
project = FactoryGirl.create(:empty_project)
project.runners << runner
expect(runner.belongs_to_one_project?).to be_truthy
end
end
+
+ describe '#search' do
+ let(:runner) { create(:ci_runner, token: '123abc') }
+
+ it 'returns runners with a matching token' do
+ expect(described_class.search(runner.token)).to eq([runner])
+ end
+
+ it 'returns runners with a partially matching token' do
+ expect(described_class.search(runner.token[0..2])).to eq([runner])
+ end
+
+ it 'returns runners with a matching token regardless of the casing' do
+ expect(described_class.search(runner.token.upcase)).to eq([runner])
+ end
+
+ it 'returns runners with a matching description' do
+ expect(described_class.search(runner.description)).to eq([runner])
+ end
+
+ it 'returns runners with a partially matching description' do
+ expect(described_class.search(runner.description[0..2])).to eq([runner])
+ end
+
+ it 'returns runners with a matching description regardless of the casing' do
+ expect(described_class.search(runner.description.upcase)).to eq([runner])
+ end
+ end
end
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index cb2f51e2011..159be939300 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -2,12 +2,13 @@
#
# Table name: ci_triggers
#
-# id :integer not null, primary key
-# token :string(255)
-# project_id :integer not null
-# deleted_at :datetime
-# created_at :datetime
-# updated_at :datetime
+# id :integer not null, primary key
+# token :string(255)
+# project_id :integer
+# deleted_at :datetime
+# created_at :datetime
+# updated_at :datetime
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 31b56953a13..71e84091cb7 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -3,12 +3,13 @@
# Table name: ci_variables
#
# id :integer not null, primary key
-# project_id :integer not null
+# project_id :integer
# key :string(255)
# value :text
# encrypted_value :text
# encrypted_value_salt :string(255)
# encrypted_value_iv :string(255)
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index ecf37b40c58..0e9111c8029 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -86,10 +86,21 @@ eos
let(:issue) { create :issue, project: project }
let(:other_project) { create :project, :public }
let(:other_issue) { create :issue, project: other_project }
+ let(:commiter) { create :user }
+
+ before do
+ project.team << [commiter, :developer]
+ other_project.team << [commiter, :developer]
+ end
it 'detects issues that this commit is marked as closing' do
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
- allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
+
+ allow(commit).to receive_messages(
+ safe_message: "Fixes ##{issue.iid} and #{ext_ref}",
+ committer_email: commiter.email
+ )
+
expect(commit.closes_issues).to include(issue)
expect(commit.closes_issues).to include(other_issue)
end
@@ -118,4 +129,38 @@ eos
it { expect(data[:modified]).to eq([".gitmodules"]) }
it { expect(data[:removed]).to eq([]) }
end
+
+ describe '#reverts_commit?' do
+ let(:another_commit) { double(:commit, revert_description: "This reverts commit #{commit.sha}") }
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_falsy }
+
+ context 'commit has no description' do
+ before { allow(commit).to receive(:description?).and_return(false) }
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_falsy }
+ end
+
+ context "another_commit's description does not revert commit" do
+ before { allow(commit).to receive(:description).and_return("Foo Bar") }
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_falsy }
+ end
+
+ context "another_commit's description reverts commit" do
+ before { allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar") }
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_truthy }
+ end
+
+ context "another_commit's description reverts merged merge request" do
+ before do
+ revert_description = "This reverts merge request !foo123"
+ allow(another_commit).to receive(:revert_description).and_return(revert_description)
+ allow(commit).to receive(:description).and_return("Foo #{another_commit.revert_description} Bar")
+ end
+
+ it { expect(commit.reverts_commit?(another_commit)).to be_truthy }
+ end
+ end
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index b8f901b3433..82c68ff6cb1 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -29,6 +29,7 @@
# target_url :string(255)
# description :string(255)
# artifacts_file :text
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 25b3f4e50da..92fdc5cd65d 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -37,7 +37,7 @@ describe CaseSensitivity, models: true do
with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
and_return(criteria)
- expect(model.iwhere(:'foo.bar' => 'bar')).to eq(criteria)
+ expect(model.iwhere('foo.bar'.to_sym => 'bar')).to eq(criteria)
end
end
@@ -87,8 +87,8 @@ describe CaseSensitivity, models: true do
with(%q{LOWER("foo"."baz") = LOWER(:value)}, value: 'baz').
and_return(final)
- got = model.iwhere(:'foo.bar' => 'bar',
- :'foo.baz' => 'baz')
+ got = model.iwhere('foo.bar'.to_sym => 'bar',
+ 'foo.baz'.to_sym => 'baz')
expect(got).to eq(final)
end
@@ -127,7 +127,7 @@ describe CaseSensitivity, models: true do
with(%q{`foo`.`bar` = :value}, value: 'bar').
and_return(criteria)
- expect(model.iwhere(:'foo.bar' => 'bar')).
+ expect(model.iwhere('foo.bar'.to_sym => 'bar')).
to eq(criteria)
end
end
@@ -178,8 +178,8 @@ describe CaseSensitivity, models: true do
with(%q{`foo`.`baz` = :value}, value: 'baz').
and_return(final)
- got = model.iwhere(:'foo.bar' => 'bar',
- :'foo.baz' => 'baz')
+ got = model.iwhere('foo.bar'.to_sym => 'bar',
+ 'foo.baz'.to_sym => 'baz')
expect(got).to eq(final)
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 021d62cdf0c..be29b6d66ff 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -32,9 +32,54 @@ describe Issue, "Issuable" do
describe ".search" do
let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
- it "matches by title" do
+ it 'returns notes with a matching title' do
+ expect(described_class.search(searchable_issue.title)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a partially matching title' do
expect(described_class.search('able')).to eq([searchable_issue])
end
+
+ it 'returns notes with a matching title regardless of the casing' do
+ expect(described_class.search(searchable_issue.title.upcase)).
+ to eq([searchable_issue])
+ end
+ end
+
+ describe ".full_search" do
+ let!(:searchable_issue) do
+ create(:issue, title: "Searchable issue", description: 'kittens')
+ end
+
+ it 'returns notes with a matching title' do
+ expect(described_class.full_search(searchable_issue.title)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a partially matching title' do
+ expect(described_class.full_search('able')).to eq([searchable_issue])
+ end
+
+ it 'returns notes with a matching title regardless of the casing' do
+ expect(described_class.full_search(searchable_issue.title.upcase)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a matching description' do
+ expect(described_class.full_search(searchable_issue.description)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a partially matching description' do
+ expect(described_class.full_search(searchable_issue.description)).
+ to eq([searchable_issue])
+ end
+
+ it 'returns notes with a matching description regardless of the casing' do
+ expect(described_class.full_search(searchable_issue.description.upcase)).
+ to eq([searchable_issue])
+ end
end
describe "#today?" do
@@ -68,18 +113,71 @@ describe Issue, "Issuable" do
end
end
+ describe '#subscribed?' do
+ context 'user is not a participant in the issue' do
+ before { allow(issue).to receive(:participants).with(user).and_return([]) }
+
+ it 'returns false when no subcription exists' do
+ expect(issue.subscribed?(user)).to be_falsey
+ end
+
+ it 'returns true when a subcription exists and subscribed is true' do
+ issue.subscriptions.create(user: user, subscribed: true)
+
+ expect(issue.subscribed?(user)).to be_truthy
+ end
+
+ it 'returns false when a subcription exists and subscribed is false' do
+ issue.subscriptions.create(user: user, subscribed: false)
+
+ expect(issue.subscribed?(user)).to be_falsey
+ end
+ end
+
+ context 'user is a participant in the issue' do
+ before { allow(issue).to receive(:participants).with(user).and_return([user]) }
+
+ it 'returns false when no subcription exists' do
+ expect(issue.subscribed?(user)).to be_truthy
+ end
+
+ it 'returns true when a subcription exists and subscribed is true' do
+ issue.subscriptions.create(user: user, subscribed: true)
+
+ expect(issue.subscribed?(user)).to be_truthy
+ end
+
+ it 'returns false when a subcription exists and subscribed is false' do
+ issue.subscriptions.create(user: user, subscribed: false)
+
+ expect(issue.subscribed?(user)).to be_falsey
+ end
+ end
+ end
+
describe "#to_hook_data" do
- let(:hook_data) { issue.to_hook_data(user) }
+ let(:data) { issue.to_hook_data(user) }
+ let(:project) { issue.project }
+
it "returns correct hook data" do
- expect(hook_data[:object_kind]).to eq("issue")
- expect(hook_data[:user]).to eq(user.hook_attrs)
- expect(hook_data[:repository][:name]).to eq(issue.project.name)
- expect(hook_data[:repository][:url]).to eq(issue.project.url_to_repo)
- expect(hook_data[:repository][:description]).to eq(issue.project.description)
- expect(hook_data[:repository][:homepage]).to eq(issue.project.web_url)
- expect(hook_data[:object_attributes]).to eq(issue.hook_attrs)
+ expect(data[:object_kind]).to eq("issue")
+ expect(data[:user]).to eq(user.hook_attrs)
+ expect(data[:object_attributes]).to eq(issue.hook_attrs)
+ expect(data).to_not have_key(:assignee)
end
+
+ context "issue is assigned" do
+ before { issue.update_attribute(:assignee, user) }
+
+ it "returns correct hook data" do
+ expect(data[:object_attributes]['assignee_id']).to eq(user.id)
+ expect(data[:assignee]).to eq(user.hook_attrs)
+ end
+ end
+
+ include_examples 'project hook data'
+ include_examples 'deprecated repository hook data'
end
describe '#card_attributes' do
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 20f0c561e44..cb33edde820 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -48,7 +48,8 @@ describe Issue, "Mentionable" do
describe '#create_new_cross_references!' do
let(:project) { create(:project) }
- let(:issues) { create_list(:issue, 2, project: project) }
+ let(:author) { create(:author) }
+ let(:issues) { create_list(:issue, 2, project: project, author: author) }
context 'before changes are persisted' do
it 'ignores pre-existing references' do
@@ -91,7 +92,7 @@ describe Issue, "Mentionable" do
end
def create_issue(description:)
- create(:issue, project: project, description: description)
+ create(:issue, project: project, description: description, author: author)
end
end
end
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
new file mode 100644
index 00000000000..47c3be673c5
--- /dev/null
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe Milestone, 'Milestoneish' do
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+ let!(:issue) { create(:issue, project: project, milestone: milestone) }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) }
+ let!(:security_issue_2) { create(:issue, :confidential, project: project, assignee: assignee, milestone: milestone) }
+ let!(:closed_issue_1) { create(:issue, :closed, project: project, milestone: milestone) }
+ let!(:closed_issue_2) { create(:issue, :closed, project: project, milestone: milestone) }
+ let!(:closed_security_issue_1) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
+ let!(:closed_security_issue_2) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
+ let!(:closed_security_issue_3) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
+ let!(:closed_security_issue_4) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
+
+ before do
+ project.team << [member, :developer]
+ end
+
+ describe '#closed_items_count' do
+ it 'should not count confidential issues for non project members' do
+ expect(milestone.closed_items_count(non_member)).to eq 2
+ end
+
+ it 'should count confidential issues for author' do
+ expect(milestone.closed_items_count(author)).to eq 4
+ end
+
+ it 'should count confidential issues for assignee' do
+ expect(milestone.closed_items_count(assignee)).to eq 4
+ end
+
+ it 'should count confidential issues for project members' do
+ expect(milestone.closed_items_count(member)).to eq 6
+ end
+
+ it 'should count all issues for admin' do
+ expect(milestone.closed_items_count(admin)).to eq 6
+ end
+ end
+
+ describe '#total_items_count' do
+ it 'should not count confidential issues for non project members' do
+ expect(milestone.total_items_count(non_member)).to eq 4
+ end
+
+ it 'should count confidential issues for author' do
+ expect(milestone.total_items_count(author)).to eq 7
+ end
+
+ it 'should count confidential issues for assignee' do
+ expect(milestone.total_items_count(assignee)).to eq 7
+ end
+
+ it 'should count confidential issues for project members' do
+ expect(milestone.total_items_count(member)).to eq 10
+ end
+
+ it 'should count all issues for admin' do
+ expect(milestone.total_items_count(admin)).to eq 10
+ end
+ end
+
+ describe '#complete?' do
+ it 'returns false when has items opened' do
+ expect(milestone.complete?(non_member)).to eq false
+ end
+
+ it 'returns true when all items are closed' do
+ issue.close
+ merge_request.close
+
+ expect(milestone.complete?(non_member)).to eq true
+ end
+ end
+
+ describe '#percent_complete' do
+ it 'should not count confidential issues for non project members' do
+ expect(milestone.percent_complete(non_member)).to eq 50
+ end
+
+ it 'should count confidential issues for author' do
+ expect(milestone.percent_complete(author)).to eq 57
+ end
+
+ it 'should count confidential issues for assignee' do
+ expect(milestone.percent_complete(assignee)).to eq 57
+ end
+
+ it 'should count confidential issues for project members' do
+ expect(milestone.percent_complete(member)).to eq 60
+ end
+
+ it 'should count confidential issues for admin' do
+ expect(milestone.percent_complete(admin)).to eq 60
+ end
+ end
+end
diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb
new file mode 100644
index 00000000000..e31fdb0bffb
--- /dev/null
+++ b/spec/models/concerns/subscribable_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Subscribable, 'Subscribable' do
+ let(:resource) { create(:issue) }
+ let(:user) { create(:user) }
+
+ describe '#subscribed?' do
+ it 'returns false when no subcription exists' do
+ expect(resource.subscribed?(user)).to be_falsey
+ end
+
+ it 'returns true when a subcription exists and subscribed is true' do
+ resource.subscriptions.create(user: user, subscribed: true)
+
+ expect(resource.subscribed?(user)).to be_truthy
+ end
+
+ it 'returns false when a subcription exists and subscribed is false' do
+ resource.subscriptions.create(user: user, subscribed: false)
+
+ expect(resource.subscribed?(user)).to be_falsey
+ end
+ end
+ describe '#subscribers' do
+ it 'returns [] when no subcribers exists' do
+ expect(resource.subscribers).to be_empty
+ end
+
+ it 'returns the subscribed users' do
+ resource.subscriptions.create(user: user, subscribed: true)
+ resource.subscriptions.create(user: create(:user), subscribed: false)
+
+ expect(resource.subscribers).to eq [user]
+ end
+ end
+
+ describe '#toggle_subscription' do
+ it 'toggles the current subscription state for the given user' do
+ expect(resource.subscribed?(user)).to be_falsey
+
+ resource.toggle_subscription(user)
+
+ expect(resource.subscribed?(user)).to be_truthy
+ end
+ end
+
+ describe '#unsubscribe' do
+ it 'unsubscribes the given current user' do
+ resource.subscriptions.create(user: user, subscribed: true)
+ expect(resource.subscribed?(user)).to be_truthy
+
+ resource.unsubscribe(user)
+
+ expect(resource.subscribed?(user)).to be_falsey
+ end
+ end
+end
diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb
new file mode 100644
index 00000000000..a20a6149649
--- /dev/null
+++ b/spec/models/email_spec.rb
@@ -0,0 +1,22 @@
+# == Schema Information
+#
+# Table name: emails
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# email :string(255) not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+require 'spec_helper'
+
+describe Email, models: true do
+
+ describe 'validations' do
+ it_behaves_like 'an object with email-formated attributes', :email do
+ subject { build(:email) }
+ end
+ end
+
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 071582b0282..5fe44246738 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -65,23 +65,38 @@ describe Event, models: true do
it { expect(@event.author).to eq(@user) }
end
- describe '.latest_update_time' do
- describe 'when events are present' do
- let(:time) { Time.utc(2015, 1, 1) }
+ describe '#proper?' do
+ context 'issue event' do
+ let(:project) { create(:empty_project, :public) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:author) { create(:author) }
+ let(:assignee) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:event) { Event.new(project: project, action: Event::CREATED, target: issue, author_id: author.id) }
before do
- create(:closed_issue_event, updated_at: time)
- create(:closed_issue_event, updated_at: time + 5)
+ project.team << [member, :developer]
end
- it 'returns the latest update time' do
- expect(Event.latest_update_time).to eq(time + 5)
+ context 'for non confidential issues' do
+ let(:issue) { create(:issue, project: project, author: author, assignee: assignee) }
+
+ it { expect(event.proper?(non_member)).to eq true }
+ it { expect(event.proper?(author)).to eq true }
+ it { expect(event.proper?(assignee)).to eq true }
+ it { expect(event.proper?(member)).to eq true }
+ it { expect(event.proper?(admin)).to eq true }
end
- end
- describe 'when no events exist' do
- it 'returns nil' do
- expect(Event.latest_update_time).to be_nil
+ context 'for confidential issues' do
+ let(:issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
+
+ it { expect(event.proper?(non_member)).to eq false }
+ it { expect(event.proper?(author)).to eq true }
+ it { expect(event.proper?(assignee)).to eq true }
+ it { expect(event.proper?(member)).to eq true }
+ it { expect(event.proper?(admin)).to eq true }
end
end
end
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 6ec6b9037a4..9b144dd1ecc 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -10,6 +10,21 @@ describe ExternalIssue, models: true do
it { is_expected.to include_module(Referable) }
end
+ describe '.reference_pattern' do
+ it 'allows underscores in the project name' do
+ expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+ end
+
+ it 'allows numbers in the project name' do
+ expect(ExternalIssue.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234'
+ end
+
+ it 'requires the project name to begin with A-Z' do
+ expect(ExternalIssue.reference_pattern.match('3EXT_EXT-1234')).to eq nil
+ expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234'
+ end
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(issue.to_reference).to eq issue.id
diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/external_wiki_service_spec.rb
index b198aa77526..d37978720bf 100644
--- a/spec/models/external_wiki_service_spec.rb
+++ b/spec/models/external_wiki_service_spec.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
require 'spec_helper'
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index d61c1c96bde..5b0883d8702 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -29,6 +29,7 @@
# target_url :string(255)
# description :string(255)
# artifacts_file :text
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 646f767e6fe..c9245fc9535 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -11,7 +11,6 @@
# type :string(255)
# description :string(255) default(""), not null
# avatar :string(255)
-# public :boolean default(FALSE)
#
require 'spec_helper'
@@ -38,14 +37,6 @@ describe Group, models: true do
it { is_expected.not_to validate_presence_of :owner }
end
- describe '.public_and_given_groups' do
- let!(:public_group) { create(:group, public: true) }
-
- subject { described_class.public_and_given_groups([group.id]) }
-
- it { is_expected.to eq([public_group, group]) }
- end
-
describe '.visible_to_user' do
let!(:group) { create(:group) }
let!(:user) { create(:user) }
@@ -113,22 +104,29 @@ describe Group, models: true do
end
end
- describe "public_profile?" do
- it "returns true for public group" do
- group = create(:group, public: true)
- expect(group.public_profile?).to be_truthy
+ describe '.search' do
+ it 'returns groups with a matching name' do
+ expect(described_class.search(group.name)).to eq([group])
+ end
+
+ it 'returns groups with a partially matching name' do
+ expect(described_class.search(group.name[0..2])).to eq([group])
+ end
+
+ it 'returns groups with a matching name regardless of the casing' do
+ expect(described_class.search(group.name.upcase)).to eq([group])
+ end
+
+ it 'returns groups with a matching path' do
+ expect(described_class.search(group.path)).to eq([group])
end
- it "returns true for non-public group with public project" do
- group = create(:group)
- create(:project, :public, group: group)
- expect(group.public_profile?).to be_truthy
+ it 'returns groups with a partially matching path' do
+ expect(described_class.search(group.path[0..2])).to eq([group])
end
- it "returns false for non-public group with no public projects" do
- group = create(:group)
- create(:project, group: group)
- expect(group.public_profile?).to be_falsy
+ it 'returns groups with a matching path regardless of the casing' do
+ expect(described_class.search(group.path.upcase)).to eq([group])
end
end
end
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 645ee0b929a..983848392b7 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -19,6 +19,10 @@
require 'spec_helper'
describe ProjectHook, models: true do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ end
+
describe '.push_hooks' do
it 'should return hooks for push events only' do
hook = create(:project_hook, push_events: true)
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 1455661485b..f800f415bd2 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -31,7 +31,7 @@ describe ServiceHook, models: true do
WebMock.stub_request(:post, @service_hook.url)
end
- it "POSTs to the web hook URL" do
+ it "POSTs to the webhook URL" do
@service_hook.execute(@data)
expect(WebMock).to have_requested(:post, @service_hook.url).with(
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook' }
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 138b87a9a06..fd1513cab1b 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -36,7 +36,7 @@ describe SystemHook, models: true do
it "project_destroy hook" do
user = create(:user)
project = create(:empty_project, namespace: user.namespace)
- Projects::DestroyService.new(project, user, {}).execute
+ Projects::DestroyService.new(project, user, {}).pending_delete!
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /project_destroy/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
@@ -65,7 +65,7 @@ describe SystemHook, models: true do
project = create(:project)
project.team << [user, :master]
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /user_add_to_team/,
+ body: /user_add_to_team/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -76,7 +76,7 @@ describe SystemHook, models: true do
project.team << [user, :master]
project.project_members.destroy_all
expect(WebMock).to have_requested(:post, @system_hook.url).with(
- body: /user_remove_from_team/,
+ body: /user_remove_from_team/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 2d90b0793cc..04bc2dcfb16 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -18,20 +18,14 @@
require 'spec_helper'
-describe ProjectHook, models: true do
- describe "Associations" do
- it { is_expected.to belong_to :project }
- end
-
- describe "Mass assignment" do
- end
-
+describe WebHook, models: true do
describe "Validations" do
it { is_expected.to validate_presence_of(:url) }
- context "url format" do
+ describe 'url' do
it { is_expected.to allow_value("http://example.com").for(:url) }
- it { is_expected.to allow_value("https://excample.com").for(:url) }
+ it { is_expected.to allow_value("https://example.com").for(:url) }
+ it { is_expected.to allow_value(" https://example.com ").for(:url) }
it { is_expected.to allow_value("http://test.com/api").for(:url) }
it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) }
it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) }
@@ -39,6 +33,12 @@ describe ProjectHook, models: true do
it { is_expected.not_to allow_value("example.com").for(:url) }
it { is_expected.not_to allow_value("ftp://example.com").for(:url) }
it { is_expected.not_to allow_value("herp-and-derp").for(:url) }
+
+ it 'strips :url before saving it' do
+ hook = create(:project_hook, url: ' https://example.com ')
+
+ expect(hook.url).to eq('https://example.com')
+ end
end
end
@@ -52,7 +52,7 @@ describe ProjectHook, models: true do
WebMock.stub_request(:post, @project_hook.url)
end
- it "POSTs to the web hook URL" do
+ it "POSTs to the webhook URL" do
@project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, @project_hook.url).with(
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
@@ -77,5 +77,17 @@ describe ProjectHook, models: true do
expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
end
+
+ it "handles 200 status code" do
+ WebMock.stub_request(:post, @project_hook.url).to_return(status: 200, body: "Success")
+
+ expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
+ end
+
+ it "handles 2xx status codes" do
+ WebMock.stub_request(:post, @project_hook.url).to_return(status: 201, body: "Success")
+
+ expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
+ end
end
end
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
new file mode 100644
index 00000000000..5afe042e154
--- /dev/null
+++ b/spec/models/identity_spec.rb
@@ -0,0 +1,38 @@
+# == Schema Information
+#
+# Table name: identities
+#
+# id :integer not null, primary key
+# extern_uid :string(255)
+# provider :string(255)
+# user_id :integer
+# created_at :datetime
+# updated_at :datetime
+#
+
+require 'spec_helper'
+
+RSpec.describe Identity, models: true do
+
+ describe 'relations' do
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'fields' do
+ it { is_expected.to respond_to(:provider) }
+ it { is_expected.to respond_to(:extern_uid) }
+ end
+
+ describe '#is_ldap?' do
+ let(:ldap_identity) { create(:identity, provider: 'ldapmain') }
+ let(:other_identity) { create(:identity, provider: 'twitter') }
+
+ it 'returns true if it is a ldap identity' do
+ expect(ldap_identity.ldap?).to be_truthy
+ end
+
+ it 'returns false if it is not a ldap identity' do
+ expect(other_identity.ldap?).to be_falsey
+ end
+ end
+end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 52271c7c8c6..2ccdec1eeff 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -105,6 +105,40 @@ describe Issue, models: true do
end
end
+ describe '#referenced_merge_requests' do
+ it 'returns the referenced merge requests' do
+ project = create(:project, :public)
+
+ mr1 = create(:merge_request,
+ source_project: project,
+ source_branch: 'master',
+ target_branch: 'feature')
+
+ mr2 = create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_branch: 'master')
+
+ issue = create(:issue, description: mr1.to_reference, project: project)
+
+ create(:note_on_issue,
+ noteable: issue,
+ note: mr2.to_reference,
+ project_id: project.id)
+
+ expect(issue.referenced_merge_requests).to eq([mr1, mr2])
+ end
+ end
+
+ describe '#related_branches' do
+ it "should " do
+ allow(subject.project.repository).to receive(:branch_names).
+ and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name])
+
+ expect(subject.related_branches).to eq [subject.to_branch_name]
+ end
+ end
+
it_behaves_like 'an editable mentionable' do
subject { create(:issue) }
@@ -115,4 +149,12 @@ describe Issue, models: true do
it_behaves_like 'a Taskable' do
let(:subject) { create :issue }
end
+
+ describe "#to_branch_name" do
+ let(:issue) { build(:issue, title: 'a' * 30) }
+
+ it "starts with the issue iid" do
+ expect(issue.to_branch_name).to match /\A#{issue.iid}-a+\z/
+ end
+ end
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 696fbf7e0aa..0614ca1e7c9 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -59,18 +59,42 @@ describe Label, models: true do
context 'using id' do
it 'returns a String reference to the object' do
expect(label.to_reference).to eq "~#{label.id}"
- expect(label.to_reference(double('project'))).to eq "~#{label.id}"
end
end
context 'using name' do
it 'returns a String reference to the object' do
- expect(label.to_reference(:name)).to eq %(~"#{label.name}")
+ expect(label.to_reference(format: :name)).to eq %(~"#{label.name}")
end
it 'uses id when name contains double quote' do
label = create(:label, name: %q{"irony"})
- expect(label.to_reference(:name)).to eq "~#{label.id}"
+ expect(label.to_reference(format: :name)).to eq "~#{label.id}"
+ end
+ end
+
+ context 'using invalid format' do
+ it 'raises error' do
+ expect { label.to_reference(format: :invalid) }
+ .to raise_error StandardError, /Unknown format/
+ end
+ end
+
+ context 'cross project reference' do
+ let(:project) { create(:project) }
+
+ context 'using name' do
+ it 'returns cross reference with label name' do
+ expect(label.to_reference(project, format: :name))
+ .to eq %Q(#{label.project.to_reference}~"#{label.name}")
+ end
+ end
+
+ context 'using id' do
+ it 'returns cross reference with label id' do
+ expect(label.to_reference(project, format: :id))
+ .to eq %Q(#{label.project.to_reference}~#{label.id})
+ end
end
end
end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 2aedca20df2..2d8f1cc1ad3 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -31,6 +31,10 @@ describe Member, models: true do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
+ it_behaves_like 'an object with email-formated attributes', :invite_email do
+ subject { build(:project_member) }
+ end
+
context "when an invite email is provided" do
let(:member) { build(:project_member, invite_email: "user@example.com", user: nil) }
@@ -159,7 +163,7 @@ describe Member, models: true do
describe "#generate_invite_token" do
let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) }
-
+
it "sets the invite token" do
expect { member.generate_invite_token }.to change { member.invite_token}
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index e0653a8327d..654c71b6825 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -2,25 +2,29 @@
#
# Table name: merge_requests
#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
+# id :integer not null, primary key
+# target_branch :string(255) not null
+# source_branch :string(255) not null
+# source_project_id :integer not null
+# author_id :integer
+# assignee_id :integer
+# title :string(255)
+# created_at :datetime
+# updated_at :datetime
+# milestone_id :integer
+# state :string(255)
+# merge_status :string(255)
+# target_project_id :integer not null
+# iid :integer
+# description :text
+# position :integer default(0)
+# locked_at :datetime
+# updated_by_id :integer
+# merge_error :string(255)
+# merge_params :text
+# merge_when_build_succeeds :boolean default(FALSE), not null
+# merge_user_id :integer
+# merge_commit_sha :string
#
require 'spec_helper'
@@ -76,6 +80,37 @@ describe MergeRequest, models: true do
it { is_expected.to respond_to(:merge_when_build_succeeds) }
end
+ describe '.in_projects' do
+ it 'returns the merge requests for a set of projects' do
+ expect(described_class.in_projects(Project.all)).to eq([subject])
+ end
+ end
+
+ describe '#source_sha' do
+ let(:last_branch_commit) { subject.source_project.repository.commit(subject.source_branch) }
+
+ context 'with diffs' do
+ subject { create(:merge_request, :with_diffs) }
+ it 'returns the sha of the source branch last commit' do
+ expect(subject.source_sha).to eq(last_branch_commit.sha)
+ end
+ end
+
+ context 'without diffs' do
+ subject { create(:merge_request, :without_diffs) }
+ it 'returns the sha of the source branch last commit' do
+ expect(subject.source_sha).to eq(last_branch_commit.sha)
+ end
+ end
+
+ context 'when the merge request is being created' do
+ subject { build(:merge_request, source_branch: nil, compare_commits: []) }
+ it 'returns nil' do
+ expect(subject.source_sha).to be_nil
+ end
+ end
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "!#{subject.iid}"
@@ -134,11 +169,13 @@ describe MergeRequest, models: true do
describe 'detection of issues to be closed' do
let(:issue0) { create :issue, project: subject.project }
let(:issue1) { create :issue, project: subject.project }
- let(:commit0) { double('commit0', closes_issues: [issue0]) }
- let(:commit1) { double('commit1', closes_issues: [issue0]) }
- let(:commit2) { double('commit2', closes_issues: [issue1]) }
+
+ let(:commit0) { double('commit0', safe_message: "Fixes #{issue0.to_reference}") }
+ let(:commit1) { double('commit1', safe_message: "Fixes #{issue0.to_reference}") }
+ let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") }
before do
+ subject.project.team << [subject.author, :developer]
allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
end
@@ -146,7 +183,9 @@ describe MergeRequest, models: true do
allow(subject.project).to receive(:default_branch).
and_return(subject.target_branch)
- expect(subject.closes_issues).to eq([issue0, issue1].sort_by(&:id))
+ closed = subject.closes_issues
+
+ expect(closed).to include(issue0, issue1)
end
it 'only lists issues as to be closed if it targets the default branch' do
@@ -164,17 +203,6 @@ describe MergeRequest, models: true do
expect(subject.closes_issues).to include(issue2)
end
-
- context 'for a project with JIRA integration' do
- let(:issue0) { JiraIssue.new('JIRA-123', subject.project) }
- let(:issue1) { JiraIssue.new('FOOBAR-4567', subject.project) }
-
- it 'returns sorted JiraIssues' do
- allow(subject.project).to receive_messages(default_branch: subject.target_branch)
-
- expect(subject.closes_issues).to eq([issue0, issue1])
- end
- end
end
describe "#work_in_progress?" do
@@ -193,6 +221,11 @@ describe MergeRequest, models: true do
expect(subject).to be_work_in_progress
end
+ it "detects the '[WIP]' prefix" do
+ subject.title = "[WIP]#{subject.title}"
+ expect(subject).to be_work_in_progress
+ end
+
it "doesn't detect WIP for words starting with WIP" do
subject.title = "Wipwap #{subject.title}"
expect(subject).not_to be_work_in_progress
@@ -231,9 +264,15 @@ describe MergeRequest, models: true do
expect(subject.can_remove_source_branch?(user2)).to be_falsey
end
- it "is can be removed in all other cases" do
+ it "can be removed if the last commit is the head of the source branch" do
+ allow(subject.source_project).to receive(:commit).and_return(subject.last_commit)
+
expect(subject.can_remove_source_branch?(user)).to be_truthy
end
+
+ it "cannot be removed if the last commit is not also the head of the source branch" do
+ expect(subject.can_remove_source_branch?(user)).to be_falsey
+ end
end
describe "#reset_merge_when_build_succeeds" do
@@ -248,13 +287,86 @@ describe MergeRequest, models: true do
end
describe "#hook_attrs" do
+ let(:attrs_hash) { subject.hook_attrs.to_h }
+
+ [:source, :target].each do |key|
+ describe "#{key} key" do
+ include_examples 'project hook data', project_key: key do
+ let(:data) { attrs_hash }
+ let(:project) { subject.send("#{key}_project") }
+ end
+ end
+ end
+
it "has all the required keys" do
- attrs = subject.hook_attrs
- attrs = attrs.to_h
- expect(attrs).to include(:source)
- expect(attrs).to include(:target)
- expect(attrs).to include(:last_commit)
- expect(attrs).to include(:work_in_progress)
+ expect(attrs_hash).to include(:source)
+ expect(attrs_hash).to include(:target)
+ expect(attrs_hash).to include(:last_commit)
+ expect(attrs_hash).to include(:work_in_progress)
+ end
+ end
+
+ describe '#diverged_commits_count' do
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:project, forked_from_project: project) }
+
+ context 'diverged on same repository' do
+ subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) }
+
+ it 'counts commits that are on target branch but not on source branch' do
+ expect(subject.diverged_commits_count).to eq(5)
+ end
+ end
+
+ context 'diverged on fork' do
+ subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_project, target_project: project) }
+
+ it 'counts commits that are on target branch but not on source branch' do
+ expect(subject.diverged_commits_count).to eq(5)
+ end
+ end
+
+ context 'rebased on fork' do
+ subject(:merge_request_rebased) { create(:merge_request, :rebased, source_project: fork_project, target_project: project) }
+
+ it 'counts commits that are on target branch but not on source branch' do
+ expect(subject.diverged_commits_count).to eq(0)
+ end
+ end
+
+ describe 'caching' do
+ before(:example) do
+ allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
+ end
+
+ it 'caches the output' do
+ expect(subject).to receive(:compute_diverged_commits_count).
+ once.
+ and_return(2)
+
+ subject.diverged_commits_count
+ subject.diverged_commits_count
+ end
+
+ it 'invalidates the cache when the source sha changes' do
+ expect(subject).to receive(:compute_diverged_commits_count).
+ twice.
+ and_return(2)
+
+ subject.diverged_commits_count
+ allow(subject).to receive(:source_sha).and_return('123abc')
+ subject.diverged_commits_count
+ end
+
+ it 'invalidates the cache when the target sha changes' do
+ expect(subject).to receive(:compute_diverged_commits_count).
+ twice.
+ and_return(2)
+
+ subject.diverged_commits_count
+ allow(subject).to receive(:target_sha).and_return('123abc')
+ subject.diverged_commits_count
+ end
end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 30a71987d86..72a4ea70228 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -32,22 +32,36 @@ describe Milestone, models: true do
let(:milestone) { create(:milestone) }
let(:issue) { create(:issue) }
+ let(:user) { create(:user) }
+
+ describe "unique milestone title per project" do
+ it "shouldn't accept the same title in a project twice" do
+ new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
+ expect(new_milestone).not_to be_valid
+ end
+
+ it "should accept the same title in another project" do
+ project = build(:project)
+ new_milestone = Milestone.new(project: project, title: milestone.title)
+
+ expect(new_milestone).to be_valid
+ end
+ end
describe "#percent_complete" do
it "should not count open issues" do
milestone.issues << issue
- expect(milestone.percent_complete).to eq(0)
+ expect(milestone.percent_complete(user)).to eq(0)
end
it "should count closed issues" do
issue.close
milestone.issues << issue
- expect(milestone.percent_complete).to eq(100)
+ expect(milestone.percent_complete(user)).to eq(100)
end
it "should recover from dividing by zero" do
- expect(milestone.issues).to receive(:count).and_return(0)
- expect(milestone.percent_complete).to eq(0)
+ expect(milestone.percent_complete(user)).to eq(0)
end
end
@@ -89,7 +103,7 @@ describe Milestone, models: true do
)
end
- it { expect(milestone.percent_complete).to eq(75) }
+ it { expect(milestone.percent_complete(user)).to eq(75) }
end
describe :items_count do
@@ -99,24 +113,23 @@ describe Milestone, models: true do
milestone.merge_requests << create(:merge_request)
end
- it { expect(milestone.closed_items_count).to eq(1) }
- it { expect(milestone.open_items_count).to eq(2) }
- it { expect(milestone.total_items_count).to eq(3) }
- it { expect(milestone.is_empty?).to be_falsey }
+ it { expect(milestone.closed_items_count(user)).to eq(1) }
+ it { expect(milestone.total_items_count(user)).to eq(3) }
+ it { expect(milestone.is_empty?(user)).to be_falsey }
end
describe :can_be_closed? do
it { expect(milestone.can_be_closed?).to be_truthy }
end
- describe :is_empty? do
+ describe :total_items_count do
before do
create :closed_issue, milestone: milestone
create :merge_request, milestone: milestone
end
it 'Should return total count of issues and merge requests assigned to milestone' do
- expect(milestone.total_items_count).to eq 2
+ expect(milestone.total_items_count(user)).to eq 2
end
end
@@ -168,4 +181,34 @@ describe Milestone, models: true do
expect(issue4.position).to eq(42)
end
end
+
+ describe '.search' do
+ let(:milestone) { create(:milestone, title: 'foo', description: 'bar') }
+
+ it 'returns milestones with a matching title' do
+ expect(described_class.search(milestone.title)).to eq([milestone])
+ end
+
+ it 'returns milestones with a partially matching title' do
+ expect(described_class.search(milestone.title[0..2])).to eq([milestone])
+ end
+
+ it 'returns milestones with a matching title regardless of the casing' do
+ expect(described_class.search(milestone.title.upcase)).to eq([milestone])
+ end
+
+ it 'returns milestones with a matching description' do
+ expect(described_class.search(milestone.description)).to eq([milestone])
+ end
+
+ it 'returns milestones with a partially matching description' do
+ expect(described_class.search(milestone.description[0..2])).
+ to eq([milestone])
+ end
+
+ it 'returns milestones with a matching description regardless of the casing' do
+ expect(described_class.search(milestone.description.upcase)).
+ to eq([milestone])
+ end
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 4fa2d2bc4d2..3c3a580942a 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -11,7 +11,6 @@
# type :string(255)
# description :string(255) default(""), not null
# avatar :string(255)
-# public :boolean default(FALSE)
#
require 'spec_helper'
@@ -42,13 +41,32 @@ describe Namespace, models: true do
it { expect(namespace.human_name).to eq(namespace.owner_name) }
end
- describe :search do
- before do
- @namespace = create :namespace
+ describe '.search' do
+ let(:namespace) { create(:namespace) }
+
+ it 'returns namespaces with a matching name' do
+ expect(described_class.search(namespace.name)).to eq([namespace])
+ end
+
+ it 'returns namespaces with a partially matching name' do
+ expect(described_class.search(namespace.name[0..2])).to eq([namespace])
+ end
+
+ it 'returns namespaces with a matching name regardless of the casing' do
+ expect(described_class.search(namespace.name.upcase)).to eq([namespace])
+ end
+
+ it 'returns namespaces with a matching path' do
+ expect(described_class.search(namespace.path)).to eq([namespace])
end
- it { expect(Namespace.search(@namespace.path)).to eq([@namespace]) }
- it { expect(Namespace.search('unknown')).to eq([]) }
+ it 'returns namespaces with a partially matching path' do
+ expect(described_class.search(namespace.path[0..2])).to eq([namespace])
+ end
+
+ it 'returns namespaces with a matching path regardless of the casing' do
+ expect(described_class.search(namespace.path.upcase)).to eq([namespace])
+ end
end
describe :move_dir do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 593d8f76215..6b18936edb1 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -24,8 +24,10 @@ require 'spec_helper'
describe Note, models: true do
describe 'associations' do
it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:noteable) }
+ it { is_expected.to belong_to(:noteable).touch(true) }
it { is_expected.to belong_to(:author).class_name('User') }
+
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe 'validation' do
@@ -125,13 +127,32 @@ describe Note, models: true do
let(:set_mentionable_text) { ->(txt) { subject.note = txt } }
end
- describe :search do
- let!(:note) { create(:note, note: "WoW") }
+ describe "#all_references" do
+ let!(:note1) { create(:note) }
+ let!(:note2) { create(:note) }
+
+ it "reads the rendered note body from the cache" do
+ expect(Banzai::Renderer).to receive(:render).with(note1.note, pipeline: :note, cache_key: [note1, "note"], project: note1.project)
+ expect(Banzai::Renderer).to receive(:render).with(note2.note, pipeline: :note, cache_key: [note2, "note"], project: note2.project)
+
+ note1.all_references
+ note2.all_references
+ end
+ end
+
+ describe '.search' do
+ let(:note) { create(:note, note: 'WoW') }
+
+ it 'returns notes with matching content' do
+ expect(described_class.search(note.note)).to eq([note])
+ end
- it { expect(Note.search('wow')).to include(note) }
+ it 'returns notes with matching content regardless of the casing' do
+ expect(described_class.search('WOW')).to eq([note])
+ end
end
- describe :grouped_awards do
+ describe '.grouped_awards' do
before do
create :note, note: "smile", is_award: true
create :note, note: "smile", is_award: true
@@ -148,6 +169,66 @@ describe Note, models: true do
end
end
+ describe '#active?' do
+ it 'is always true when the note has no associated diff' do
+ note = build(:note)
+
+ expect(note).to receive(:diff).and_return(nil)
+
+ expect(note).to be_active
+ end
+
+ it 'is never true when the note has no noteable associated' do
+ note = build(:note)
+
+ expect(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:noteable).and_return(nil)
+
+ expect(note).not_to be_active
+ end
+
+ it 'returns the memoized value if defined' do
+ note = build(:note)
+
+ expect(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:noteable).and_return(double)
+
+ note.instance_variable_set(:@active, 'foo')
+ expect(note).not_to receive(:find_noteable_diff)
+
+ expect(note.active?).to eq 'foo'
+ end
+
+ context 'for a merge request noteable' do
+ it 'is false when noteable has no matching diff' do
+ merge = build_stubbed(:merge_request, :simple)
+ note = build(:note, noteable: merge)
+
+ allow(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:find_noteable_diff).and_return(nil)
+
+ expect(note).not_to be_active
+ end
+
+ it 'is true when noteable has a matching diff' do
+ merge = create(:merge_request, :simple)
+
+ # Generate a real line_code value so we know it will match. We use a
+ # random line from a random diff just for funsies.
+ diff = merge.diffs.to_a.sample
+ line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
+ code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
+
+ # We're persisting in order to trigger the set_diff callback
+ note = create(:note, noteable: merge, line_code: code)
+
+ # Make sure we don't get a false positive from a guard clause
+ expect(note).to receive(:find_noteable_diff).and_call_original
+ expect(note).to be_active
+ end
+ end
+ end
+
describe "editable?" do
it "returns true" do
note = build(:note)
@@ -164,13 +245,53 @@ describe Note, models: true do
expect(note.editable?).to be_falsy
end
end
-
+
+ describe "cross_reference_not_visible_for?" do
+ let(:private_user) { create(:user) }
+ let(:private_project) { create(:project, namespace: private_user.namespace).tap { |p| p.team << [private_user, :master] } }
+ let(:private_issue) { create(:issue, project: private_project) }
+
+ let(:ext_proj) { create(:project, :public) }
+ let(:ext_issue) { create(:issue, project: ext_proj) }
+
+ let(:note) do
+ create :note,
+ noteable: ext_issue, project: ext_proj,
+ note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
+ system: true
+ end
+
+ it "returns true" do
+ expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy
+ end
+
+ it "returns false" do
+ expect(note.cross_reference_not_visible_for?(private_user)).to be_falsy
+ end
+ end
+
describe "set_award!" do
- let(:issue) { create :issue }
+ let(:merge_request) { create :merge_request }
it "converts aliases to actual name" do
- note = create :note, note: ":+1:", noteable: issue
+ note = create(:note, note: ":+1:", noteable: merge_request)
expect(note.reload.note).to eq("thumbsup")
end
+
+ it "is not an award emoji when comment is on a diff" do
+ note = create(:note, note: ":blowfish:", noteable: merge_request, line_code: "11d5d2e667e9da4f7f610f81d86c974b146b13bd_0_2")
+ note = note.reload
+
+ expect(note.note).to eq(":blowfish:")
+ expect(note.is_award?).to be_falsy
+ end
+ end
+
+ describe 'clear_blank_line_code!' do
+ it 'clears a blank line code before validation' do
+ note = build(:note, line_code: ' ')
+
+ expect { note.valid? }.to change(note, :line_code).to(nil)
+ end
end
end
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
new file mode 100644
index 00000000000..2fa6715fcaf
--- /dev/null
+++ b/spec/models/project_group_link_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe ProjectGroupLink do
+ describe "Associations" do
+ it { should belong_to(:group) }
+ it { should belong_to(:project) }
+ end
+
+ describe "Validation" do
+ let!(:project_group_link) { create(:project_group_link) }
+
+ it { should validate_presence_of(:project_id) }
+ it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) }
+ it { should validate_presence_of(:group_id) }
+ it { should validate_presence_of(:group_access) }
+ end
+end
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index 64bb92fba95..f3d15f3c1ea 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -40,6 +40,20 @@ describe AsanaService, models: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
+ def create_data_for_commits(*messages)
+ {
+ object_kind: 'push',
+ ref: 'master',
+ user_name: user.name,
+ commits: messages.map do |m|
+ {
+ message: m,
+ url: 'https://gitlab.com/',
+ }
+ end
+ }
+ end
+
before do
@asana = AsanaService.new
allow(@asana).to receive_messages(
@@ -51,16 +65,67 @@ describe AsanaService, models: true do
)
end
- it 'should call Asana service to created a story' do
- expect(Asana::Task).to receive(:find).with('123456').once
+ it 'should call Asana service to create a story' do
+ data = create_data_for_commits('Message from commit. related to #123456')
+ expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.name_with_namespace} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}"
- @asana.check_commit('related to #123456', 'pushed')
+ d1 = double('Asana::Task')
+ expect(d1).to receive(:add_comment).with(text: expected_message)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '123456').once.and_return(d1)
+
+ @asana.execute(data)
end
- it 'should call Asana service to created a story and close a task' do
- expect(Asana::Task).to receive(:find).with('456789').twice
+ it 'should call Asana service to create a story and close a task' do
+ data = create_data_for_commits('fix #456789')
+ d1 = double('Asana::Task')
+ expect(d1).to receive(:add_comment)
+ expect(d1).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '456789').once.and_return(d1)
+
+ @asana.execute(data)
+ end
+
+ it 'should be able to close via url' do
+ data = create_data_for_commits('closes https://app.asana.com/19292/956299/42')
+ d1 = double('Asana::Task')
+ expect(d1).to receive(:add_comment)
+ expect(d1).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '42').once.and_return(d1)
+
+ @asana.execute(data)
+ end
+
+ it 'should allow multiple matches per line' do
+ message = <<-EOF
+ minor bigfix, refactoring, fixed #123 and Closes #456 work on #789
+ ref https://app.asana.com/19292/956299/42 and closing https://app.asana.com/19292/956299/12
+ EOF
+ data = create_data_for_commits(message)
+ d1 = double('Asana::Task')
+ expect(d1).to receive(:add_comment)
+ expect(d1).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '123').once.and_return(d1)
+
+ d2 = double('Asana::Task')
+ expect(d2).to receive(:add_comment)
+ expect(d2).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '456').once.and_return(d2)
+
+ d3 = double('Asana::Task')
+ expect(d3).to receive(:add_comment)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '789').once.and_return(d3)
+
+ d4 = double('Asana::Task')
+ expect(d4).to receive(:add_comment)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '42').once.and_return(d4)
+
+ d5 = double('Asana::Task')
+ expect(d5).to receive(:add_comment)
+ expect(d5).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '12').once.and_return(d5)
- @asana.check_commit('fix #456789', 'pushed')
+ @asana.execute(data)
end
end
end
diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb
new file mode 100644
index 00000000000..905379a64e3
--- /dev/null
+++ b/spec/models/project_services/builds_email_service_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe BuildsEmailService do
+ let(:build) { create(:ci_build) }
+ let(:data) { Gitlab::BuildDataBuilder.build(build) }
+ let(:service) { BuildsEmailService.new }
+
+ describe :execute do
+ it "sends email" do
+ service.recipients = 'test@gitlab.com'
+ data[:build_status] = 'failed'
+ expect(BuildEmailWorker).to receive(:perform_async)
+ service.execute(data)
+ end
+
+ it "does not sends email with failed build and allowed_failure on" do
+ data[:build_status] = 'failed'
+ data[:build_allow_failure] = true
+ expect(BuildEmailWorker).not_to receive(:perform_async)
+ service.execute(data)
+ end
+ end
+end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index cc92eb0bd9f..e0feb606f78 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 400bdf2d962..b8b9a455b83 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -29,6 +29,13 @@
# import_source :string(255)
# commit_count :integer default(0)
# import_error :text
+# ci_id :integer
+# builds_enabled :boolean default(TRUE), not null
+# shared_runners_enabled :boolean default(TRUE), not null
+# runners_token :string
+# build_coverage_regex :string
+# build_allow_git_fetch :boolean default(TRUE), not null
+# build_timeout :integer default(3600), not null
#
require 'spec_helper'
@@ -61,6 +68,7 @@ describe Project, models: true do
it { is_expected.to have_many(:runners) }
it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) }
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe 'modules' do
@@ -95,7 +103,7 @@ describe Project, models: true do
expect(project2.errors[:limit_reached].first).to match(/Your project limit is 0/)
end
end
-
+
describe 'project token' do
it 'should set an random token if none provided' do
project = FactoryGirl.create :empty_project, runners_token: ''
@@ -512,35 +520,35 @@ describe Project, models: true do
describe :any_runners do
let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) }
- let(:specific_runner) { create(:ci_specific_runner) }
- let(:shared_runner) { create(:ci_shared_runner) }
+ let(:specific_runner) { create(:ci_runner) }
+ let(:shared_runner) { create(:ci_runner, :shared) }
context 'for shared runners disabled' do
let(:shared_runners_enabled) { false }
-
+
it 'there are no runners available' do
expect(project.any_runners?).to be_falsey
end
-
+
it 'there is a specific runner' do
project.runners << specific_runner
expect(project.any_runners?).to be_truthy
end
-
+
it 'there is a shared runner, but they are prohibited to use' do
shared_runner
expect(project.any_runners?).to be_falsey
end
-
+
it 'checks the presence of specific runner' do
project.runners << specific_runner
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
end
end
-
+
context 'for shared runners enabled' do
let(:shared_runners_enabled) { true }
-
+
it 'there is a shared runner' do
shared_runner
expect(project.any_runners?).to be_truthy
@@ -554,7 +562,7 @@ describe Project, models: true do
end
describe '#visibility_level_allowed?' do
- let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+ let(:project) { create(:project, :internal) }
context 'when checking on non-forked project' do
it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
@@ -574,6 +582,142 @@ describe Project, models: true do
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
end
+ end
+
+ describe '.search' do
+ let(:project) { create(:project, description: 'kitten mittens') }
+
+ it 'returns projects with a matching name' do
+ expect(described_class.search(project.name)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching name' do
+ expect(described_class.search(project.name[0..2])).to eq([project])
+ end
+
+ it 'returns projects with a matching name regardless of the casing' do
+ expect(described_class.search(project.name.upcase)).to eq([project])
+ end
+
+ it 'returns projects with a matching description' do
+ expect(described_class.search(project.description)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching description' do
+ expect(described_class.search('kitten')).to eq([project])
+ end
+
+ it 'returns projects with a matching description regardless of the casing' do
+ expect(described_class.search('KITTEN')).to eq([project])
+ end
+
+ it 'returns projects with a matching path' do
+ expect(described_class.search(project.path)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching path' do
+ expect(described_class.search(project.path[0..2])).to eq([project])
+ end
+
+ it 'returns projects with a matching path regardless of the casing' do
+ expect(described_class.search(project.path.upcase)).to eq([project])
+ end
+
+ it 'returns projects with a matching namespace name' do
+ expect(described_class.search(project.namespace.name)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching namespace name' do
+ expect(described_class.search(project.namespace.name[0..2])).to eq([project])
+ end
+
+ it 'returns projects with a matching namespace name regardless of the casing' do
+ expect(described_class.search(project.namespace.name.upcase)).to eq([project])
+ end
+
+ it 'returns projects when eager loading namespaces' do
+ relation = described_class.all.includes(:namespace)
+ expect(relation.search(project.namespace.name)).to eq([project])
+ end
+ end
+
+ describe '#rename_repo' do
+ let(:project) { create(:project) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
+
+ before do
+ # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
+ # call. This makes testing a bit easier.
+ allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
+ end
+
+ it 'renames a repository' do
+ allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
+
+ ns = project.namespace_dir
+
+ expect(gitlab_shell).to receive(:mv_repository).
+ ordered.
+ with("#{ns}/foo", "#{ns}/#{project.path}").
+ and_return(true)
+
+ expect(gitlab_shell).to receive(:mv_repository).
+ ordered.
+ with("#{ns}/foo.wiki", "#{ns}/#{project.path}.wiki").
+ and_return(true)
+
+ expect_any_instance_of(SystemHooksService).
+ to receive(:execute_hooks_for).
+ with(project, :rename)
+
+ expect_any_instance_of(Gitlab::UploadsTransfer).
+ to receive(:rename_project).
+ with('foo', project.path, ns)
+
+ expect(project).to receive(:expire_caches_before_rename)
+
+ project.rename_repo
+ end
+ end
+
+ describe '#expire_caches_before_rename' do
+ let(:project) { create(:project) }
+ let(:repo) { double(:repo, exists?: true) }
+ let(:wiki) { double(:wiki, exists?: true) }
+
+ it 'expires the caches of the repository and wiki' do
+ allow(Repository).to receive(:new).
+ with('foo', project).
+ and_return(repo)
+
+ allow(Repository).to receive(:new).
+ with('foo.wiki', project).
+ and_return(wiki)
+
+ expect(repo).to receive(:expire_cache)
+ expect(repo).to receive(:expire_emptiness_caches)
+
+ expect(wiki).to receive(:expire_cache)
+ expect(wiki).to receive(:expire_emptiness_caches)
+
+ project.expire_caches_before_rename('foo')
+ end
+ end
+
+ describe '.search_by_title' do
+ let(:project) { create(:project, name: 'kittens') }
+
+ it 'returns projects with a matching name' do
+ expect(described_class.search_by_title(project.name)).to eq([project])
+ end
+
+ it 'returns projects with a partially matching name' do
+ expect(described_class.search_by_title('kitten')).to eq([project])
+ end
+
+ it 'returns projects with a matching name regardless of the casing' do
+ expect(described_class.search_by_title('KITTENS')).to eq([project])
+ end
end
end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 5cd5ae327bf..bacb17a8883 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -67,15 +67,69 @@ describe ProjectTeam, models: true do
end
end
+ describe :max_invited_level do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project) }
+
+ before do
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::DEVELOPER
+ )
+
+ group.add_user(master, Gitlab::Access::MASTER)
+ group.add_user(reporter, Gitlab::Access::REPORTER)
+ end
+
+ it { expect(project.team.max_invited_level(master.id)).to eq(Gitlab::Access::DEVELOPER) }
+ it { expect(project.team.max_invited_level(reporter.id)).to eq(Gitlab::Access::REPORTER) }
+ it { expect(project.team.max_invited_level(nonmember.id)).to be_nil }
+ end
+
+ describe :max_member_access do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project) }
+
+ before do
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::DEVELOPER
+ )
+
+ group.add_user(master, Gitlab::Access::MASTER)
+ group.add_user(reporter, Gitlab::Access::REPORTER)
+ end
+
+ it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) }
+ it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
+ it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
+
+ it "does not have an access" do
+ project.namespace.update(share_with_group_lock: true)
+ expect(project.team.max_member_access(master.id)).to be_nil
+ expect(project.team.max_member_access(reporter.id)).to be_nil
+ end
+ end
+
describe "#human_max_access" do
- it "return master role" do
- user = create :user
- group = create :group
- group.add_users([user.id], GroupMember::MASTER)
- project = create(:project, namespace: group)
- project.team << [user, :guest]
-
- expect(project.team.human_max_access(user.id)).to eq("Master")
+ it 'returns Master role' do
+ user = create(:user)
+ group = create(:group)
+ group.add_master(user)
+
+ project = build_stubbed(:empty_project, namespace: group)
+
+ expect(project.team.human_max_access(user.id)).to eq 'Master'
+ end
+
+ it 'returns Owner role' do
+ user = create(:user)
+ group = create(:group)
+ group.add_owner(user)
+
+ project = build_stubbed(:empty_project, namespace: group)
+
+ expect(project.team.human_max_access(user.id)).to eq 'Owner'
end
end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 876b927eaea..a2085df5bcd 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -36,6 +36,13 @@ describe ProjectWiki, models: true do
end
end
+ describe "#wiki_base_path" do
+ it "returns the wiki base path" do
+ wiki_base_path = "/#{project.path_with_namespace}/wikis"
+ expect(subject.wiki_base_path).to eq(wiki_base_path)
+ end
+ end
+
describe "#wiki" do
it "contains a Gollum::Wiki instance" do
expect(subject.wiki).to be_a Gollum::Wiki
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index afbf62035ac..a57229a4fdf 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -5,6 +5,15 @@ describe Repository, models: true do
let(:repository) { create(:project).repository }
let(:user) { create(:user) }
+ let(:commit_options) do
+ author = repository.user_to_committer(user)
+ { message: 'Test message', committer: author, author: author }
+ end
+ let(:merge_commit) do
+ source_sha = repository.find_branch('feature').target
+ merge_commit_id = repository.merge(user, source_sha, 'master', commit_options)
+ repository.commit(merge_commit_id)
+ end
describe :branch_names_contains do
subject { repository.branch_names_contains(sample_commit.id) }
@@ -92,13 +101,29 @@ describe Repository, models: true do
end
describe 'parsing result' do
- subject { repository.parse_search_result(results.first) }
+ subject { repository.parse_search_result(search_result) }
+ let(:search_result) { results.first }
it { is_expected.to be_an OpenStruct }
it { expect(subject.filename).to eq('CHANGELOG') }
+ it { expect(subject.basename).to eq('CHANGELOG') }
it { expect(subject.ref).to eq('master') }
it { expect(subject.startline).to eq(186) }
it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") }
+
+ context "when filename has extension" do
+ let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
+
+ it { expect(subject.filename).to eq('CONTRIBUTE.md') }
+ it { expect(subject.basename).to eq('CONTRIBUTE') }
+ end
+
+ context "when file under directory" do
+ let(:search_result) { "master:a/b/c.md:5:a b c\n" }
+
+ it { expect(subject.filename).to eq('a/b/c.md') }
+ it { expect(subject.basename).to eq('a/b/c') }
+ end
end
end
@@ -139,6 +164,12 @@ describe Repository, models: true do
expect(branch.name).to eq('new_feature')
end
+
+ it 'calls the after_create_branch hook' do
+ expect(repository).to receive(:after_create_branch)
+
+ repository.add_branch(user, 'new_feature', 'master')
+ end
end
context 'when pre hooks failed' do
@@ -200,13 +231,22 @@ describe Repository, models: true do
describe :commit_with_hooks do
context 'when pre hooks were successful' do
- it 'should run without errors' do
- expect_any_instance_of(GitHooksService).to receive(:execute).and_return(true)
+ before do
+ expect_any_instance_of(GitHooksService).to receive(:execute).
+ and_return(true)
+ end
+ it 'should run without errors' do
expect do
repository.commit_with_hooks(user, 'feature') { sample_commit.id }
end.not_to raise_error
end
+
+ it 'should ensure the autocrlf Git option is set to :input' do
+ expect(repository).to receive(:update_autocrlf_option)
+
+ repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+ end
end
context 'when pre hooks failed' do
@@ -219,4 +259,555 @@ describe Repository, models: true do
end
end
end
+
+ describe '#exists?' do
+ it 'returns true when a repository exists' do
+ expect(repository.exists?).to eq(true)
+ end
+
+ it 'returns false when a repository does not exist' do
+ expect(repository.raw_repository).to receive(:rugged).
+ and_raise(Gitlab::Git::Repository::NoRepository)
+
+ expect(repository.exists?).to eq(false)
+ end
+
+ it 'returns false when there is no namespace' do
+ allow(repository).to receive(:path_with_namespace).and_return(nil)
+
+ expect(repository.exists?).to eq(false)
+ end
+ end
+
+ describe '#has_visible_content?' do
+ subject { repository.has_visible_content? }
+
+ describe 'when there are no branches' do
+ before do
+ allow(repository.raw_repository).to receive(:branch_count).and_return(0)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ describe 'when there are branches' do
+ it 'returns true' do
+ expect(repository.raw_repository).to receive(:branch_count).and_return(3)
+
+ expect(subject).to eq(true)
+ end
+
+ it 'caches the output' do
+ expect(repository.raw_repository).to receive(:branch_count).
+ once.
+ and_return(3)
+
+ repository.has_visible_content?
+ repository.has_visible_content?
+ end
+ end
+ end
+
+ describe '#update_autocrlf_option' do
+ describe 'when autocrlf is not already set to :input' do
+ before do
+ repository.raw_repository.autocrlf = true
+ end
+
+ it 'sets autocrlf to :input' do
+ repository.update_autocrlf_option
+
+ expect(repository.raw_repository.autocrlf).to eq(:input)
+ end
+ end
+
+ describe 'when autocrlf is already set to :input' do
+ before do
+ repository.raw_repository.autocrlf = :input
+ end
+
+ it 'does nothing' do
+ expect(repository.raw_repository).to_not receive(:autocrlf=).
+ with(:input)
+
+ repository.update_autocrlf_option
+ end
+ end
+ end
+
+ describe '#empty?' do
+ let(:empty_repository) { create(:project_empty_repo).repository }
+
+ it 'returns true for an empty repository' do
+ expect(empty_repository.empty?).to eq(true)
+ end
+
+ it 'returns false for a non-empty repository' do
+ expect(repository.empty?).to eq(false)
+ end
+
+ it 'caches the output' do
+ expect(repository.raw_repository).to receive(:empty?).
+ once.
+ and_return(false)
+
+ repository.empty?
+ repository.empty?
+ end
+ end
+
+ describe '#root_ref' do
+ it 'returns a branch name' do
+ expect(repository.root_ref).to be_an_instance_of(String)
+ end
+
+ it 'caches the output' do
+ expect(repository.raw_repository).to receive(:root_ref).
+ once.
+ and_return('master')
+
+ repository.root_ref
+ repository.root_ref
+ end
+ end
+
+ describe '#expire_cache' do
+ it 'expires all caches' do
+ expect(repository).to receive(:expire_branch_cache)
+
+ repository.expire_cache
+ end
+
+ it 'expires the caches for a specific branch' do
+ expect(repository).to receive(:expire_branch_cache).with('master')
+
+ repository.expire_cache('master')
+ end
+
+ it 'expires the emptiness caches for an empty repository' do
+ expect(repository).to receive(:empty?).and_return(true)
+ expect(repository).to receive(:expire_emptiness_caches)
+
+ repository.expire_cache
+ end
+
+ it 'does not expire the emptiness caches for a non-empty repository' do
+ expect(repository).to receive(:empty?).and_return(false)
+ expect(repository).to_not receive(:expire_emptiness_caches)
+
+ repository.expire_cache
+ end
+ end
+
+ describe '#expire_root_ref_cache' do
+ it 'expires the root reference cache' do
+ repository.root_ref
+
+ expect(repository.raw_repository).to receive(:root_ref).
+ once.
+ and_return('foo')
+
+ repository.expire_root_ref_cache
+
+ expect(repository.root_ref).to eq('foo')
+ end
+ end
+
+ describe '#expire_has_visible_content_cache' do
+ it 'expires the visible content cache' do
+ repository.has_visible_content?
+
+ expect(repository.raw_repository).to receive(:branch_count).
+ once.
+ and_return(0)
+
+ repository.expire_has_visible_content_cache
+
+ expect(repository.has_visible_content?).to eq(false)
+ end
+ end
+
+ describe '#expire_branch_cache' do
+ # This method is private but we need it for testing purposes. Sadly there's
+ # no other proper way of testing caching operations.
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache for all branches' do
+ expect(cache).to receive(:expire).
+ at_least(repository.branches.length).
+ times
+
+ repository.expire_branch_cache
+ end
+
+ it 'expires the cache for all branches when the root branch is given' do
+ expect(cache).to receive(:expire).
+ at_least(repository.branches.length).
+ times
+
+ repository.expire_branch_cache(repository.root_ref)
+ end
+
+ it 'expires the cache for a specific branch' do
+ expect(cache).to receive(:expire).once
+
+ repository.expire_branch_cache('foo')
+ end
+ end
+
+ describe '#expire_emptiness_caches' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the caches' do
+ expect(cache).to receive(:expire).with(:empty?)
+ expect(repository).to receive(:expire_has_visible_content_cache)
+
+ repository.expire_emptiness_caches
+ end
+ end
+
+ describe :skip_merged_commit do
+ subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", nil, 100, 0, true).map{ |k| k.id } }
+
+ it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
+ end
+
+ describe '#merge' do
+ it 'should merge the code and return the commit id' do
+ expect(merge_commit).to be_present
+ expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present
+ end
+ end
+
+ describe '#revert' do
+ let(:new_image_commit) { repository.commit('33f3729a45c02fc67d00adb1b8bca394b0e761d9') }
+ let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
+
+ context 'when there is a conflict' do
+ it 'should abort the operation' do
+ expect(repository.revert(user, new_image_commit, 'master')).to eq(false)
+ end
+ end
+
+ context 'when commit was already reverted' do
+ it 'should abort the operation' do
+ repository.revert(user, update_image_commit, 'master')
+
+ expect(repository.revert(user, update_image_commit, 'master')).to eq(false)
+ end
+ end
+
+ context 'when commit can be reverted' do
+ it 'should revert the changes' do
+ expect(repository.revert(user, update_image_commit, 'master')).to be_truthy
+ end
+ end
+
+ context 'reverting a merge commit' do
+ it 'should revert the changes' do
+ merge_commit
+ expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).to be_present
+
+ repository.revert(user, merge_commit, 'master')
+ expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).not_to be_present
+ end
+ end
+ end
+
+ describe '#before_delete' do
+ describe 'when a repository does not exist' do
+ before do
+ allow(repository).to receive(:exists?).and_return(false)
+ end
+
+ it 'does not flush caches that depend on repository data' do
+ expect(repository).to_not receive(:expire_cache)
+
+ repository.before_delete
+ end
+
+ it 'flushes the root ref cache' do
+ expect(repository).to receive(:expire_root_ref_cache)
+
+ repository.before_delete
+ end
+
+ it 'flushes the emptiness caches' do
+ expect(repository).to receive(:expire_emptiness_caches)
+
+ repository.before_delete
+ end
+ end
+
+ describe 'when a repository exists' do
+ before do
+ allow(repository).to receive(:exists?).and_return(true)
+ end
+
+ it 'flushes the caches that depend on repository data' do
+ expect(repository).to receive(:expire_cache)
+
+ repository.before_delete
+ end
+
+ it 'flushes the root ref cache' do
+ expect(repository).to receive(:expire_root_ref_cache)
+
+ repository.before_delete
+ end
+
+ it 'flushes the emptiness caches' do
+ expect(repository).to receive(:expire_emptiness_caches)
+
+ repository.before_delete
+ end
+ end
+ end
+
+ describe '#before_change_head' do
+ it 'flushes the branch cache' do
+ expect(repository).to receive(:expire_branch_cache)
+
+ repository.before_change_head
+ end
+
+ it 'flushes the root ref cache' do
+ expect(repository).to receive(:expire_root_ref_cache)
+
+ repository.before_change_head
+ end
+ end
+
+ describe '#before_push_tag' do
+ it 'flushes the cache' do
+ expect(repository).to receive(:expire_cache)
+ expect(repository).to receive(:expire_tag_count_cache)
+
+ repository.before_push_tag
+ end
+ end
+
+ describe '#after_import' do
+ it 'flushes the emptiness cachess' do
+ expect(repository).to receive(:expire_emptiness_caches)
+
+ repository.after_import
+ end
+ end
+
+ describe '#after_push_commit' do
+ it 'flushes the cache' do
+ expect(repository).to receive(:expire_cache).with('master', '123')
+
+ repository.after_push_commit('master', '123')
+ end
+ end
+
+ describe '#after_create_branch' do
+ it 'flushes the visible content cache' do
+ expect(repository).to receive(:expire_has_visible_content_cache)
+
+ repository.after_create_branch
+ end
+ end
+
+ describe '#after_remove_branch' do
+ it 'flushes the visible content cache' do
+ expect(repository).to receive(:expire_has_visible_content_cache)
+
+ repository.after_remove_branch
+ end
+ end
+
+ describe "#main_language" do
+ it 'shows the main language of the project' do
+ expect(repository.main_language).to eq("Ruby")
+ end
+
+ it 'returns nil when the repository is empty' do
+ allow(repository).to receive(:empty?).and_return(true)
+
+ expect(repository.main_language).to be_nil
+ end
+ end
+
+ describe '#before_remove_tag' do
+ it 'flushes the tag cache' do
+ expect(repository).to receive(:expire_tag_count_cache)
+
+ repository.before_remove_tag
+ end
+ end
+
+ describe '#branch_count' do
+ it 'returns the number of branches' do
+ expect(repository.branch_count).to be_an_instance_of(Fixnum)
+ end
+ end
+
+ describe '#tag_count' do
+ it 'returns the number of tags' do
+ expect(repository.tag_count).to be_an_instance_of(Fixnum)
+ end
+ end
+
+ describe '#expire_branch_count_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache' do
+ expect(cache).to receive(:expire).with(:branch_count)
+
+ repository.expire_branch_count_cache
+ end
+ end
+
+ describe '#expire_tag_count_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'expires the cache' do
+ expect(cache).to receive(:expire).with(:tag_count)
+
+ repository.expire_tag_count_cache
+ end
+ end
+
+ describe '#add_tag' do
+ it 'adds a tag' do
+ expect(repository).to receive(:before_push_tag)
+
+ expect_any_instance_of(Gitlab::Shell).to receive(:add_tag).
+ with(repository.path_with_namespace, '8.5', 'master', 'foo')
+
+ repository.add_tag('8.5', 'master', 'foo')
+ end
+ end
+
+ describe '#rm_branch' do
+ let(:user) { create(:user) }
+
+ it 'removes a branch' do
+ expect(repository).to receive(:before_remove_branch)
+ expect(repository).to receive(:after_remove_branch)
+
+ repository.rm_branch(user, 'feature')
+ end
+ end
+
+ describe '#rm_tag' do
+ it 'removes a tag' do
+ expect(repository).to receive(:before_remove_tag)
+
+ expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
+ with(repository.path_with_namespace, '8.5')
+
+ repository.rm_tag('8.5')
+ end
+ end
+
+ describe '#avatar' do
+ it 'returns the first avatar file found in the repository' do
+ expect(repository).to receive(:blob_at_branch).
+ with('master', 'logo.png').
+ and_return(true)
+
+ expect(repository.avatar).to eq('logo.png')
+ end
+
+ it 'caches the output' do
+ allow(repository).to receive(:blob_at_branch).
+ with('master', 'logo.png').
+ and_return(true)
+
+ expect(repository.avatar).to eq('logo.png')
+
+ expect(repository).to_not receive(:blob_at_branch)
+ expect(repository.avatar).to eq('logo.png')
+ end
+ end
+
+ describe '#expire_avatar_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ before do
+ allow(repository).to receive(:cache).and_return(cache)
+ end
+
+ context 'without a branch or revision' do
+ it 'flushes the cache' do
+ expect(cache).to receive(:expire).with(:avatar)
+
+ repository.expire_avatar_cache
+ end
+ end
+
+ context 'with a branch' do
+ it 'does not flush the cache if the branch is not the default branch' do
+ expect(cache).not_to receive(:expire)
+
+ repository.expire_avatar_cache('cats')
+ end
+
+ it 'flushes the cache if the branch equals the default branch' do
+ expect(cache).to receive(:expire).with(:avatar)
+
+ repository.expire_avatar_cache(repository.root_ref)
+ end
+ end
+
+ context 'with a branch and revision' do
+ let(:commit) { double(:commit) }
+
+ before do
+ allow(repository).to receive(:commit).and_return(commit)
+ end
+
+ it 'does not flush the cache if the commit does not change any logos' do
+ diff = double(:diff, new_path: 'test.txt')
+
+ expect(commit).to receive(:diffs).and_return([diff])
+ expect(cache).not_to receive(:expire)
+
+ repository.expire_avatar_cache(repository.root_ref, '123')
+ end
+
+ it 'flushes the cache if the commit changes any of the logos' do
+ diff = double(:diff, new_path: Repository::AVATAR_FILES[0])
+
+ expect(commit).to receive(:diffs).and_return([diff])
+ expect(cache).to receive(:expire).with(:avatar)
+
+ repository.expire_avatar_cache(repository.root_ref, '123')
+ end
+ end
+ end
+
+ describe '#build_cache' do
+ let(:cache) { repository.send(:cache) }
+
+ it 'builds the caches if they do not already exist' do
+ expect(cache).to receive(:exist?).
+ exactly(repository.cache_keys.length).
+ times.
+ and_return(false)
+
+ repository.cache_keys.each do |key|
+ expect(repository).to receive(key)
+ end
+
+ repository.build_cache
+ end
+
+ it 'does not build any caches that already exist' do
+ expect(cache).to receive(:exist?).
+ exactly(repository.cache_keys.length).
+ times.
+ and_return(true)
+
+ repository.cache_keys.each do |key|
+ expect(repository).to_not receive(key)
+ end
+
+ repository.build_cache
+ end
+ end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 0ca82365b98..173628c08d0 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
require 'spec_helper'
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index eb2dbbdc5a4..5077ac7b62b 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
@@ -60,4 +59,48 @@ describe Snippet, models: true do
expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}"
end
end
+
+ describe '.search' do
+ let(:snippet) { create(:snippet) }
+
+ it 'returns snippets with a matching title' do
+ expect(described_class.search(snippet.title)).to eq([snippet])
+ end
+
+ it 'returns snippets with a partially matching title' do
+ expect(described_class.search(snippet.title[0..2])).to eq([snippet])
+ end
+
+ it 'returns snippets with a matching title regardless of the casing' do
+ expect(described_class.search(snippet.title.upcase)).to eq([snippet])
+ end
+
+ it 'returns snippets with a matching file name' do
+ expect(described_class.search(snippet.file_name)).to eq([snippet])
+ end
+
+ it 'returns snippets with a partially matching file name' do
+ expect(described_class.search(snippet.file_name[0..2])).to eq([snippet])
+ end
+
+ it 'returns snippets with a matching file name regardless of the casing' do
+ expect(described_class.search(snippet.file_name.upcase)).to eq([snippet])
+ end
+ end
+
+ describe '#search_code' do
+ let(:snippet) { create(:snippet, content: 'class Foo; end') }
+
+ it 'returns snippets with matching content' do
+ expect(described_class.search_code(snippet.content)).to eq([snippet])
+ end
+
+ it 'returns snippets with partially matching content' do
+ expect(described_class.search_code('class')).to eq([snippet])
+ end
+
+ it 'returns snippets with matching content regardless of the casing' do
+ expect(described_class.search_code('FOO')).to eq([snippet])
+ end
+ end
end
diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb
new file mode 100644
index 00000000000..c4ec7625cb0
--- /dev/null
+++ b/spec/models/spam_log_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe SpamLog, models: true do
+ describe 'associations' do
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:user) }
+ end
+
+ describe '#remove_user' do
+ it 'blocks the user' do
+ spam_log = build(:spam_log)
+
+ expect { spam_log.remove_user }.to change { spam_log.user.blocked? }.to(true)
+ end
+
+ it 'removes the user' do
+ spam_log = build(:spam_log)
+
+ expect { spam_log.remove_user }.to change { User.count }.by(-1)
+ end
+ end
+end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
new file mode 100644
index 00000000000..fe9ea7e7d1e
--- /dev/null
+++ b/spec/models/todo_spec.rb
@@ -0,0 +1,69 @@
+# == Schema Information
+#
+# Table name: todos
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# target_id :integer not null
+# target_type :string not null
+# author_id :integer
+# note_id :integer
+# action :integer not null
+# state :string not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+require 'spec_helper'
+
+describe Todo, models: true do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:author).class_name("User") }
+ it { is_expected.to belong_to(:note) }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:target).touch(true) }
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'respond to' do
+ it { is_expected.to respond_to(:author_name) }
+ it { is_expected.to respond_to(:author_email) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:action) }
+ it { is_expected.to validate_presence_of(:target) }
+ it { is_expected.to validate_presence_of(:user) }
+ end
+
+ describe '#body' do
+ before do
+ subject.target = build(:issue, title: 'Bugfix')
+ end
+
+ it 'returns target title when note is blank' do
+ subject.note = nil
+
+ expect(subject.body).to eq 'Bugfix'
+ end
+
+ it 'returns note when note is present' do
+ subject.note = build(:note, note: 'quick fix')
+
+ expect(subject.body).to eq 'quick fix'
+ end
+ end
+
+ describe '#done!' do
+ it 'changes state to done' do
+ todo = create(:todo, state: :pending)
+ expect { todo.done! }.to change(todo, :state).from('pending').to('done')
+ end
+
+ it 'does not raise error when is already done' do
+ todo = create(:todo, state: :done)
+ expect { todo.done! }.not_to raise_error
+ end
+ end
+end
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
new file mode 100644
index 00000000000..0737999e125
--- /dev/null
+++ b/spec/models/tree_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe Tree, models: true do
+ let(:repository) { create(:project).repository }
+ let(:sha) { repository.root_ref }
+
+ subject { described_class.new(repository, '54fcc214') }
+
+ describe '#readme' do
+ class FakeBlob
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def readme?
+ name =~ /^readme/i
+ end
+ end
+
+ it 'returns nil when repository does not contains a README file' do
+ files = [FakeBlob.new('file'), FakeBlob.new('license'), FakeBlob.new('copying')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme).to eq nil
+ end
+
+ it 'returns nil when repository does not contains a previewable README file' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README.pages'), FakeBlob.new('README.png')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme).to eq nil
+ end
+
+ it 'returns README when repository contains a previewable README file' do
+ files = [FakeBlob.new('README.png'), FakeBlob.new('README'), FakeBlob.new('file')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README'
+ end
+
+ it 'returns first previewable README when repository contains more than one' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README.md'), FakeBlob.new('README.asciidoc')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README.md'
+ end
+
+ it 'returns first plain text README when repository contains more than one' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.txt')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README'
+ end
+
+ it 'prioritizes previewable README file over one in plain text' do
+ files = [FakeBlob.new('file'), FakeBlob.new('README'), FakeBlob.new('README.md')]
+ expect(subject).to receive(:blobs).and_return(files)
+
+ expect(subject.readme.name).to eq 'README.md'
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 2f184bbaf92..0ab7fd88ce6 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,62 +2,63 @@
#
# Table name: users
#
-# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime
-# updated_at :datetime
-# name :string(255)
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# unlock_token :string(255)
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# color_scheme_id :integer default(1), not null
-# notification_level :integer default(1), not null
-# password_expires_at :datetime
-# created_by_id :integer
-# last_credential_check_at :datetime
-# avatar :string(255)
-# confirmation_token :string(255)
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
-# notification_email :string(255)
-# hide_no_password :boolean default(FALSE)
-# password_automatically_set :boolean default(FALSE)
-# location :string(255)
-# encrypted_otp_secret :string(255)
-# encrypted_otp_secret_iv :string(255)
-# encrypted_otp_secret_salt :string(255)
-# otp_required_for_login :boolean default(FALSE), not null
-# otp_backup_codes :text
-# public_email :string(255) default(""), not null
-# dashboard :integer default(0)
-# project_view :integer default(0)
-# consumed_timestep :integer
-# layout :integer default(0)
-# hide_project_limit :boolean default(FALSE)
+# id :integer not null, primary key
+# email :string(255) default(""), not null
+# encrypted_password :string(255) default(""), not null
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# created_at :datetime
+# updated_at :datetime
+# name :string(255)
+# admin :boolean default(FALSE), not null
+# projects_limit :integer default(10)
+# skype :string(255) default(""), not null
+# linkedin :string(255) default(""), not null
+# twitter :string(255) default(""), not null
+# authentication_token :string(255)
+# theme_id :integer default(1), not null
+# bio :string(255)
+# failed_attempts :integer default(0)
+# locked_at :datetime
+# username :string(255)
+# can_create_group :boolean default(TRUE), not null
+# can_create_team :boolean default(TRUE), not null
+# state :string(255)
+# color_scheme_id :integer default(1), not null
+# notification_level :integer default(1), not null
+# password_expires_at :datetime
+# created_by_id :integer
+# last_credential_check_at :datetime
+# avatar :string(255)
+# confirmation_token :string(255)
+# confirmed_at :datetime
+# confirmation_sent_at :datetime
+# unconfirmed_email :string(255)
+# hide_no_ssh_key :boolean default(FALSE)
+# website_url :string(255) default(""), not null
+# notification_email :string(255)
+# hide_no_password :boolean default(FALSE)
+# password_automatically_set :boolean default(FALSE)
+# location :string(255)
+# encrypted_otp_secret :string(255)
+# encrypted_otp_secret_iv :string(255)
+# encrypted_otp_secret_salt :string(255)
+# otp_required_for_login :boolean default(FALSE), not null
+# otp_backup_codes :text
+# public_email :string(255) default(""), not null
+# dashboard :integer default(0)
+# project_view :integer default(0)
+# consumed_timestep :integer
+# layout :integer default(0)
+# hide_project_limit :boolean default(FALSE)
+# unlock_token :string
+# otp_grace_period_started_at :datetime
#
require 'spec_helper'
@@ -90,6 +91,8 @@ describe User, models: true do
it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) }
it { is_expected.to have_many(:identities).dependent(:destroy) }
it { is_expected.to have_one(:abuse_report) }
+ it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
+ it { is_expected.to have_many(:todos).dependent(:destroy) }
end
describe 'validations' do
@@ -106,7 +109,7 @@ describe User, models: true do
end
it 'validates uniqueness' do
- expect(subject).to validate_uniqueness_of(:username)
+ expect(subject).to validate_uniqueness_of(:username).case_insensitive
end
end
@@ -117,37 +120,15 @@ describe User, models: true do
it { is_expected.to validate_length_of(:bio).is_within(0..255) }
- describe 'email' do
- it 'accepts info@example.com' do
- user = build(:user, email: 'info@example.com')
- expect(user).to be_valid
- end
-
- it 'accepts info+test@example.com' do
- user = build(:user, email: 'info+test@example.com')
- expect(user).to be_valid
- end
-
- it "accepts o'reilly@example.com" do
- user = build(:user, email: "o'reilly@example.com")
- expect(user).to be_valid
- end
-
- it 'rejects test@test@example.com' do
- user = build(:user, email: 'test@test@example.com')
- expect(user).to be_invalid
- end
-
- it 'rejects mailto:test@example.com' do
- user = build(:user, email: 'mailto:test@example.com')
- expect(user).to be_invalid
- end
+ it_behaves_like 'an object with email-formated attributes', :email do
+ subject { build(:user) }
+ end
- it "rejects lol!'+=?><#$%^&*()@gmail.com" do
- user = build(:user, email: "lol!'+=?><#$%^&*()@gmail.com")
- expect(user).to be_invalid
- end
+ it_behaves_like 'an object with email-formated attributes', :public_email, :notification_email do
+ subject { build(:user).tap { |user| user.emails << build(:email, email: email_value) } }
+ end
+ describe 'email' do
context 'when no signup domains listed' do
before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return([]) }
it 'accepts any email' do
@@ -199,6 +180,20 @@ describe User, models: true do
it { is_expected.to respond_to(:is_admin?) }
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:private_token) }
+ it { is_expected.to respond_to(:external?) }
+ end
+
+ describe 'before save hook' do
+ context 'when saving an external user' do
+ let(:user) { create(:user) }
+ let(:external_user) { create(:user, external: true) }
+
+ it "sets other properties aswell" do
+ expect(external_user.can_create_team).to be_falsey
+ expect(external_user.can_create_group).to be_falsey
+ expect(external_user.projects_limit).to be 0
+ end
+ end
end
describe '#confirm' do
@@ -275,6 +270,7 @@ describe User, models: true do
expect(user).to be_two_factor_enabled
expect(user.encrypted_otp_secret).not_to be_nil
expect(user.otp_backup_codes).not_to be_nil
+ expect(user.otp_grace_period_started_at).not_to be_nil
user.disable_two_factor!
@@ -283,6 +279,7 @@ describe User, models: true do
expect(user.encrypted_otp_secret_iv).to be_nil
expect(user.encrypted_otp_secret_salt).to be_nil
expect(user.otp_backup_codes).to be_nil
+ expect(user.otp_grace_period_started_at).to be_nil
end
end
@@ -421,6 +418,7 @@ describe User, models: true do
expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
+ expect(user.external).to be_falsey
end
end
@@ -454,17 +452,43 @@ describe User, models: true do
end
end
- describe 'search' do
- let(:user1) { create(:user, username: 'James', email: 'james@testing.com') }
- let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') }
+ describe '.search' do
+ let(:user) { create(:user) }
- it "should be case insensitive" do
- expect(User.search(user1.username.upcase).to_a).to eq([user1])
- expect(User.search(user1.username.downcase).to_a).to eq([user1])
- expect(User.search(user2.username.upcase).to_a).to eq([user2])
- expect(User.search(user2.username.downcase).to_a).to eq([user2])
- expect(User.search(user1.username.downcase).to_a.size).to eq(2)
- expect(User.search(user2.username.downcase).to_a.size).to eq(1)
+ it 'returns users with a matching name' do
+ expect(described_class.search(user.name)).to eq([user])
+ end
+
+ it 'returns users with a partially matching name' do
+ expect(described_class.search(user.name[0..2])).to eq([user])
+ end
+
+ it 'returns users with a matching name regardless of the casing' do
+ expect(described_class.search(user.name.upcase)).to eq([user])
+ end
+
+ it 'returns users with a matching Email' do
+ expect(described_class.search(user.email)).to eq([user])
+ end
+
+ it 'returns users with a partially matching Email' do
+ expect(described_class.search(user.email[0..2])).to eq([user])
+ end
+
+ it 'returns users with a matching Email regardless of the casing' do
+ expect(described_class.search(user.email.upcase)).to eq([user])
+ end
+
+ it 'returns users with a matching username' do
+ expect(described_class.search(user.username)).to eq([user])
+ end
+
+ it 'returns users with a partially matching username' do
+ expect(described_class.search(user.username[0..2])).to eq([user])
+ end
+
+ it 'returns users with a matching username regardless of the casing' do
+ expect(described_class.search(user.username.upcase)).to eq([user])
end
end
@@ -568,27 +592,39 @@ describe User, models: true do
end
end
- describe :ldap_user? do
- it "is true if provider name starts with ldap" do
- user = create(:omniauth_user, provider: 'ldapmain')
- expect( user.ldap_user? ).to be_truthy
- end
+ context 'ldap synchronized user' do
+ describe :ldap_user? do
+ it 'is true if provider name starts with ldap' do
+ user = create(:omniauth_user, provider: 'ldapmain')
+ expect(user.ldap_user?).to be_truthy
+ end
+
+ it 'is false for other providers' do
+ user = create(:omniauth_user, provider: 'other-provider')
+ expect(user.ldap_user?).to be_falsey
+ end
- it "is false for other providers" do
- user = create(:omniauth_user, provider: 'other-provider')
- expect( user.ldap_user? ).to be_falsey
+ it 'is false if no extern_uid is provided' do
+ user = create(:omniauth_user, extern_uid: nil)
+ expect(user.ldap_user?).to be_falsey
+ end
end
- it "is false if no extern_uid is provided" do
- user = create(:omniauth_user, extern_uid: nil)
- expect( user.ldap_user? ).to be_falsey
+ describe :ldap_identity do
+ it 'returns ldap identity' do
+ user = create :omniauth_user
+ expect(user.ldap_identity.provider).not_to be_empty
+ end
end
- end
- describe :ldap_identity do
- it "returns ldap identity" do
- user = create :omniauth_user
- expect(user.ldap_identity.provider).not_to be_empty
+ describe '#ldap_block' do
+ let(:user) { create(:omniauth_user, provider: 'ldapmain', name: 'John Smith') }
+
+ it 'blocks user flaging the action caming from ldap' do
+ user.ldap_block
+ expect(user.blocked?).to be_truthy
+ expect(user.ldap_blocked?).to be_truthy
+ end
end
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index c1b03838aa9..ddc49495eda 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -189,6 +189,38 @@ describe WikiPage, models: true do
end
end
+ describe '#historical?' do
+ before do
+ create_page('Update', 'content')
+ @page = wiki.find_page('Update')
+ 3.times { |i| @page.update("content #{i}") }
+ end
+
+ after do
+ destroy_page('Update')
+ end
+
+ it 'returns true when requesting an old version' do
+ old_version = @page.versions.last.to_s
+ old_page = wiki.find_page('Update', old_version)
+
+ expect(old_page.historical?).to eq true
+ end
+
+ it 'returns false when requesting latest version' do
+ latest_version = @page.versions.first.to_s
+ latest_page = wiki.find_page('Update', latest_version)
+
+ expect(latest_page.historical?).to eq false
+ end
+
+ it 'returns false when version is nil' do
+ latest_page = wiki.find_page('Update', nil)
+
+ expect(latest_page.historical?).to eq false
+ end
+ end
+
private
def remove_temp_repo(path)
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 36461e84c3a..55582aa53d2 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -7,8 +7,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:branch_name) { 'feature' }
let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
new file mode 100644
index 00000000000..967c34800d0
--- /dev/null
+++ b/spec/requests/api/builds_spec.rb
@@ -0,0 +1,244 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:api_user) { user }
+ let(:user2) { create(:user) }
+ let!(:project) { create(:project, creator_id: user.id) }
+ let!(:developer) { create(:project_member, :developer, user: user, project: project) }
+ let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) }
+ let(:commit) { create(:ci_commit, project: project)}
+ let(:build) { create(:ci_build, commit: commit) }
+
+ describe 'GET /projects/:id/builds ' do
+ let(:query) { '' }
+
+ before { get api("/projects/#{project.id}/builds?#{query}", api_user) }
+
+ context 'authorized user' do
+ it 'should return project builds' do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
+
+ context 'filter project with one scope element' do
+ let(:query) { 'scope=pending' }
+
+ it do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'filter project with array of scope elements' do
+ let(:query) { 'scope[0]=pending&scope[1]=running' }
+
+ it do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'respond 400 when scope contains invalid state' do
+ let(:query) { 'scope[0]=pending&scope[1]=unknown_status' }
+
+ it { expect(response.status).to eq(400) }
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return project builds' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/repository/commits/:sha/builds' do
+ before do
+ project.ensure_ci_commit(commit.sha)
+ get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user)
+ end
+
+ context 'authorized user' do
+ it 'should return project builds for specific commit' do
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return project builds' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/builds/:build_id' do
+ before { get api("/projects/#{project.id}/builds/#{build.id}", api_user) }
+
+ context 'authorized user' do
+ it 'should return specific build data' do
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq('test')
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return specific build data' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/builds/:build_id/artifacts' do
+ before { get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user) }
+
+ context 'build with artifacts' do
+ let(:build) { create(:ci_build, :artifacts, commit: commit) }
+
+ context 'authorized user' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding'=>'binary',
+ 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
+ end
+
+ it 'should return specific build artifacts' do
+ expect(response.status).to eq(200)
+ expect(response.headers).to include(download_headers)
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return specific build artifacts' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ it 'should not return build artifacts if not uploaded' do
+ expect(response.status).to eq(404)
+ end
+ end
+
+ describe 'GET /projects/:id/builds/:build_id/trace' do
+ let(:build) { create(:ci_build, :trace, commit: commit) }
+
+ before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) }
+
+ context 'authorized user' do
+ it 'should return specific build trace' do
+ expect(response.status).to eq(200)
+ expect(response.body).to eq(build.trace)
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not return specific build trace' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/builds/:build_id/cancel' do
+ before { post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user) }
+
+ context 'authorized user' do
+ context 'user with :update_build persmission' do
+ it 'should cancel running or pending build' do
+ expect(response.status).to eq(201)
+ expect(project.builds.first.status).to eq('canceled')
+ end
+ end
+
+ context 'user without :update_build permission' do
+ let(:api_user) { user2 }
+
+ it 'should not cancel build' do
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not cancel build' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/builds/:build_id/retry' do
+ let(:build) { create(:ci_build, :canceled, commit: commit) }
+
+ before { post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user) }
+
+ context 'authorized user' do
+ context 'user with :update_build permission' do
+ it 'should retry non-running build' do
+ expect(response.status).to eq(201)
+ expect(project.builds.first.status).to eq('canceled')
+ expect(json_response['status']).to eq('pending')
+ end
+ end
+
+ context 'user without :update_build permission' do
+ let(:api_user) { user2 }
+
+ it 'should not retry build' do
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'should not retry build' do
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/builds/:build_id/erase' do
+ before do
+ post api("/projects/#{project.id}/builds/#{build.id}/erase", user)
+ end
+
+ context 'build is erasable' do
+ let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, commit: commit) }
+
+ it 'should erase build content' do
+ expect(response.status).to eq 201
+ expect(build.trace).to be_empty
+ expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_metadata.exists?).to be_falsy
+ end
+
+ it 'should update build' do
+ expect(build.reload.erased_at).to be_truthy
+ expect(build.reload.erased_by).to eq user
+ end
+ end
+
+ context 'build is not erasable' do
+ let(:build) { create(:ci_build, :trace, project: project, commit: commit) }
+
+ it 'should respond with forbidden' do
+ expect(response.status).to eq 403
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb
index a28607bd240..429a24109fd 100644
--- a/spec/requests/api/commit_status_spec.rb
+++ b/spec/requests/api/commit_status_spec.rb
@@ -1,86 +1,126 @@
require 'spec_helper'
-describe API::API, api: true do
+describe API::CommitStatus, api: true do
include ApiHelpers
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let!(:project) { create(:project, creator_id: user.id) }
- let!(:reporter) { create(:project_member, user: user, project: project, access_level: ProjectMember::REPORTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+
+ let!(:project) { create(:project) }
let(:commit) { project.repository.commit }
- let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
let(:commit_status) { create(:commit_status, commit: ci_commit) }
+ let(:guest) { create_user(:guest) }
+ let(:reporter) { create_user(:reporter) }
+ let(:developer) { create_user(:developer) }
+ let(:sha) { commit.id }
+
describe "GET /projects/:id/repository/commits/:sha/statuses" do
- context "reporter user" do
- let(:statuses_id) { json_response.map { |status| status['id'] } }
-
- before do
- @status1 = create(:commit_status, commit: ci_commit, status: 'running')
- @status2 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'pending')
- @status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running', allow_failure: true)
- @status4 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'success')
- @status5 = create(:commit_status, commit: ci_commit, ref: 'develop', status: 'success')
- @status6 = create(:commit_status, commit: ci_commit, status: 'success')
- end
+ let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
- it "should return latest commit statuses" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user)
- expect(response.status).to eq(200)
+ context 'ci commit exists' do
+ let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
- expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status3.id, @status4.id, @status5.id, @status6.id)
- json_response.sort_by!{ |status| status['id'] }
- expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false])
+ it_behaves_like 'a paginated resources' do
+ let(:request) { get api(get_url, reporter) }
end
- it "should return all commit statuses" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", user)
- expect(response.status).to eq(200)
+ context "reporter user" do
+ let(:statuses_id) { json_response.map { |status| status['id'] } }
- expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status1.id, @status2.id, @status3.id, @status4.id, @status5.id, @status6.id)
- end
+ def create_status(opts = {})
+ create(:commit_status, { commit: ci_commit }.merge(opts))
+ end
- it "should return latest commit statuses for specific ref" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", user)
- expect(response.status).to eq(200)
+ let!(:status1) { create_status(status: 'running') }
+ let!(:status2) { create_status(name: 'coverage', status: 'pending') }
+ let!(:status3) { create_status(ref: 'develop', status: 'running', allow_failure: true) }
+ let!(:status4) { create_status(name: 'coverage', status: 'success') }
+ let!(:status5) { create_status(name: 'coverage', ref: 'develop', status: 'success') }
+ let!(:status6) { create_status(status: 'success') }
- expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status3.id, @status5.id)
+ context 'latest commit statuses' do
+ before { get api(get_url, reporter) }
+
+ it 'returns latest commit statuses' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id)
+ json_response.sort_by!{ |status| status['id'] }
+ expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false])
+ end
+ end
+
+ context 'all commit statuses' do
+ before { get api(get_url, reporter), all: 1 }
+
+ it 'returns all commit statuses' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status1.id, status2.id,
+ status3.id, status4.id,
+ status5.id, status6.id)
+ end
+ end
+
+ context 'latest commit statuses for specific ref' do
+ before { get api(get_url, reporter), ref: 'develop' }
+
+ it 'returns latest commit statuses for specific ref' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status3.id, status5.id)
+ end
+ end
+
+ context 'latest commit statues for specific name' do
+ before { get api(get_url, reporter), name: 'coverage' }
+
+ it 'return latest commit statuses for specific name' do
+ expect(response.status).to eq(200)
+
+ expect(json_response).to be_an Array
+ expect(statuses_id).to contain_exactly(status4.id, status5.id)
+ end
+ end
end
+ end
- it "should return latest commit statuses for specific name" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", user)
- expect(response.status).to eq(200)
+ context 'ci commit does not exist' do
+ before { get api(get_url, reporter) }
+ it 'returns empty array' do
+ expect(response.status).to eq 200
expect(json_response).to be_an Array
- expect(statuses_id).to contain_exactly(@status3.id, @status4.id)
+ expect(json_response).to be_empty
end
end
context "guest user" do
+ before { get api(get_url, guest) }
+
it "should not return project commits" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user2)
expect(response.status).to eq(403)
end
end
context "unauthorized user" do
+ before { get api(get_url) }
+
it "should not return project commits" do
- get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses")
expect(response.status).to eq(401)
end
end
end
describe 'POST /projects/:id/statuses/:sha' do
- let(:post_url) { "/projects/#{project.id}/statuses/#{commit.id}" }
+ let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" }
- context 'reporter user' do
- context 'should create commit status' do
- it 'with only required parameters' do
- post api(post_url, user), state: 'success'
+ context 'developer user' do
+ context 'only required parameters' do
+ before { post api(post_url, developer), state: 'success' }
+
+ it 'creates commit status' do
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
@@ -89,9 +129,17 @@ describe API::API, api: true do
expect(json_response['target_url']).to be_nil
expect(json_response['description']).to be_nil
end
+ end
+
+ context 'with all optional parameters' do
+ before do
+ optional_params = { state: 'success', context: 'coverage',
+ ref: 'develop', target_url: 'url', description: 'test' }
+
+ post api(post_url, developer), optional_params
+ end
- it 'with all optional parameters' do
- post api(post_url, user), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test'
+ it 'creates commit status' do
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
@@ -102,36 +150,60 @@ describe API::API, api: true do
end
end
- context 'should not create commit status' do
- it 'with invalid state' do
- post api(post_url, user), state: 'invalid'
+ context 'invalid status' do
+ before { post api(post_url, developer), state: 'invalid' }
+
+ it 'does not create commit status' do
expect(response.status).to eq(400)
end
+ end
+
+ context 'request without state' do
+ before { post api(post_url, developer) }
- it 'without state' do
- post api(post_url, user)
+ it 'does not create commit status' do
expect(response.status).to eq(400)
end
+ end
- it 'invalid commit' do
- post api("/projects/#{project.id}/statuses/invalid_sha", user), state: 'running'
+ context 'invalid commit' do
+ let(:sha) { 'invalid_sha' }
+ before { post api(post_url, developer), state: 'running' }
+
+ it 'returns not found error' do
expect(response.status).to eq(404)
end
end
end
+ context 'reporter user' do
+ before { post api(post_url, reporter) }
+
+ it 'should not create commit status' do
+ expect(response.status).to eq(403)
+ end
+ end
+
context 'guest user' do
+ before { post api(post_url, guest) }
+
it 'should not create commit status' do
- post api(post_url, user2)
expect(response.status).to eq(403)
end
end
context 'unauthorized user' do
+ before { post api(post_url) }
+
it 'should not create commit status' do
- post api(post_url)
expect(response.status).to eq(401)
end
end
end
+
+ def create_user(access_level_trait)
+ user = create(:user)
+ create(:project_member, access_level_trait, user: user, project: project)
+ user
+ end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 49acc3368f4..7ff21175c1b 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index 3fe7efff5ba..fa94e03ec32 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -12,7 +12,7 @@ describe API::API, api: true do
end
let(:project_user2) do
- create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST)
+ create(:project_member, :guest, user: user2, project: project)
end
describe 'POST /projects/fork/:id' do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 8d0ae1475c2..22802dd0e05 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -54,6 +54,18 @@ describe API::API, api: true do
project.team << [user, :developer]
end
+ context "git push with project.wiki" do
+ it 'responds with success' do
+ project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki')
+ project_wiki.team << [user, :developer]
+
+ push(key, project_wiki)
+
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_truthy
+ end
+ end
+
context "git pull" do
it do
pull(key, project)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 5e65ad18c0e..bb2ab058003 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -3,7 +3,11 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
- let!(:project) { create(:project, namespace: user.namespace ) }
+ let(:non_member) { create(:user) }
+ let(:author) { create(:author) }
+ let(:assignee) { create(:assignee) }
+ let(:admin) { create(:admin) }
+ let!(:project) { create(:project, :public, namespace: user.namespace ) }
let!(:closed_issue) do
create :closed_issue,
author: user,
@@ -12,6 +16,13 @@ describe API::API, api: true do
state: :closed,
milestone: milestone
end
+ let!(:confidential_issue) do
+ create :issue,
+ :confidential,
+ project: project,
+ author: author,
+ assignee: assignee
+ end
let!(:issue) do
create :issue,
author: user,
@@ -46,10 +57,10 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it "should add pagination headers" do
- get api("/issues?per_page=3", user)
+ it "should add pagination headers and keep query params" do
+ get api("/issues?state=closed&per_page=3", user)
expect(response.headers['Link']).to eq(
- '<http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="last"'
+ '<http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="last"' % [user.private_token, user.private_token]
)
end
@@ -123,10 +134,43 @@ describe API::API, api: true do
let(:base_url) { "/projects/#{project.id}" }
let(:title) { milestone.title }
- it "should return project issues" do
+ it 'should return project issues without confidential issues for non project members' do
+ get api("#{base_url}/issues", non_member)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for author' do
+ get api("#{base_url}/issues", author)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for assignee' do
+ get api("#{base_url}/issues", assignee)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['title']).to eq(issue.title)
+ end
+
+ it 'should return project confidential issues for admin' do
+ get api("#{base_url}/issues", admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
expect(json_response.first['title']).to eq(issue.title)
end
@@ -206,6 +250,41 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/54321", user)
expect(response.status).to eq(404)
end
+
+ context 'confidential issues' do
+ it "should return 404 for non project members" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member)
+ expect(response.status).to eq(404)
+ end
+
+ it "should return confidential issue for project members" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for author" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for assignee" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+
+ it "should return confidential issue for admin" do
+ get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin)
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(confidential_issue.title)
+ expect(json_response['iid']).to eq(confidential_issue.iid)
+ end
+ end
end
describe "POST /projects/:id/issues" do
@@ -241,6 +320,37 @@ describe API::API, api: true do
end
end
+ describe 'POST /projects/:id/issues with spam filtering' do
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:check_for_spam?).and_return(true)
+ allow(endpoint).to receive(:is_spam?).and_return(true)
+ end
+ end
+
+ let(:params) do
+ {
+ title: 'new issue',
+ description: 'content here',
+ labels: 'label, label2'
+ }
+ end
+
+ it "should not create a new project issue" do
+ expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count)
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq({ "error" => "Spam detected" })
+
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('new issue')
+ expect(spam_logs[0].description).to eq('content here')
+ expect(spam_logs[0].user).to eq(user)
+ expect(spam_logs[0].noteable_type).to eq('Issue')
+ expect(spam_logs[0].project_id).to eq(project.id)
+ end
+ end
+
describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
@@ -263,6 +373,35 @@ describe API::API, api: true do
expect(response.status).to eq(400)
expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
end
+
+ context 'confidential issues' do
+ it "should return 403 for non project members" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member),
+ title: 'updated title'
+ expect(response.status).to eq(403)
+ end
+
+ it "should update a confidential issue for project members" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it "should update a confidential issue for author" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+
+ it "should update a confidential issue for admin" do
+ put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin),
+ title: 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
+ end
+ end
end
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e194eb93cf4..4fd1df25568 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -10,6 +10,7 @@ describe API::API, api: true do
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
+ let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
before do
project.team << [user, :reporters]
@@ -109,12 +110,13 @@ describe API::API, api: true do
end
end
- describe "GET /projects/:id/merge_request/:merge_request_id" do
+ describe "GET /projects/:id/merge_requests/:merge_request_id" do
it "should return merge_request" do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
expect(response.status).to eq(200)
expect(json_response['title']).to eq(merge_request.title)
expect(json_response['iid']).to eq(merge_request.iid)
+ expect(json_response['merge_status']).to eq('can_be_merged')
end
it 'should return merge_request by iid' do
@@ -126,14 +128,14 @@ describe API::API, api: true do
end
it "should return a 404 error if merge_request_id not found" do
- get api("/projects/#{project.id}/merge_request/999", user)
+ get api("/projects/#{project.id}/merge_requests/999", user)
expect(response.status).to eq(404)
end
end
- describe 'GET /projects/:id/merge_request/:merge_request_id/commits' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/commits' do
context 'valid merge request' do
- before { get api("/projects/#{project.id}/merge_request/#{merge_request.id}/commits", user) }
+ before { get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user) }
let(:commit) { merge_request.commits.first }
it { expect(response.status).to eq 200 }
@@ -143,20 +145,20 @@ describe API::API, api: true do
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_request/999/commits", user)
+ get api("/projects/#{project.id}/merge_requests/999/commits", user)
expect(response.status).to eq(404)
end
end
- describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do
it 'should return the change information of the merge_request' do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
expect(response.status).to eq 200
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end
it 'returns a 404 when merge_request_id not found' do
- get api("/projects/#{project.id}/merge_request/999/changes", user)
+ get api("/projects/#{project.id}/merge_requests/999/changes", user)
expect(response.status).to eq(404)
end
end
@@ -169,10 +171,12 @@ describe API::API, api: true do
source_branch: 'feature_conflict',
target_branch: 'master',
author: user,
- labels: 'label, label2'
+ labels: 'label, label2',
+ milestone_id: milestone.id
expect(response.status).to eq(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['labels']).to eq(['label', 'label2'])
+ expect(json_response['milestone']['id']).to eq(milestone.id)
end
it "should return 422 when source_branch equals target_branch" do
@@ -311,19 +315,19 @@ describe API::API, api: true do
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do
it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
expect(response.status).to eq(200)
expect(json_response['state']).to eq('closed')
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do
+ describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do
let(:ci_commit) { create(:ci_commit_without_jobs) }
it "should return merge_request in case of success" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(200)
end
@@ -332,7 +336,7 @@ describe API::API, api: true do
allow_any_instance_of(MergeRequest).
to receive(:can_be_merged?).and_return(false)
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(406)
expect(json_response['message']).to eq('Branch cannot be merged')
@@ -340,14 +344,14 @@ describe API::API, api: true do
it "should return 405 if merge_request is not open" do
merge_request.close
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
it "should return 405 if merge_request is a work in progress" do
merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response.status).to eq(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
@@ -355,7 +359,7 @@ describe API::API, api: true do
it "should return 401 if user has no permissions to merge" do
user2 = create(:user)
project.team << [user2, :reporter]
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user2)
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2)
expect(response.status).to eq(401)
expect(json_response['message']).to eq('401 Unauthorized')
end
@@ -364,7 +368,7 @@ describe API::API, api: true do
allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
allow(ci_commit).to receive(:active?).and_return(true)
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), merge_when_build_succeeds: true
expect(response.status).to eq(200)
expect(json_response['title']).to eq('Test')
@@ -372,33 +376,39 @@ describe API::API, api: true do
end
end
- describe "PUT /projects/:id/merge_request/:merge_request_id" do
- it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
+ describe "PUT /projects/:id/merge_requests/:merge_request_id" do
+ it "updates title and returns merge_request" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), title: "New title"
expect(response.status).to eq(200)
expect(json_response['title']).to eq('New title')
end
- it "should return merge_request" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), description: "New description"
+ it "updates description and returns merge_request" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), description: "New description"
expect(response.status).to eq(200)
expect(json_response['description']).to eq('New description')
end
+ it "updates milestone_id and returns merge_request" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), milestone_id: milestone.id
+ expect(response.status).to eq(200)
+ expect(json_response['milestone']['id']).to eq(milestone.id)
+ end
+
it "should return 400 when source_branch is specified" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
expect(response.status).to eq(400)
end
it "should return merge_request with renamed target_branch" do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki"
expect(response.status).to eq(200)
expect(json_response['target_branch']).to eq('wiki')
end
it 'should return 400 on invalid label names' do
- put api("/projects/#{project.id}/merge_request/#{merge_request.id}",
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}",
user),
title: 'new issue',
labels: 'label, ?'
@@ -407,11 +417,11 @@ describe API::API, api: true do
end
end
- describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
+ describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do
it "should return comment" do
original_count = merge_request.notes.size
- post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment"
expect(response.status).to eq(201)
expect(json_response['note']).to eq('My comment')
expect(json_response['author']['name']).to eq(user.name)
@@ -420,20 +430,20 @@ describe API::API, api: true do
end
it "should return 400 if note is missing" do
- post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response.status).to eq(400)
end
it "should return 404 if note is attached to non existent merge request" do
- post api("/projects/#{project.id}/merge_request/404/comments", user),
+ post api("/projects/#{project.id}/merge_requests/404/comments", user),
note: 'My comment'
expect(response.status).to eq(404)
end
end
- describe "GET :id/merge_request/:merge_request_id/comments" do
+ describe "GET :id/merge_requests/:merge_request_id/comments" do
it "should return merge_request comments ordered by created_at" do
- get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
@@ -443,11 +453,33 @@ describe API::API, api: true do
end
it "should return a 404 error if merge_request_id not found" do
- get api("/projects/#{project.id}/merge_request/999/comments", user)
+ get api("/projects/#{project.id}/merge_requests/999/comments", user)
expect(response.status).to eq(404)
end
end
+ describe 'GET :id/merge_requests/:merge_request_id/closes_issues' do
+ it 'returns the issue that will be closed on merge' do
+ issue = create(:issue, project: project)
+ mr = merge_request.tap do |mr|
+ mr.update_attribute(:description, "Closes #{issue.to_reference(mr.project)}")
+ end
+
+ get api("/projects/#{project.id}/merge_requests/#{mr.id}/closes_issues", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(issue.id)
+ end
+
+ it 'returns an empty array when there are no issues to be closed' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+ end
+
def mr_with_later_created_and_updated_at_time
merge_request
merge_request.created_at += 1.hour
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 8b177af4689..39f9a06fe1b 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -10,9 +10,32 @@ describe API::API, api: true do
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) }
let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) }
+
+ # For testing the cross-reference of a private issue in a public issue
+ let(:private_user) { create(:user) }
+ let(:private_project) do
+ create(:project, namespace: private_user.namespace).
+ tap { |p| p.team << [private_user, :master] }
+ end
+ let(:private_issue) { create(:issue, project: private_project) }
+
+ let(:ext_proj) { create(:project, :public) }
+ let(:ext_issue) { create(:issue, project: ext_proj) }
+
+ let!(:cross_reference_note) do
+ create :note,
+ noteable: ext_issue, project: ext_proj,
+ note: "mentioned in issue #{private_issue.to_reference(ext_proj)}",
+ system: true
+ end
+
before { project.team << [user, :reporter] }
describe "GET /projects/:id/noteable/:noteable_id/notes" do
+ it_behaves_like 'a paginated resources' do
+ let(:request) { get api("/projects/#{project.id}/issues/#{issue.id}/notes", user) }
+ end
+
context "when noteable is an Issue" do
it "should return an array of issue notes" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
@@ -25,6 +48,24 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/123/notes", user)
expect(response.status).to eq(404)
end
+
+ context "that references a private issue" do
+ it "should return an empty array" do
+ get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response).to be_empty
+ end
+
+ context "and current user can view the note" do
+ it "should return an empty array" do
+ get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['body']).to eq(cross_reference_note.note)
+ end
+ end
+ end
end
context "when noteable is a Snippet" do
@@ -68,6 +109,21 @@ describe API::API, api: true do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
expect(response.status).to eq(404)
end
+
+ context "that references a private issue" do
+ it "should return a 404 error" do
+ get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user)
+ expect(response.status).to eq(404)
+ end
+
+ context "and current user can view the note" do
+ it "should return an issue note by id" do
+ get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user)
+ expect(response.status).to eq(200)
+ expect(json_response['body']).to eq(cross_reference_note.note)
+ end
+ end
+ end
end
context "when noteable is a Snippet" do
diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb
index 6358f6a2a4a..4301588b16a 100644
--- a/spec/requests/api/project_members_spec.rb
+++ b/spec/requests/api/project_members_spec.rb
@@ -6,8 +6,8 @@ describe API::API, api: true do
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
- let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+ let(:project_member) { create(:project_member, :master, user: user, project: project) }
+ let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
describe "GET /projects/:id/members" do
before { project_member }
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
new file mode 100644
index 00000000000..3722ddf5a33
--- /dev/null
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -0,0 +1,18 @@
+require 'rails_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ describe 'GET /projects/:project_id/snippets/:id' do
+ # TODO (rspeicher): Deprecated; remove in 9.0
+ it 'always exposes expires_at as nil' do
+ admin = create(:admin)
+ snippet = create(:project_snippet, author: admin)
+
+ get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin)
+
+ expect(json_response).to have_key('expires_at')
+ expect(json_response['expires_at']).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 7f0f9454b10..a6699cdc81c 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -12,8 +12,8 @@ describe API::API, api: true do
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
- let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
+ let(:project_member) { create(:project_member, :master, user: user, project: project) }
+ let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
let(:project3) do
create(:project,
@@ -90,6 +90,29 @@ describe API::API, api: true do
end
end
+ context 'and using the visibility filter' do
+ it 'should filter based on private visibility param' do
+ get api('/projects', user), { visibility: 'private' }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count)
+ end
+
+ it 'should filter based on internal visibility param' do
+ get api('/projects', user), { visibility: 'internal' }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count)
+ end
+
+ it 'should filter based on public visibility param' do
+ get api('/projects', user), { visibility: 'public' }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count)
+ end
+ end
+
context 'and using sorting' do
before do
project2
@@ -353,6 +376,20 @@ describe API::API, api: true do
end
end
+ describe "POST /projects/:id/uploads" do
+ before { project }
+
+ it "uploads the file and returns its info" do
+ post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")
+
+ expect(response.status).to be(201)
+ expect(json_response['alt']).to eq("dk")
+ expect(json_response['url']).to start_with("/uploads/")
+ expect(json_response['url']).to end_with("/dk.png")
+ expect(json_response['is_image']).to eq(true)
+ end
+ end
+
describe 'GET /projects/:id' do
before { project }
before { project_member }
@@ -382,6 +419,15 @@ describe API::API, api: true do
expect(response.status).to eq(404)
end
+ it 'should handle users with dots' do
+ dot_user = create(:user, username: 'dot.user')
+ project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace)
+
+ get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq(project.name)
+ end
+
describe 'permissions' do
context 'all projects' do
it 'Contains permission information' do
@@ -701,6 +747,42 @@ describe API::API, api: true do
end
end
+ describe "POST /projects/:id/share" do
+ let(:group) { create(:group) }
+
+ it "should share project with group" do
+ expect do
+ post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
+ end.to change { ProjectGroupLink.count }.by(1)
+
+ expect(response.status).to eq 201
+ expect(json_response['group_id']).to eq group.id
+ expect(json_response['group_access']).to eq Gitlab::Access::DEVELOPER
+ end
+
+ it "should return a 400 error when group id is not given" do
+ post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER
+ expect(response.status).to eq 400
+ end
+
+ it "should return a 400 error when access level is not given" do
+ post api("/projects/#{project.id}/share", user), group_id: group.id
+ expect(response.status).to eq 400
+ end
+
+ it "should return a 400 error when sharing is disabled" do
+ project.namespace.update(share_with_group_lock: true)
+ post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
+ expect(response.status).to eq 400
+ end
+
+ it "should return a 409 error when wrong params passed" do
+ post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234
+ expect(response.status).to eq 409
+ expect(json_response['message']).to eq 'Group access is not included in the list'
+ end
+ end
+
describe 'GET /projects/search/:query' do
let!(:query) { 'query'}
let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 4911cdd9da6..7cf4a01d76b 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -4,12 +4,13 @@ require 'mime/types'
describe API::API, api: true do
include ApiHelpers
include RepoHelpers
+ include WorkhorseHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
describe "GET /projects/:id/repository/tree" do
context "authorized user" do
@@ -91,21 +92,27 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
end
it "should get the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
end
it "should get the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
+ type, params = workhorse_send_data
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
end
it "should return 404 for invalid sha" do
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
new file mode 100644
index 00000000000..3af61d4b335
--- /dev/null
+++ b/spec/requests/api/runners_spec.rb
@@ -0,0 +1,464 @@
+require 'spec_helper'
+
+describe API::Runners, api: true do
+ include ApiHelpers
+
+ let(:admin) { create(:user, :admin) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+
+ let(:project) { create(:project, creator_id: user.id) }
+ let(:project2) { create(:project, creator_id: user.id) }
+
+ let!(:shared_runner) { create(:ci_runner, :shared) }
+ let!(:unused_specific_runner) { create(:ci_runner) }
+
+ let!(:specific_runner) do
+ create(:ci_runner).tap do |runner|
+ create(:ci_runner_project, runner: runner, project: project)
+ end
+ end
+
+ let!(:two_projects_runner) do
+ create(:ci_runner).tap do |runner|
+ create(:ci_runner_project, runner: runner, project: project)
+ create(:ci_runner_project, runner: runner, project: project2)
+ end
+ end
+
+ before do
+ # Set project access for users
+ create(:project_member, :master, user: user, project: project)
+ create(:project_member, :master, user: user, project: project2)
+ create(:project_member, :reporter, user: user2, project: project)
+ end
+
+ describe 'GET /runners' do
+ context 'authorized user' do
+ it 'should return user available runners' do
+ get api('/runners', user)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_falsey
+ end
+
+ it 'should filter runners by scope' do
+ get api('/runners?scope=active', user)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_falsey
+ end
+
+ it 'should avoid filtering if scope is invalid' do
+ get api('/runners?scope=unknown', user)
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return runners' do
+ get api('/runners')
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /runners/all' do
+ context 'authorized user' do
+ context 'with admin privileges' do
+ it 'should return all runners' do
+ get api('/runners/all', admin)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_truthy
+ end
+ end
+
+ context 'without admin privileges' do
+ it 'should not return runners list' do
+ get api('/runners/all', user)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ it 'should filter runners by scope' do
+ get api('/runners/all?scope=specific', admin)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_falsey
+ end
+
+ it 'should avoid filtering if scope is invalid' do
+ get api('/runners?scope=unknown', admin)
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return runners' do
+ get api('/runners')
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it "should return runner's details" do
+ get api("/runners/#{shared_runner.id}", admin)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(shared_runner.description)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it "should return runner's details" do
+ get api("/runners/#{specific_runner.id}", admin)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(specific_runner.description)
+ end
+ end
+
+ it 'should return 404 if runner does not exists' do
+ get api('/runners/9999', admin)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context "runner project's administrative user" do
+ context 'when runner is not shared' do
+ it "should return runner's details" do
+ get api("/runners/#{specific_runner.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(specific_runner.description)
+ end
+ end
+
+ context 'when runner is shared' do
+ it "should return runner's details" do
+ get api("/runners/#{shared_runner.id}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq(shared_runner.description)
+ end
+ end
+ end
+
+ context 'other authorized user' do
+ it "should not return runner's details" do
+ get api("/runners/#{specific_runner.id}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "should not return runner's details" do
+ get api("/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'PUT /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it 'should update runner' do
+ description = shared_runner.description
+ active = shared_runner.active
+
+ put api("/runners/#{shared_runner.id}", admin), description: "#{description}_updated", active: !active,
+ tag_list: ['ruby2.1', 'pgsql', 'mysql']
+ shared_runner.reload
+
+ expect(response.status).to eq(200)
+ expect(shared_runner.description).to eq("#{description}_updated")
+ expect(shared_runner.active).to eq(!active)
+ expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql')
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should update runner' do
+ description = specific_runner.description
+ put api("/runners/#{specific_runner.id}", admin), description: 'test'
+ specific_runner.reload
+
+ expect(response.status).to eq(200)
+ expect(specific_runner.description).to eq('test')
+ expect(specific_runner.description).not_to eq(description)
+ end
+ end
+
+ it 'should return 404 if runner does not exists' do
+ put api('/runners/9999', admin), description: 'test'
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user' do
+ context 'when runner is shared' do
+ it 'should not update runner' do
+ put api("/runners/#{shared_runner.id}", user)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should not update runner without access to it' do
+ put api("/runners/#{specific_runner.id}", user2)
+
+ expect(response.status).to eq(403)
+ end
+
+ it 'should update runner with access to it' do
+ description = specific_runner.description
+ put api("/runners/#{specific_runner.id}", admin), description: 'test'
+ specific_runner.reload
+
+ expect(response.status).to eq(200)
+ expect(specific_runner.description).to eq('test')
+ expect(specific_runner.description).not_to eq(description)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not delete runner' do
+ put api("/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /runners/:id' do
+ context 'admin user' do
+ context 'when runner is shared' do
+ it 'should delete runner' do
+ expect do
+ delete api("/runners/#{shared_runner.id}", admin)
+ end.to change{ Ci::Runner.shared.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should delete unused runner' do
+ expect do
+ delete api("/runners/#{unused_specific_runner.id}", admin)
+ end.to change{ Ci::Runner.specific.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should delete used runner' do
+ expect do
+ delete api("/runners/#{specific_runner.id}", admin)
+ end.to change{ Ci::Runner.specific.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ it 'should return 404 if runner does not exists' do
+ delete api('/runners/9999', admin)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user' do
+ context 'when runner is shared' do
+ it 'should not delete runner' do
+ delete api("/runners/#{shared_runner.id}", user)
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'when runner is not shared' do
+ it 'should not delete runner without access to it' do
+ delete api("/runners/#{specific_runner.id}", user2)
+ expect(response.status).to eq(403)
+ end
+
+ it 'should not delete runner with more than one associated project' do
+ delete api("/runners/#{two_projects_runner.id}", user)
+ expect(response.status).to eq(403)
+ end
+
+ it 'should delete runner for one owned project' do
+ expect do
+ delete api("/runners/#{specific_runner.id}", user)
+ end.to change{ Ci::Runner.specific.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not delete runner' do
+ delete api("/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/runners' do
+ context 'authorized user with master privileges' do
+ it "should return project's runners" do
+ get api("/projects/#{project.id}/runners", user)
+ shared = json_response.any?{ |r| r['is_shared'] }
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(shared).to be_truthy
+ end
+ end
+
+ context 'authorized user without master privileges' do
+ it "should not return project's runners" do
+ get api("/projects/#{project.id}/runners", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "should not return project's runners" do
+ get api("/projects/#{project.id}/runners")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/runners' do
+ context 'authorized user' do
+ it 'should enable specific runner' do
+ specific_runner2 = create(:ci_runner).tap do |runner|
+ create(:ci_runner_project, runner: runner, project: project2)
+ end
+
+ expect do
+ post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
+ end.to change{ project.runners.count }.by(+1)
+ expect(response.status).to eq(201)
+ end
+
+ it 'should avoid changes when enabling already enabled runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
+ end.to change{ project.runners.count }.by(0)
+ expect(response.status).to eq(201)
+ end
+
+ it 'should not enable shared runner' do
+ post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id
+
+ expect(response.status).to eq(403)
+ end
+
+ context 'user is admin' do
+ it 'should enable any specific runner' do
+ expect do
+ post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id
+ end.to change{ project.runners.count }.by(+1)
+ expect(response.status).to eq(201)
+ end
+ end
+
+ context 'user is not admin' do
+ it 'should not enable runner without access to' do
+ post api("/projects/#{project.id}/runners", user), runner_id: unused_specific_runner.id
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ it 'should raise an error when no runner_id param is provided' do
+ post api("/projects/#{project.id}/runners", admin)
+
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'authorized user without permissions' do
+ it 'should not enable runner' do
+ post api("/projects/#{project.id}/runners", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not enable runner' do
+ post api("/projects/#{project.id}/runners")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/runners/:runner_id' do
+ context 'authorized user' do
+ context 'when runner have more than one associated projects' do
+ it "should disable project's runner" do
+ expect do
+ delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
+ end.to change{ project.runners.count }.by(-1)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when runner have one associated projects' do
+ it "should not disable project's runner" do
+ expect do
+ delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
+ end.to change{ project.runners.count }.by(0)
+ expect(response.status).to eq(403)
+ end
+ end
+
+ it 'should return 404 is runner is not found' do
+ delete api("/projects/#{project.id}/runners/9999", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user without permissions' do
+ it "should not disable project's runner" do
+ delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it "should not disable project's runner" do
+ delete api("/projects/#{project.id}/runners/#{specific_runner.id}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 17f2643fd45..a15be07ed57 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -8,8 +8,8 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
- let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
- let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
describe "GET /projects/:id/repository/tags" do
let(:tag_name) { project.repository.tag_names.sort.reverse.first }
@@ -65,6 +65,27 @@ describe API::API, api: true do
end
end
+ describe 'DELETE /projects/:id/repository/tags/:tag_name' do
+ let(:tag_name) { project.repository.tag_names.sort.reverse.first }
+
+ before do
+ allow_any_instance_of(Repository).to receive(:rm_tag).and_return(true)
+ end
+
+ context 'delete tag' do
+ it 'should delete an existing tag' do
+ delete api("/projects/#{project.id}/repository/tags/#{tag_name}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['tag_name']).to eq(tag_name)
+ end
+
+ it 'should raise 404 if the tag does not exist' do
+ delete api("/projects/#{project.id}/repository/tags/foobar", user)
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
context 'annotated tag' do
it 'should create a new annotated tag' do
# Identity must be set in .gitconfig to create annotated tag.
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 314bd7ddc59..0510b77a39b 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -3,11 +3,19 @@ require 'spec_helper'
describe API::API do
include ApiHelpers
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:trigger_token) { 'secure_token' }
+ let!(:trigger_token_2) { 'secure_token_2' }
+ let!(:project) { create(:project, creator_id: user.id) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
+ let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
+ let!(:trigger2) { create(:ci_trigger, project: project, token: trigger_token_2) }
+ let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') }
+
describe 'POST /projects/:project_id/trigger' do
- let!(:trigger_token) { 'secure token' }
- let!(:project) { FactoryGirl.create(:project) }
- let!(:project2) { FactoryGirl.create(:empty_project) }
- let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
+ let!(:project2) { create(:empty_project) }
let(:options) do
{
token: trigger_token
@@ -77,4 +85,127 @@ describe API::API do
end
end
end
+
+ describe 'GET /projects/:id/triggers' do
+ context 'authenticated user with valid permissions' do
+ it 'should return list of triggers' do
+ get api("/projects/#{project.id}/triggers", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a(Array)
+ expect(json_response[0]).to have_key('token')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/triggers/:token' do
+ context 'authenticated user with valid permissions' do
+ it 'should return trigger details' do
+ get api("/projects/#{project.id}/triggers/#{trigger.token}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a(Hash)
+ end
+
+ it 'should respond with 404 Not Found if requesting non-existing trigger' do
+ get api("/projects/#{project.id}/triggers/abcdef012345", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers/#{trigger.token}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/triggers' do
+ context 'authenticated user with valid permissions' do
+ it 'should create trigger' do
+ expect do
+ post api("/projects/#{project.id}/triggers", user)
+ end.to change{project.triggers.count}.by(1)
+
+ expect(response.status).to eq(201)
+ expect(json_response).to be_a(Hash)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not create trigger' do
+ post api("/projects/#{project.id}/triggers", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not create trigger' do
+ post api("/projects/#{project.id}/triggers")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/triggers/:token' do
+ context 'authenticated user with valid permissions' do
+ it 'should delete trigger' do
+ expect do
+ delete api("/projects/#{project.id}/triggers/#{trigger.token}", user)
+ end.to change{project.triggers.count}.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should respond with 404 Not Found if requesting non-existing trigger' do
+ delete api("/projects/#{project.id}/triggers/abcdef012345", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not delete trigger' do
+ delete api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not delete trigger' do
+ delete api("/projects/#{project.id}/triggers/#{trigger.token}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 4f278551d07..679227bf881 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -8,6 +8,8 @@ describe API::API, api: true do
let(:key) { create(:key, user: user) }
let(:email) { create(:email, user: user) }
let(:omniauth_user) { create(:omniauth_user) }
+ let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') }
+ let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
describe "GET /users" do
context "when unauthenticated" do
@@ -45,6 +47,8 @@ describe API::API, api: true do
expect(json_response.first.keys).to include 'identities'
expect(json_response.first.keys).to include 'can_create_project'
expect(json_response.first.keys).to include 'two_factor_enabled'
+ expect(json_response.first.keys).to include 'last_sign_in_at'
+ expect(json_response.first.keys).to include 'confirmed_at'
end
end
end
@@ -116,6 +120,26 @@ describe API::API, api: true do
expect(response.status).to eq(201)
end
+ it 'creates non-external users by default' do
+ post api("/users", admin), attributes_for(:user)
+ expect(response.status).to eq(201)
+
+ user_id = json_response['id']
+ new_user = User.find(user_id)
+ expect(new_user).not_to eq nil
+ expect(new_user.external).to be_falsy
+ end
+
+ it 'should allow an external user to be created' do
+ post api("/users", admin), attributes_for(:user, external: true)
+ expect(response.status).to eq(201)
+
+ user_id = json_response['id']
+ new_user = User.find(user_id)
+ expect(new_user).not_to eq nil
+ expect(new_user.external).to be_truthy
+ end
+
it "should not create user with invalid email" do
post api('/users', admin),
email: 'invalid email',
@@ -258,6 +282,13 @@ describe API::API, api: true do
expect(user.reload.admin).to eq(true)
end
+ it "should update external status" do
+ put api("/users/#{user.id}", admin), { external: true }
+ expect(response.status).to eq 200
+ expect(json_response['external']).to eq(true)
+ expect(user.reload.external?).to be_truthy
+ end
+
it "should not update admin status" do
put api("/users/#{admin_user.id}", admin), { can_create_group: false }
expect(response.status).to eq(200)
@@ -783,6 +814,12 @@ describe API::API, api: true do
expect(user.reload.state).to eq('blocked')
end
+ it 'should not re-block ldap blocked users' do
+ put api("/users/#{ldap_blocked_user.id}/block", admin)
+ expect(response.status).to eq(403)
+ expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
+ end
+
it 'should not be available for non admin users' do
put api("/users/#{user.id}/block", user)
expect(response.status).to eq(403)
@@ -797,7 +834,9 @@ describe API::API, api: true do
end
describe 'PUT /user/:id/unblock' do
+ let(:blocked_user) { create(:user, state: 'blocked') }
before { admin }
+
it 'should unblock existing user' do
put api("/users/#{user.id}/unblock", admin)
expect(response.status).to eq(200)
@@ -805,12 +844,15 @@ describe API::API, api: true do
end
it 'should unblock a blocked user' do
- put api("/users/#{user.id}/block", admin)
+ put api("/users/#{blocked_user.id}/unblock", admin)
expect(response.status).to eq(200)
- expect(user.reload.state).to eq('blocked')
- put api("/users/#{user.id}/unblock", admin)
- expect(response.status).to eq(200)
- expect(user.reload.state).to eq('active')
+ expect(blocked_user.reload.state).to eq('active')
+ end
+
+ it 'should not unblock ldap blocked users' do
+ put api("/users/#{ldap_blocked_user.id}/unblock", admin)
+ expect(response.status).to eq(403)
+ expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end
it 'should not be available for non admin users' do
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
new file mode 100644
index 00000000000..b1e1053d037
--- /dev/null
+++ b/spec/requests/api/variables_spec.rb
@@ -0,0 +1,182 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:project) { create(:project, creator_id: user.id) }
+ let!(:master) { create(:project_member, :master, user: user, project: project) }
+ let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
+ let!(:variable) { create(:ci_variable, project: project) }
+
+ describe 'GET /projects/:id/variables' do
+ context 'authorized user with proper permissions' do
+ it 'should return project variables' do
+ get api("/projects/#{project.id}/variables", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a(Array)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not return project variables' do
+ get api("/projects/#{project.id}/variables", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return project variables' do
+ get api("/projects/#{project.id}/variables")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/variables/:key' do
+ context 'authorized user with proper permissions' do
+ it 'should return project variable details' do
+ get api("/projects/#{project.id}/variables/#{variable.key}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['value']).to eq(variable.value)
+ end
+
+ it 'should respond with 404 Not Found if requesting non-existing variable' do
+ get api("/projects/#{project.id}/variables/non_existing_variable", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not return project variable details' do
+ get api("/projects/#{project.id}/variables/#{variable.key}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return project variable details' do
+ get api("/projects/#{project.id}/variables/#{variable.key}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/variables' do
+ context 'authorized user with proper permissions' do
+ it 'should create variable' do
+ expect do
+ post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
+ end.to change{project.variables.count}.by(1)
+
+ expect(response.status).to eq(201)
+ expect(json_response['key']).to eq('TEST_VARIABLE_2')
+ expect(json_response['value']).to eq('VALUE_2')
+ end
+
+ it 'should not allow to duplicate variable key' do
+ expect do
+ post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2'
+ end.to change{project.variables.count}.by(0)
+
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not create variable' do
+ post api("/projects/#{project.id}/variables", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not create variable' do
+ post api("/projects/#{project.id}/variables")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/variables/:key' do
+ context 'authorized user with proper permissions' do
+ it 'should update variable data' do
+ initial_variable = project.variables.first
+ value_before = initial_variable.value
+
+ put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP'
+
+ updated_variable = project.variables.first
+
+ expect(response.status).to eq(200)
+ expect(value_before).to eq(variable.value)
+ expect(updated_variable.value).to eq('VALUE_1_UP')
+ end
+
+ it 'should responde with 404 Not Found if requesting non-existing variable' do
+ put api("/projects/#{project.id}/variables/non_existing_variable", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not update variable' do
+ put api("/projects/#{project.id}/variables/#{variable.key}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not update variable' do
+ put api("/projects/#{project.id}/variables/#{variable.key}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/variables/:key' do
+ context 'authorized user with proper permissions' do
+ it 'should delete variable' do
+ expect do
+ delete api("/projects/#{project.id}/variables/#{variable.key}", user)
+ end.to change{project.variables.count}.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should responde with 404 Not Found if requesting non-existing variable' do
+ delete api("/projects/#{project.id}/variables/non_existing_variable", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authorized user with invalid permissions' do
+ it 'should not delete variable' do
+ delete api("/projects/#{project.id}/variables/#{variable.key}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not delete variable' do
+ delete api("/projects/#{project.id}/variables/#{variable.key}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index c27e87c4acc..57d7eb927fd 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -101,31 +101,66 @@ describe Ci::API::API do
{ "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false },
])
end
+
+ it "returns dependent builds" do
+ commit = FactoryGirl.create(:ci_commit, project: project)
+ commit.create_builds('master', false, nil, nil)
+ commit.builds.where(stage: 'test').each(&:success)
+
+ post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
+
+ expect(response.status).to eq(201)
+ expect(json_response["depends_on_builds"].count).to eq(2)
+ expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec")
+ end
+
+ %w(name version revision platform architecture).each do |param|
+ context "updates runner #{param}" do
+ let(:value) { "#{param}_value" }
+
+ subject { runner.read_attribute(param.to_sym) }
+
+ it do
+ post ci_api("/builds/register"), token: runner.token, info: { param => value }
+ expect(response.status).to eq(404)
+ runner.reload
+ is_expected.to eq(value)
+ end
+ end
+ end
end
describe "PUT /builds/:id" do
- let(:commit) { FactoryGirl.create(:ci_commit, project: project)}
- let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
+ let(:commit) {create(:ci_commit, project: project)}
+ let(:build) { create(:ci_build, :trace, commit: commit, runner_id: runner.id) }
- it "should update a running build" do
+ before do
build.run!
put ci_api("/builds/#{build.id}"), token: runner.token
+ end
+
+ it "should update a running build" do
expect(response.status).to eq(200)
end
- it 'Should not override trace information when no trace is given' do
- build.run!
- build.update!(trace: 'hello_world')
- put ci_api("/builds/#{build.id}"), token: runner.token
- expect(build.reload.trace).to eq 'hello_world'
+ it 'should not override trace information when no trace is given' do
+ expect(build.reload.trace).to eq 'BUILD TRACE'
+ end
+
+ context 'build has been erased' do
+ let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
+
+ it 'should respond with forbidden' do
+ expect(response.status).to eq 403
+ end
end
end
context "Artifacts" do
let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
- let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
- let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
+ let(:commit) { create(:ci_commit, project: project) }
+ let(:build) { create(:ci_build, commit: commit, runner_id: runner.id) }
let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
@@ -133,12 +168,10 @@ describe Ci::API::API do
let(:headers) { { "GitLab-Workhorse" => "1.0" } }
let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
+ before { build.run! }
+
describe "POST /builds/:id/artifacts/authorize" do
context "should authorize posting artifact to running build" do
- before do
- build.run!
- end
-
it "using token as parameter" do
post authorize_url, { token: build.token }, headers
expect(response.status).to eq(200)
@@ -153,10 +186,6 @@ describe Ci::API::API do
end
context "should fail to post too large artifact" do
- before do
- build.run!
- end
-
it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0)
post authorize_url, { token: build.token, filesize: 100 }, headers
@@ -170,26 +199,32 @@ describe Ci::API::API do
end
end
- context "should get denied" do
- it do
- post authorize_url, { token: 'invalid', filesize: 100 }
+ context 'authorization token is invalid' do
+ before { post authorize_url, { token: 'invalid', filesize: 100 } }
+
+ it 'should respond with forbidden' do
expect(response.status).to eq(403)
end
end
end
describe "POST /builds/:id/artifacts" do
- context "Disable sanitizer" do
+ context "disable sanitizer" do
before do
# by configuring this path we allow to pass temp file from any path
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
end
- context "should post artifact to running build" do
- before do
- build.run!
+ context 'build has been erased' do
+ let(:build) { create(:ci_build, erased_at: Time.now) }
+ before { upload_artifacts(file_upload, headers_with_token) }
+
+ it 'should respond with forbidden' do
+ expect(response.status).to eq 403
end
+ end
+ context "should post artifact to running build" do
it "uses regual file post" do
upload_artifacts(file_upload, headers_with_token, false)
expect(response.status).to eq(201)
@@ -210,55 +245,83 @@ describe Ci::API::API do
end
end
- context "should fail to post too large artifact" do
+ context 'should post artifacts file and metadata file' do
+ let!(:artifacts) { file_upload }
+ let!(:metadata) { file_upload2 }
+
+ let(:stored_artifacts_file) { build.reload.artifacts_file.file }
+ let(:stored_metadata_file) { build.reload.artifacts_metadata.file }
+
before do
- build.run!
+ post(post_url, post_data, headers_with_token)
+ end
+
+ context 'post data accelerated by workhorse is correct' do
+ let(:post_data) do
+ { 'file.path' => artifacts.path,
+ 'file.name' => artifacts.original_filename,
+ 'metadata.path' => metadata.path,
+ 'metadata.name' => metadata.original_filename }
+ end
+
+ it 'stores artifacts and artifacts metadata' do
+ expect(response.status).to eq(201)
+ expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
+ expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
+ end
+ end
+
+ context 'no artifacts file in post data' do
+ let(:post_data) do
+ { 'metadata' => metadata }
+ end
+
+ it 'is expected to respond with bad request' do
+ expect(response.status).to eq(400)
+ end
+
+ it 'does not store metadata' do
+ expect(stored_metadata_file).to be_nil
+ end
end
+ end
- it do
+ context "artifacts file is too large" do
+ it "should fail to post too large artifact" do
stub_application_setting(max_artifacts_size: 0)
upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(413)
end
end
- context "should fail to post artifacts without file" do
- before do
- build.run!
- end
-
- it do
+ context "artifacts post request does not contain file" do
+ it "should fail to post artifacts without file" do
post post_url, {}, headers_with_token
expect(response.status).to eq(400)
end
end
- context "should fail to post artifacts without GitLab-Workhorse" do
- before do
- build.run!
- end
-
- it do
+ context 'GitLab Workhorse is not configured' do
+ it "should fail to post artifacts without GitLab-Workhorse" do
post post_url, { token: build.token }, {}
expect(response.status).to eq(403)
end
end
end
- context "should fail to post artifacts for outside of tmp path" do
+ context "artifacts are being stored outside of tmp path" do
before do
# by configuring this path we allow to pass file from @tmpdir only
# but all temporary files are stored in system tmp directory
@tmpdir = Dir.mktmpdir
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir)
- build.run!
end
after do
FileUtils.remove_entry @tmpdir
end
- it do
+ it "should fail to post artifacts for outside of tmp path" do
upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(400)
end
@@ -276,33 +339,37 @@ describe Ci::API::API do
end
end
- describe "DELETE /builds/:id/artifacts" do
- before do
- build.run!
- post delete_url, token: build.token, file: file_upload
- end
+ describe 'DELETE /builds/:id/artifacts' do
+ let(:build) { create(:ci_build, :artifacts) }
+ before { delete delete_url, token: build.token }
- it "should delete artifact build" do
- build.success
- delete delete_url, token: build.token
+ it 'should remove build artifacts' do
expect(response.status).to eq(200)
+ expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_metadata.exists?).to be_falsy
end
end
- describe "GET /builds/:id/artifacts" do
- before do
- build.run!
- end
+ describe 'GET /builds/:id/artifacts' do
+ before { get get_url, token: build.token }
- it "should download artifact" do
- build.update_attributes(artifacts_file: file_upload)
- get get_url, token: build.token
- expect(response.status).to eq(200)
+ context 'build has artifacts' do
+ let(:build) { create(:ci_build, :artifacts) }
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding'=>'binary',
+ 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
+ end
+
+ it 'should download artifact' do
+ expect(response.status).to eq(200)
+ expect(response.headers).to include download_headers
+ end
end
- it "should fail to download if no artifact uploaded" do
- get get_url, token: build.token
- expect(response.status).to eq(404)
+ context 'build does not has artifacts' do
+ it 'should respond with not found' do
+ expect(response.status).to eq(404)
+ end
end
end
end
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index 5942aa7a1b5..db8189ffb79 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -51,6 +51,20 @@ describe Ci::API::API do
expect(response.status).to eq(400)
end
+
+ %w(name version revision platform architecture).each do |param|
+ context "creates runner with #{param} saved" do
+ let(:value) { "#{param}_value" }
+
+ subject { Ci::Runner.first.read_attribute(param.to_sym) }
+
+ it do
+ post ci_api("/runners/register"), token: registration_token, info: { param => value }
+ expect(response.status).to eq(201)
+ is_expected.to eq(value)
+ end
+ end
+ end
end
describe "DELETE /runners/delete" do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 82f62a8709c..538f44e4f3f 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -80,6 +80,7 @@ describe ProjectsController, 'routing' do
it 'to #show' do
expect(get('/gitlab/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq.keys')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq.keys')
end
it 'to #update' do
@@ -320,12 +321,12 @@ describe Projects::HooksController, 'routing' do
end
end
-# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /[[:alnum:]]{6,40}/, project_id: /[^\/]+/}
+# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/}
describe Projects::CommitController, 'routing' do
it 'to #show' do
- expect(get('/gitlab/gitlabhq/commit/4246fb')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb')
- expect(get('/gitlab/gitlabhq/commit/4246fb.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'diff')
- expect(get('/gitlab/gitlabhq/commit/4246fb.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'patch')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'diff')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch')
expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
end
end
@@ -434,6 +435,18 @@ describe Projects::TreeController, 'routing' do
end
end
+# project_find_file GET /:namespace_id/:project_id/find_file/*id(.:format) projects/find_file#show {:id=>/.+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?<!\.atom)/, :format=>/html/}
+# project_files GET /:namespace_id/:project_id/files/*id(.:format) projects/find_file#list {:id=>/(?:[^.]|\.(?!json$))+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?<!\.atom)/, :format=>/json/}
+describe Projects::FindFileController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
+ end
+
+ it 'to #list' do
+ expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
+ end
+end
+
describe Projects::BlobController, 'routing' do
it 'to #edit' do
expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to(
@@ -483,11 +496,11 @@ end
describe Projects::ForksController, 'routing' do
it 'to #new' do
- expect(get('/gitlab/gitlabhq/fork/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
it 'to #create' do
- expect(post('/gitlab/gitlabhq/fork')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index dfa18f69e05..1527eddfa48 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -137,7 +137,6 @@ end
# keys GET /keys(.:format) keys#index
# POST /keys(.:format) keys#create
-# new_key GET /keys/new(.:format) keys#new
# edit_key GET /keys/:id/edit(.:format) keys#edit
# key GET /keys/:id(.:format) keys#show
# PUT /keys/:id(.:format) keys#update
@@ -151,10 +150,6 @@ describe Profiles::KeysController, "routing" do
expect(post("/profile/keys")).to route_to('profiles/keys#create')
end
- it "to #new" do
- expect(get("/profile/keys/new")).to route_to('profiles/keys#new')
- end
-
it "to #edit" do
expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1')
end
diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb
deleted file mode 100644
index bd871605c66..00000000000
--- a/spec/services/archive_repository_service_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'spec_helper'
-
-describe ArchiveRepositoryService, services: true do
- let(:project) { create(:project) }
- subject { ArchiveRepositoryService.new(project, "master", "zip") }
-
- describe "#execute" do
- it "cleans old archives" do
- expect(RepositoryArchiveCacheWorker).to receive(:perform_async)
-
- subject.execute(timeout: 0.0)
- end
-
- context "when the repository doesn't have an archive file path" do
- before do
- allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
- end
-
- it "raises an error" do
- expect { subject.execute(timeout: 0.0) }.to raise_error(RuntimeError)
- end
- end
-
- end
-end
diff --git a/spec/services/ci/create_builds_service_spec.rb b/spec/services/ci/create_builds_service_spec.rb
new file mode 100644
index 00000000000..1fca3628686
--- /dev/null
+++ b/spec/services/ci/create_builds_service_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Ci::CreateBuildsService, services: true do
+ let(:commit) { create(:ci_commit) }
+ let(:user) { create(:user) }
+
+ describe '#execute' do
+ # Using stubbed .gitlab-ci.yml created in commit factory
+ #
+
+ subject do
+ described_class.new.execute(commit, 'test', 'master', nil, user, nil, status)
+ end
+
+ context 'next builds available' do
+ let(:status) { 'success' }
+
+ it { is_expected.to be_an_instance_of Array }
+ it { is_expected.to all(be_an_instance_of Ci::Build) }
+ end
+
+ context 'builds skipped' do
+ let(:status) { 'skipped' }
+
+ it { is_expected.to be_empty }
+ end
+ end
+end
diff --git a/spec/services/delete_tag_service_spec.rb b/spec/services/delete_tag_service_spec.rb
new file mode 100644
index 00000000000..5b7ba521812
--- /dev/null
+++ b/spec/services/delete_tag_service_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe DeleteTagService, services: true do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:user) { create(:user) }
+ let(:service) { described_class.new(project, user) }
+
+ let(:tag) { double(:tag, name: '8.5', target: 'abc123') }
+
+ describe '#execute' do
+ before do
+ allow(repository).to receive(:find_tag).and_return(tag)
+ end
+
+ it 'removes the tag' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:rm_tag).
+ and_return(true)
+
+ expect(repository).to receive(:before_remove_tag)
+ expect(service).to receive(:success)
+
+ service.execute('8.5')
+ end
+ end
+end
diff --git a/spec/services/delete_user_service_spec.rb b/spec/services/delete_user_service_spec.rb
new file mode 100644
index 00000000000..a65938fa03b
--- /dev/null
+++ b/spec/services/delete_user_service_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe DeleteUserService, services: true do
+ describe "Deletes a user and all their personal projects" do
+ let!(:user) { create(:user) }
+ let!(:current_user) { create(:user) }
+ let!(:namespace) { create(:namespace, owner: user) }
+ let!(:project) { create(:project, namespace: namespace) }
+
+ context 'no options are given' do
+ it 'deletes the user' do
+ DeleteUserService.new(current_user).execute(user)
+
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'will delete the project in the near future' do
+ expect_any_instance_of(Projects::DestroyService).to receive(:pending_delete!).once
+
+ DeleteUserService.new(current_user).execute(user)
+ end
+ end
+
+ context "solo owned groups present" do
+ let(:solo_owned) { create(:group) }
+ let(:member) { create(:group_member) }
+ let(:user) { member.user }
+
+ before do
+ solo_owned.group_members = [member]
+ DeleteUserService.new(current_user).execute(user)
+ end
+
+ it 'does not delete the user' do
+ expect(User.find(user.id)).to eq user
+ end
+ end
+
+ context "deletions with solo owned groups" do
+ let(:solo_owned) { create(:group) }
+ let(:member) { create(:group_member) }
+ let(:user) { member.user }
+
+ before do
+ solo_owned.group_members = [member]
+ DeleteUserService.new(current_user).execute(user, delete_solo_owned_groups: true)
+ end
+
+ it 'deletes solo owned groups' do
+ expect { Project.find(solo_owned.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'deletes the user' do
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index c1080ef190a..8490a729e51 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -5,7 +5,6 @@ describe GitPushService, services: true do
let(:user) { create :user }
let(:project) { create :project }
- let(:service) { GitPushService.new }
before do
@blankrev = Gitlab::Git::BLANK_SHA
@@ -15,34 +14,70 @@ describe GitPushService, services: true do
end
describe 'Push branches' do
+
+ let(:oldrev) { @oldrev }
+ let(:newrev) { @newrev }
+
+ subject do
+ execute_service(project, user, oldrev, newrev, @ref )
+ end
+
context 'new branch' do
- subject do
- service.execute(project, user, @blankrev, @newrev, @ref)
- end
+
+ let(:oldrev) { @blankrev }
it { is_expected.to be_truthy }
+
+ it 'flushes general cached data' do
+ expect(project.repository).to receive(:expire_cache).
+ with('master', newrev)
+
+ subject
+ end
+
+ it 'flushes the visible content cache' do
+ expect(project.repository).to receive(:expire_has_visible_content_cache)
+
+ subject
+ end
end
context 'existing branch' do
- subject do
- service.execute(project, user, @oldrev, @newrev, @ref)
- end
it { is_expected.to be_truthy }
+
+ it 'flushes general cached data' do
+ expect(project.repository).to receive(:expire_cache).
+ with('master', newrev)
+
+ subject
+ end
end
context 'rm branch' do
- subject do
- service.execute(project, user, @oldrev, @blankrev, @ref)
- end
+
+ let(:newrev) { @blankrev }
it { is_expected.to be_truthy }
+
+ it 'flushes the visible content cache' do
+ expect(project.repository).to receive(:expire_has_visible_content_cache)
+
+ subject
+ end
+
+ it 'flushes general cached data' do
+ expect(project.repository).to receive(:expire_cache).
+ with('master', newrev)
+
+ subject
+ end
end
end
describe "Git Push Data" do
before do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ service = execute_service(project, user, @oldrev, @newrev, @ref )
@push_data = service.push_data
@commit = project.commit(@newrev)
end
@@ -104,31 +139,49 @@ describe GitPushService, services: true do
describe "Push Event" do
before do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ service = execute_service(project, user, @oldrev, @newrev, @ref )
@event = Event.last
+ @push_data = service.push_data
end
it { expect(@event).not_to be_nil }
it { expect(@event.project).to eq(project) }
it { expect(@event.action).to eq(Event::PUSHED) }
- it { expect(@event.data).to eq(service.push_data) }
+ it { expect(@event.data).to eq(@push_data) }
context "Updates merge requests" do
it "when pushing a new branch for the first time" do
expect(project).to receive(:update_merge_requests).
with(@blankrev, 'newrev', 'refs/heads/master', user)
- service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+ end
+ end
+ end
+
+ describe "Updates main language" do
+
+ context "before push" do
+ it { expect(project.main_language).to eq(nil) }
+ end
+
+ context "after push" do
+ before do
+ @service = execute_service(project, user, @oldrev, @newrev, @ref)
end
+
+ it { expect(@service.update_main_language).to eq(true) }
+ it { expect(project.main_language).to eq("Ruby") }
end
end
- describe "Web Hooks" do
- context "execute web hooks" do
+
+ describe "Webhooks" do
+ context "execute webhooks" do
it "when pushing a branch for the first time" do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false })
- service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
it "when pushing a branch for the first time with default branch protection disabled" do
@@ -137,7 +190,7 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
expect(project.protected_branches).not_to receive(:create)
- service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
@@ -146,12 +199,12 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true })
- service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
it "when pushing new commits to existing branch" do
expect(project).to receive(:execute_hooks)
- service.execute(project, user, 'oldrev', 'newrev', 'refs/heads/master')
+ execute_service(project, user, 'oldrev', 'newrev', 'refs/heads/master' )
end
end
end
@@ -162,19 +215,23 @@ describe GitPushService, services: true do
let(:commit) { project.commit }
before do
+ project.team << [commit_author, :developer]
+ project.team << [user, :developer]
+
allow(commit).to receive_messages(
safe_message: "this commit \n mentions #{issue.to_reference}",
references: [issue],
author_name: commit_author.name,
author_email: commit_author.email
)
+
allow(project.repository).to receive(:commits_between).and_return([commit])
end
it "creates a note if a pushed commit mentions an issue" do
expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "only creates a cross-reference note if one doesn't already exist" do
@@ -182,7 +239,7 @@ describe GitPushService, services: true do
expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "defaults to the pushing user if the commit's author is not known" do
@@ -192,7 +249,7 @@ describe GitPushService, services: true do
)
expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "finds references in the first push to a non-default branch" do
@@ -201,7 +258,7 @@ describe GitPushService, services: true do
expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
- service.execute(project, user, @blankrev, @newrev, 'refs/heads/other')
+ execute_service(project, user, @blankrev, @newrev, 'refs/heads/other' )
end
end
@@ -221,22 +278,24 @@ describe GitPushService, services: true do
allow(project.repository).to receive(:commits_between).
and_return([closing_commit])
+
+ project.team << [commit_author, :master]
end
context "to default branches" do
it "closes issues" do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
expect(Issue.find(issue.id)).to be_closed
end
it "adds a note indicating that the issue is now closed" do
expect(SystemNoteService).to receive(:change_status).with(issue, project, commit_author, "closed", closing_commit)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
end
it "doesn't create additional cross-reference notes" do
expect(SystemNoteService).not_to receive(:cross_reference)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
end
it "doesn't close issues when external issue tracker is in use" do
@@ -244,7 +303,7 @@ describe GitPushService, services: true do
# The push still shouldn't create cross-reference notes.
expect do
- service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf')
+ execute_service(project, commit_author, @oldrev, @newrev, 'refs/heads/hurf' )
end.not_to change { Note.where(project_id: project.id, system: true).count }
end
end
@@ -257,16 +316,15 @@ describe GitPushService, services: true do
it "creates cross-reference notes" do
expect(SystemNoteService).to receive(:cross_reference).with(issue, closing_commit, commit_author)
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
end
it "doesn't close issues" do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
expect(Issue.find(issue.id)).to be_opened
end
end
- # EE-only tests
context "for jira issue tracker" do
include JiraServiceHelper
@@ -298,7 +356,7 @@ describe GitPushService, services: true do
let(:message) { "this is some work.\n\nrelated to JIRA-1" }
it "should initiate one api call to jira server to mention the issue" do
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, user, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
body: /mentioned this issue in/
@@ -316,7 +374,7 @@ describe GitPushService, services: true do
}
}.to_json
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_transition_url).with(
body: transition_body
).once
@@ -327,7 +385,7 @@ describe GitPushService, services: true do
body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
}.to_json
- service.execute(project, user, @oldrev, @newrev, @ref)
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
body: comment_body
).once
@@ -346,7 +404,52 @@ describe GitPushService, services: true do
end
it 'push to first branch updates HEAD' do
- service.execute(project, user, @blankrev, @newrev, new_ref)
+ execute_service(project, user, @blankrev, @newrev, new_ref )
+ end
+ end
+
+ describe "housekeeping" do
+ let(:housekeeping) { Projects::HousekeepingService.new(project) }
+
+ before do
+ allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping)
end
+
+ it 'does not perform housekeeping when not needed' do
+ expect(housekeeping).not_to receive(:execute)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+
+ context 'when housekeeping is needed' do
+ before do
+ allow(housekeeping).to receive(:needed?).and_return(true)
+ end
+
+ it 'performs housekeeping' do
+ expect(housekeeping).to receive(:execute)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+
+ it 'does not raise an exception' do
+ allow(housekeeping).to receive(:try_obtain_lease).and_return(false)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+ end
+
+
+ it 'increments the push counter' do
+ expect(housekeeping).to receive(:increment!)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+ end
+
+ def execute_service(project, user, oldrev, newrev, ref)
+ service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref )
+ service.execute
+ service
end
end
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index b982274c529..cc780587e74 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -78,8 +78,8 @@ describe GitTagPushService, services: true do
end
end
- describe "Web Hooks" do
- context "execute web hooks" do
+ describe "Webhooks" do
+ context "execute webhooks" do
it "when pushing tags" do
expect(project).to receive(:execute_hooks)
service.execute(project, user, 'oldrev', 'newrev', 'refs/tags/v1.0.0')
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 3a8daf28f5e..62b25709a5d 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -5,6 +5,7 @@ describe Issues::CloseService, services: true do
let(:user2) { create(:user) }
let(:issue) { create(:issue, assignee: user2) }
let(:project) { issue.project }
+ let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
before do
project.team << [user, :master]
@@ -32,6 +33,10 @@ describe Issues::CloseService, services: true do
note = @issue.notes.last
expect(note.note).to include "Status changed to closed"
end
+
+ it 'marks todos as done' do
+ expect(todo.reload).to be_done
+ end
end
context "external issue tracker" do
@@ -42,6 +47,7 @@ describe Issues::CloseService, services: true do
it { expect(@issue).to be_valid }
it { expect(@issue).to be_opened }
+ it { expect(todo.reload).to be_pending }
end
end
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 2148d091a57..5e7915db7e1 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -3,14 +3,18 @@ require 'spec_helper'
describe Issues::CreateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
+ let(:assignee) { create(:user) }
describe :execute do
- context "valid params" do
+ context 'valid params' do
before do
project.team << [user, :master]
+ project.team << [assignee, :master]
+
opts = {
title: 'Awesome issue',
- description: 'please fix'
+ description: 'please fix',
+ assignee: assignee
}
@issue = Issues::CreateService.new(project, user, opts).execute
@@ -18,6 +22,21 @@ describe Issues::CreateService, services: true do
it { expect(@issue).to be_valid }
it { expect(@issue.title).to eq('Awesome issue') }
+ it { expect(@issue.assignee).to eq assignee }
+
+ it 'creates a pending todo for new assignee' do
+ attributes = {
+ project: project,
+ author: user,
+ user: assignee,
+ target_id: @issue.id,
+ target_type: @issue.class.name,
+ action: Todo::ASSIGNED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
end
end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 87da0e9618b..4ffe753fef5 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -6,6 +6,7 @@ describe Issues::UpdateService, services: true do
let(:user3) { create(:user) }
let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) }
let(:label) { create(:label) }
+ let(:label2) { create(:label) }
let(:project) { issue.project }
before do
@@ -48,7 +49,7 @@ describe Issues::UpdateService, services: true do
it { expect(@issue.assignee).to eq(user2) }
it { expect(@issue).to be_closed }
it { expect(@issue.labels.count).to eq(1) }
- it { expect(@issue.labels.first.title).to eq('Bug') }
+ it { expect(@issue.labels.first.title).to eq(label.name) }
it 'should send email to user2 about assign of new issue and email to user3 about issue unassignment' do
deliveries = ActionMailer::Base.deliveries
@@ -80,6 +81,116 @@ describe Issues::UpdateService, services: true do
end
end
+ context 'todos' do
+ let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
+
+ context 'when the title change' do
+ before do
+ update_issue({ title: 'New title' })
+ end
+
+ it 'marks pending todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+ end
+
+ context 'when the description change' do
+ before do
+ update_issue({ description: 'Also please fix' })
+ end
+
+ it 'marks todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+ end
+
+ context 'when is reassigned' do
+ before do
+ update_issue({ assignee: user2 })
+ end
+
+ it 'marks previous assignee todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+
+ it 'creates a todo for new assignee' do
+ attributes = {
+ project: project,
+ author: user,
+ user: user2,
+ target_id: issue.id,
+ target_type: issue.class.name,
+ action: Todo::ASSIGNED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+ end
+
+ context 'when the milestone change' do
+ before do
+ update_issue({ milestone: create(:milestone) })
+ end
+
+ it 'marks todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+ end
+
+ context 'when the labels change' do
+ before do
+ update_issue({ label_ids: [label.id] })
+ end
+
+ it 'marks todos as done' do
+ expect(todo.reload.done?).to eq true
+ end
+ end
+ end
+
+ context 'when the issue is relabeled' do
+ let!(:non_subscriber) { create(:user) }
+ let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } }
+
+ it 'sends notifications for subscribers of newly added labels' do
+ opts = { label_ids: [label.id] }
+
+ perform_enqueued_jobs do
+ @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ end
+
+ should_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+
+ context 'when issue has the `label` label' do
+ before { issue.labels << label }
+
+ it 'does not send notifications for existing labels' do
+ opts = { label_ids: [label.id, label2.id] }
+
+ perform_enqueued_jobs do
+ @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ end
+
+ should_not_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+
+ it 'does not send notifications for removed labels' do
+ opts = { label_ids: [label2.id] }
+
+ perform_enqueued_jobs do
+ @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ end
+
+ should_not_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+ end
+ end
+
context 'when Issue has tasks' do
before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
@@ -144,6 +255,5 @@ describe Issues::UpdateService, services: true do
end
end
end
-
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 50d0c288790..8443a00e70c 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -5,6 +5,7 @@ describe MergeRequests::CloseService, services: true do
let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, assignee: user2) }
let(:project) { merge_request.project }
+ let!(:todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
before do
project.team << [user, :master]
@@ -41,6 +42,10 @@ describe MergeRequests::CloseService, services: true do
note = @merge_request.notes.last
expect(note.note).to include 'Status changed to closed'
end
+
+ it 'marks todos as done' do
+ expect(todo.reload).to be_done
+ end
end
end
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index be8f1676eeb..120f4d6a669 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe MergeRequests::CreateService, services: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
+ let(:assignee) { create(:user) }
describe :execute do
context 'valid params' do
@@ -14,10 +15,12 @@ describe MergeRequests::CreateService, services: true do
target_branch: 'master'
}
end
+
let(:service) { MergeRequests::CreateService.new(project, user, opts) }
before do
project.team << [user, :master]
+ project.team << [assignee, :developer]
allow(service).to receive(:execute_hooks)
@merge_request = service.execute
@@ -25,10 +28,49 @@ describe MergeRequests::CreateService, services: true do
it { expect(@merge_request).to be_valid }
it { expect(@merge_request.title).to eq('Awesome merge_request') }
+ it { expect(@merge_request.assignee).to be_nil }
it 'should execute hooks with default action' do
expect(service).to have_received(:execute_hooks).with(@merge_request)
end
+
+ it 'does not creates todos' do
+ attributes = {
+ project: project,
+ target_id: @merge_request.id,
+ target_type: @merge_request.class.name
+ }
+
+ expect(Todo.where(attributes).count).to be_zero
+ end
+
+ context 'when merge request is assigned to someone' do
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ description: 'please fix',
+ source_branch: 'feature',
+ target_branch: 'master',
+ assignee: assignee
+ }
+ end
+
+ it { expect(@merge_request.assignee).to eq assignee }
+
+ it 'creates a todo for new assignee' do
+ attributes = {
+ project: project,
+ author: user,
+ user: assignee,
+ target_id: @merge_request.id,
+ target_type: @merge_request.class.name,
+ action: Todo::ASSIGNED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+ end
end
end
end
diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
index 449cecaa789..52a302e0e1a 100644
--- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -6,7 +6,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
let(:mr_merge_if_green_enabled) do
create(:merge_request, merge_when_build_succeeds: true, merge_user: user,
- source_branch: "source_branch", target_branch: project.default_branch,
+ source_branch: "master", target_branch: 'feature',
source_project: project, target_project: project, state: "opened")
end
@@ -54,14 +54,84 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
end
describe "#trigger" do
- let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
+ context 'build with ref' do
+ let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
- it "merges all merge requests with merge when build succeeds enabled" do
- allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
- allow(ci_commit).to receive(:success?).and_return(true)
+ it "merges all merge requests with merge when build succeeds enabled" do
+ allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+ allow(ci_commit).to receive(:success?).and_return(true)
+
+ expect(MergeWorker).to receive(:perform_async)
+ service.trigger(build)
+ end
+ end
+
+ context 'triggered by an old build' do
+ let(:old_build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
+ let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") }
+
+ it "merges all merge requests with merge when build succeeds enabled" do
+ allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+ allow(ci_commit).to receive(:success?).and_return(true)
+ allow(old_build).to receive(:sha).and_return('1234abcdef')
+
+ expect(MergeWorker).to_not receive(:perform_async)
+ service.trigger(old_build)
+ end
+ end
+
+ context 'commit status without ref' do
+ let(:commit_status) { create(:generic_commit_status, status: 'success') }
+
+ before { mr_merge_if_green_enabled }
+
+ it "doesn't merge a requests for status on other branch" do
+ allow(project.repository).to receive(:branch_names_contains).with(commit_status.sha).and_return([])
+
+ expect(MergeWorker).to_not receive(:perform_async)
+ service.trigger(commit_status)
+ end
+
+ it 'discovers branches and merges all merge requests when status is success' do
+ allow(project.repository).to receive(:branch_names_contains).
+ with(commit_status.sha).and_return([mr_merge_if_green_enabled.source_branch])
+ allow(ci_commit).to receive(:success?).and_return(true)
+ allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_return(ci_commit)
+ allow(ci_commit).to receive(:success?).and_return(true)
+
+ expect(MergeWorker).to receive(:perform_async)
+ service.trigger(commit_status)
+ end
+ end
- expect(MergeWorker).to receive(:perform_async)
- service.trigger(build)
+ context 'properly handles multiple stages' do
+ let(:ref) { mr_merge_if_green_enabled.source_branch }
+ let(:build) { create(:ci_build, commit: ci_commit, ref: ref, name: 'build', stage: 'build') }
+ let(:test) { create(:ci_build, commit: ci_commit, ref: ref, name: 'test', stage: 'test') }
+
+ before do
+ # This behavior of MergeRequest: we instantiate a new object
+ allow_any_instance_of(MergeRequest).to receive(:ci_commit).and_wrap_original do
+ Ci::Commit.find(ci_commit.id)
+ end
+
+ # We create test after the build
+ allow(ci_commit).to receive(:create_next_builds).and_wrap_original do
+ test
+ end
+ end
+
+ it "doesn't merge if some stages failed" do
+ expect(MergeWorker).to_not receive(:perform_async)
+ build.success
+ test.drop
+ end
+
+ it 'merge when all stages succeeded' do
+ expect(MergeWorker).to receive(:perform_async)
+ build.success
+ test.success
+ end
end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 450250ba032..fea8182bd30 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -79,7 +79,7 @@ describe MergeRequests::RefreshService, services: true do
it { expect(@merge_request.notes.last.note).to include('changed to merged') }
it { expect(@merge_request).to be_merged }
- it { expect(@merge_request.diffs.length).to be > 0 }
+ it { expect(@merge_request.diffs.size).to be > 0 }
it { expect(@fork_merge_request).to be_merged }
it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') }
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 2e9e6e0870d..cb8cff2fa8c 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -7,6 +7,7 @@ describe MergeRequests::UpdateService, services: true do
let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) }
let(:project) { merge_request.project }
let(:label) { create(:label) }
+ let(:label2) { create(:label) }
before do
project.team << [user, :master]
@@ -53,7 +54,7 @@ describe MergeRequests::UpdateService, services: true do
it { expect(@merge_request.assignee).to eq(user2) }
it { expect(@merge_request).to be_closed }
it { expect(@merge_request.labels.count).to eq(1) }
- it { expect(@merge_request.labels.first.title).to eq('Bug') }
+ it { expect(@merge_request.labels.first.title).to eq(label.name) }
it { expect(@merge_request.target_branch).to eq('target') }
it 'should execute hooks with update action' do
@@ -98,6 +99,126 @@ describe MergeRequests::UpdateService, services: true do
end
end
+ context 'todos' do
+ let!(:pending_todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
+
+ context 'when the title change' do
+ before do
+ update_merge_request({ title: 'New title' })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+
+ context 'when the description change' do
+ before do
+ update_merge_request({ description: 'Also please fix' })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+
+ context 'when is reassigned' do
+ before do
+ update_merge_request({ assignee: user2 })
+ end
+
+ it 'marks previous assignee pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+
+ it 'creates a pending todo for new assignee' do
+ attributes = {
+ project: project,
+ author: user,
+ user: user2,
+ target_id: merge_request.id,
+ target_type: merge_request.class.name,
+ action: Todo::ASSIGNED,
+ state: :pending
+ }
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+ end
+
+ context 'when the milestone change' do
+ before do
+ update_merge_request({ milestone: create(:milestone) })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+
+ context 'when the labels change' do
+ before do
+ update_merge_request({ label_ids: [label.id] })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+
+ context 'when the target branch change' do
+ before do
+ update_merge_request({ target_branch: 'target' })
+ end
+
+ it 'marks pending todos as done' do
+ expect(pending_todo.reload).to be_done
+ end
+ end
+ end
+
+ context 'when the issue is relabeled' do
+ let!(:non_subscriber) { create(:user) }
+ let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } }
+
+ it 'sends notifications for subscribers of newly added labels' do
+ opts = { label_ids: [label.id] }
+
+ perform_enqueued_jobs do
+ @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ end
+
+ should_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+
+ context 'when issue has the `label` label' do
+ before { merge_request.labels << label }
+
+ it 'does not send notifications for existing labels' do
+ opts = { label_ids: [label.id, label2.id] }
+
+ perform_enqueued_jobs do
+ @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ end
+
+ should_not_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+
+ it 'does not send notifications for removed labels' do
+ opts = { label_ids: [label2.id] }
+
+ perform_enqueued_jobs do
+ @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ end
+
+ should_not_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+ end
+ end
+
context 'when MergeRequest has tasks' do
before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
@@ -130,6 +251,5 @@ describe MergeRequests::UpdateService, services: true do
end
end
end
-
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index a797a2fe4aa..ff23f13e1cb 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -14,9 +14,7 @@ describe Notes::CreateService, services: true do
noteable_type: 'Issue',
noteable_id: issue.id
}
-
- expect(project).to receive(:execute_hooks)
- expect(project).to receive(:execute_services)
+
@note = Notes::CreateService.new(project, user, opts).execute
end
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
new file mode 100644
index 00000000000..d4c50f824c1
--- /dev/null
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Notes::PostProcessService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:user) { create(:user) }
+
+ describe :execute do
+ before do
+ project.team << [user, :master]
+ note_opts = {
+ note: 'Awesome comment',
+ noteable_type: 'Issue',
+ noteable_id: issue.id
+ }
+
+ @note = Notes::CreateService.new(project, user, note_opts).execute
+ end
+
+ it do
+ expect(project).to receive(:execute_hooks)
+ expect(project).to receive(:execute_services)
+
+ Notes::PostProcessService.new(@note).execute
+ end
+ end
+end
diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb
new file mode 100644
index 00000000000..dde4bde7dc2
--- /dev/null
+++ b/spec/services/notes/update_service_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Notes::UpdateService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+ let(:note) { create(:note, project: project, noteable: issue, author: user, note: 'Old note') }
+
+ before do
+ project.team << [user, :master]
+ project.team << [user2, :developer]
+ end
+
+ describe '#execute' do
+ def update_note(opts)
+ @note = Notes::UpdateService.new(project, user, opts).execute(note)
+ @note.reload
+ end
+
+ context 'todos' do
+ let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
+
+ context 'when the note change' do
+ before do
+ update_note({ note: 'New note' })
+ end
+
+ it 'marks todos as done' do
+ expect(todo.reload).to be_done
+ end
+ end
+
+ context 'when the note does not change' do
+ before do
+ update_note({ note: 'Old note' })
+ end
+
+ it 'keep todos' do
+ expect(todo.reload).to be_pending
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index c103752198d..b5407397c1d 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -52,6 +52,9 @@ describe NotificationService, services: true do
it do
add_users_with_subscription(note.project, issue)
+ # Ensure create SentNotification by noteable = issue 6 times, not noteable = note
+ expect(SentNotification).to receive(:record).with(issue, any_args).exactly(7).times
+
ActionMailer::Base.deliveries.clear
notification.new_note(note)
@@ -61,6 +64,7 @@ describe NotificationService, services: true do
should_email(note.noteable.assignee)
should_email(@u_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_email(@subscribed_participant)
should_not_email(note.author)
should_not_email(@u_participating)
@@ -220,10 +224,19 @@ describe NotificationService, services: true do
should_not_email(issue.assignee)
end
+
+ it "emails subscribers of the issue's labels" do
+ subscriber = create(:user)
+ label = create(:label, issues: [issue])
+ label.toggle_subscription(subscriber)
+ notification.new_issue(issue, @u_disabled)
+
+ should_email(subscriber)
+ end
end
describe :reassigned_issue do
- it 'should email new assignee' do
+ it 'emails new assignee' do
notification.reassigned_issue(issue, @u_disabled)
should_email(issue.assignee)
@@ -234,6 +247,91 @@ describe NotificationService, services: true do
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
+
+ it 'emails previous assignee even if he has the "on mention" notif level' do
+ issue.update_attribute(:assignee, @u_mentioned)
+ issue.update_attributes(assignee: @u_watcher)
+ notification.reassigned_issue(issue, @u_disabled)
+
+ should_email(@u_mentioned)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ end
+
+ it 'emails new assignee even if he has the "on mention" notif level' do
+ issue.update_attributes(assignee: @u_mentioned)
+ notification.reassigned_issue(issue, @u_disabled)
+
+ expect(issue.assignee).to be @u_mentioned
+ should_email(issue.assignee)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ end
+
+ it 'emails new assignee' do
+ issue.update_attribute(:assignee, @u_mentioned)
+ notification.reassigned_issue(issue, @u_disabled)
+
+ expect(issue.assignee).to be @u_mentioned
+ should_email(issue.assignee)
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ end
+
+ it 'does not email new assignee if they are the current user' do
+ issue.update_attribute(:assignee, @u_mentioned)
+ notification.reassigned_issue(issue, @u_mentioned)
+
+ expect(issue.assignee).to be @u_mentioned
+ should_email(@u_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_not_email(issue.assignee)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ end
+ end
+
+ describe '#relabeled_issue' do
+ let(:label) { create(:label, issues: [issue]) }
+ let(:label2) { create(:label) }
+ let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
+ let!(:subscriber_to_label2) { create(:user).tap { |u| label2.toggle_subscription(u) } }
+
+ it "emails subscribers of the issue's added labels only" do
+ notification.relabeled_issue(issue, [label2], @u_disabled)
+
+ should_not_email(subscriber_to_label)
+ should_email(subscriber_to_label2)
+ end
+
+ it "doesn't send email to anyone but subscribers of the given labels" do
+ notification.relabeled_issue(issue, [label2], @u_disabled)
+
+ should_not_email(issue.assignee)
+ should_not_email(issue.author)
+ should_not_email(@u_watcher)
+ should_not_email(@u_participant_mentioned)
+ should_not_email(@subscriber)
+ should_not_email(@watcher_and_subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(subscriber_to_label)
+ should_email(subscriber_to_label2)
+ end
end
describe :close_issue do
@@ -245,6 +343,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -260,6 +359,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
end
@@ -282,10 +382,20 @@ describe NotificationService, services: true do
should_email(merge_request.assignee)
should_email(@u_watcher)
+ should_email(@watcher_and_subscriber)
should_email(@u_participant_mentioned)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
+
+ it "emails subscribers of the merge request's labels" do
+ subscriber = create(:user)
+ label = create(:label, merge_requests: [merge_request])
+ label.toggle_subscription(subscriber)
+ notification.new_merge_request(merge_request, @u_disabled)
+
+ should_email(subscriber)
+ end
end
describe :reassigned_merge_request do
@@ -296,12 +406,42 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
end
+ describe :relabel_merge_request do
+ let(:label) { create(:label, merge_requests: [merge_request]) }
+ let(:label2) { create(:label) }
+ let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
+ let!(:subscriber_to_label2) { create(:user).tap { |u| label2.toggle_subscription(u) } }
+
+ it "emails subscribers of the merge request's added labels only" do
+ notification.relabeled_merge_request(merge_request, [label2], @u_disabled)
+
+ should_not_email(subscriber_to_label)
+ should_email(subscriber_to_label2)
+ end
+
+ it "doesn't send email to anyone but subscribers of the given labels" do
+ notification.relabeled_merge_request(merge_request, [label2], @u_disabled)
+
+ should_not_email(merge_request.assignee)
+ should_not_email(merge_request.author)
+ should_not_email(@u_watcher)
+ should_not_email(@u_participant_mentioned)
+ should_not_email(@subscriber)
+ should_not_email(@watcher_and_subscriber)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(subscriber_to_label)
+ should_email(subscriber_to_label2)
+ end
+ end
+
describe :closed_merge_request do
it do
notification.close_mr(merge_request, @u_disabled)
@@ -310,6 +450,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -324,6 +465,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -338,6 +480,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -387,25 +530,17 @@ describe NotificationService, services: true do
@subscriber = create :user
@unsubscriber = create :user
@subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING)
+ @watcher_and_subscriber = create(:user, notification_level: Notification::N_WATCH)
project.team << [@subscribed_participant, :master]
project.team << [@subscriber, :master]
project.team << [@unsubscriber, :master]
+ project.team << [@watcher_and_subscriber, :master]
issuable.subscriptions.create(user: @subscriber, subscribed: true)
issuable.subscriptions.create(user: @subscribed_participant, subscribed: true)
issuable.subscriptions.create(user: @unsubscriber, subscribed: false)
- end
-
- def sent_to_user?(user)
- ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
- end
-
- def should_email(user)
- expect(sent_to_user?(user)).to be_truthy
- end
-
- def should_not_email(user)
- expect(sent_to_user?(user)).to be_falsey
+ # Make the watcher a subscriber to detect dupes
+ issuable.subscriptions.create(user: @watcher_and_subscriber, subscribed: true)
end
end
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
new file mode 100644
index 00000000000..6108c26a78b
--- /dev/null
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe Projects::AutocompleteService, services: true do
+ describe '#issues' do
+ describe 'confidential issues' do
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:project) { create(:empty_project, :public) }
+ let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
+ let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
+ let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
+
+ it 'should not list project confidential issues for guests' do
+ autocomplete = described_class.new(project, nil)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).not_to include security_issue_1.iid
+ expect(issues).not_to include security_issue_2.iid
+ expect(issues.count).to eq 1
+ end
+
+ it 'should not list project confidential issues for non project members' do
+ autocomplete = described_class.new(project, non_member)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).not_to include security_issue_1.iid
+ expect(issues).not_to include security_issue_2.iid
+ expect(issues.count).to eq 1
+ end
+
+ it 'should list project confidential issues for author' do
+ autocomplete = described_class.new(project, author)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).to include security_issue_1.iid
+ expect(issues).not_to include security_issue_2.iid
+ expect(issues.count).to eq 2
+ end
+
+ it 'should list project confidential issues for assignee' do
+ autocomplete = described_class.new(project, assignee)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).not_to include security_issue_1.iid
+ expect(issues).to include security_issue_2.iid
+ expect(issues.count).to eq 2
+ end
+
+ it 'should list project confidential issues for project members' do
+ project.team << [member, :developer]
+
+ autocomplete = described_class.new(project, member)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).to include security_issue_1.iid
+ expect(issues).to include security_issue_2.iid
+ expect(issues.count).to eq 3
+ end
+
+ it 'should list all project issues for admin' do
+ autocomplete = described_class.new(project, admin)
+ issues = autocomplete.issues.map(&:iid)
+
+ expect(issues).to include issue.iid
+ expect(issues).to include security_issue_1.iid
+ expect(issues).to include security_issue_2.iid
+ expect(issues.count).to eq 3
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 5d0b18558b1..e43903dbd3c 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -32,6 +32,7 @@ describe Projects::CreateService, services: true do
it { expect(@project).to be_valid }
it { expect(@project.owner).to eq(@user) }
+ it { expect(@project.team.masters).to include(@user) }
it { expect(@project.namespace).to eq(@user.namespace) }
end
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index 5ceed5af9a5..f252e2c5902 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -33,12 +33,12 @@ describe Projects::DownloadService, services: true do
@link_to_file = download_file(@project, url)
end
- it { expect(@link_to_file).to have_key('alt') }
- it { expect(@link_to_file).to have_key('url') }
- it { expect(@link_to_file).to have_key('is_image') }
- it { expect(@link_to_file['is_image']).to be true }
- it { expect(@link_to_file['url']).to match('rails_sample.jpg') }
- it { expect(@link_to_file['alt']).to eq('rails_sample') }
+ it { expect(@link_to_file).to have_key(:alt) }
+ it { expect(@link_to_file).to have_key(:url) }
+ it { expect(@link_to_file).to have_key(:is_image) }
+ it { expect(@link_to_file[:is_image]).to be true }
+ it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
+ it { expect(@link_to_file[:alt]).to eq('rails_sample') }
end
context 'a txt file' do
@@ -47,12 +47,12 @@ describe Projects::DownloadService, services: true do
@link_to_file = download_file(@project, url)
end
- it { expect(@link_to_file).to have_key('alt') }
- it { expect(@link_to_file).to have_key('url') }
- it { expect(@link_to_file).to have_key('is_image') }
- it { expect(@link_to_file['is_image']).to be false }
- it { expect(@link_to_file['url']).to match('doc_sample.txt') }
- it { expect(@link_to_file['alt']).to eq('doc_sample.txt') }
+ it { expect(@link_to_file).to have_key(:alt) }
+ it { expect(@link_to_file).to have_key(:url) }
+ it { expect(@link_to_file).to have_key(:is_image) }
+ it { expect(@link_to_file[:is_image]).to be false }
+ it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
+ it { expect(@link_to_file[:alt]).to eq('doc_sample.txt') }
end
end
end
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
new file mode 100644
index 00000000000..93bf1b81fbe
--- /dev/null
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Projects::HousekeepingService do
+ subject { Projects::HousekeepingService.new(project) }
+ let(:project) { create :project }
+
+ describe 'execute' do
+ before do
+ project.pushes_since_gc = 3
+ project.save!
+ end
+
+ it 'enqueues a sidekiq job' do
+ expect(subject).to receive(:try_obtain_lease).and_return(true)
+ expect(GitlabShellWorker).to receive(:perform_async).with(:gc, project.path_with_namespace)
+
+ subject.execute
+ expect(project.pushes_since_gc).to eq(0)
+ end
+
+ it 'does not enqueue a job when no lease can be obtained' do
+ expect(subject).to receive(:try_obtain_lease).and_return(false)
+ expect(GitlabShellWorker).not_to receive(:perform_async)
+
+ expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken)
+ expect(project.pushes_since_gc).to eq(0)
+ end
+ end
+
+ describe 'needed?' do
+ it 'when the count is low enough' do
+ expect(subject.needed?).to eq(false)
+ end
+
+ it 'when the count is high enough' do
+ allow(project).to receive(:pushes_since_gc).and_return(10)
+ expect(subject.needed?).to eq(true)
+ end
+ end
+
+ describe 'increment!' do
+ it 'increments the pushes_since_gc counter' do
+ expect(project.pushes_since_gc).to eq(0)
+ subject.increment!
+ expect(project.pushes_since_gc).to eq(1)
+ end
+ end
+end
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
new file mode 100644
index 00000000000..04f474c736c
--- /dev/null
+++ b/spec/services/projects/import_service_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper'
+
+describe Projects::ImportService, services: true do
+ let!(:project) { create(:empty_project) }
+ let(:user) { project.creator }
+
+ subject { described_class.new(project, user) }
+
+ describe '#execute' do
+ context 'with unknown url' do
+ before do
+ project.import_url = Project::UNKNOWN_IMPORT_URL
+ end
+
+ it 'succeeds if repository is created successfully' do
+ expect(project).to receive(:create_repository).and_return(true)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if repository creation fails' do
+ expect(project).to receive(:create_repository).and_return(false)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'The repository could not be created.'
+ end
+ end
+
+ context 'with known url' do
+ before do
+ project.import_url = 'https://github.com/vim/vim.git'
+ end
+
+ it 'succeeds if repository import is successfully' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if repository import fails' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_raise(Gitlab::Shell::Error.new('Failed to import the repository'))
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'Failed to import the repository'
+ end
+ end
+
+ context 'with valid importer' do
+ before do
+ stub_github_omniauth_provider
+
+ project.import_url = 'https://github.com/vim/vim.git'
+ project.import_type = 'github'
+
+ allow(project).to receive(:import_data).and_return(double.as_null_object)
+ end
+
+ it 'succeeds if importer succeeds' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(true)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :success
+ end
+
+ it 'fails if importer fails' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_return(false)
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'The remote data could not be imported.'
+ end
+
+ it 'fails if importer raise an error' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.path_with_namespace, project.import_url).and_return(true)
+ expect_any_instance_of(Gitlab::GithubImport::Importer).to receive(:execute).and_raise(Projects::ImportService::Error.new('Github: failed to connect API'))
+
+ result = subject.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'Github: failed to connect API'
+ end
+ end
+
+ def stub_github_omniauth_provider
+ provider = OpenStruct.new(
+ name: 'github',
+ app_id: 'asd123',
+ app_secret: 'asd123'
+ )
+
+ Gitlab.config.omniauth.providers << provider
+ end
+ end
+end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 3c06a890163..e8b9e6b9238 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -102,8 +102,8 @@ describe Projects::UpdateService, services: true do
describe :visibility_level do
let(:user) { create :user, admin: true }
- let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
- let(:forked_project) { create :forked_project_with_submodules, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+ let(:project) { create(:project, :internal) }
+ let(:forked_project) { create(:forked_project_with_submodules, :internal) }
let(:opts) { {} }
before do
diff --git a/spec/services/repair_ldap_blocked_user_service_spec.rb b/spec/services/repair_ldap_blocked_user_service_spec.rb
new file mode 100644
index 00000000000..ce7d1455975
--- /dev/null
+++ b/spec/services/repair_ldap_blocked_user_service_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe RepairLdapBlockedUserService, services: true do
+ let(:user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
+ let(:identity) { user.ldap_identity }
+ subject(:service) { RepairLdapBlockedUserService.new(user) }
+
+ describe '#execute' do
+ it 'change to normal block after destroying last ldap identity' do
+ identity.destroy
+ service.execute
+
+ expect(user.reload).not_to be_ldap_blocked
+ end
+
+ it 'change to normal block after changing last ldap identity to another provider' do
+ identity.update_attribute(:provider, 'twitter')
+ service.execute
+
+ expect(user.reload).not_to be_ldap_blocked
+ end
+ end
+end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index febc78d2784..fef211ded50 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -9,37 +9,54 @@ describe SystemHooksService, services: true do
let(:group_member) { create(:group_member) }
context 'event data' do
- it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :email, :user_id) }
- it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :email, :user_id) }
- it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
- it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
+ it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) }
+ it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) }
+ it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
+ it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
+ it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
+ it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
it { expect(event_data(key, :create)).to include(:username, :key, :id) }
it { expect(event_data(key, :destroy)).to include(:username, :key, :id) }
it do
+ project.old_path_with_namespace = 'renamed_from_path'
+ expect(event_data(project, :rename)).to include(
+ :event_name, :name, :created_at, :updated_at, :path, :project_id,
+ :owner_name, :owner_email, :project_visibility,
+ :old_path_with_namespace
+ )
+ end
+ it do
+ project.old_path_with_namespace = 'transfered_from_path'
+ expect(event_data(project, :transfer)).to include(
+ :event_name, :name, :created_at, :updated_at, :path, :project_id,
+ :owner_name, :owner_email, :project_visibility,
+ :old_path_with_namespace
+ )
+ end
+
+ it do
expect(event_data(group, :create)).to include(
- :event_name, :name, :created_at, :path, :group_id, :owner_name,
- :owner_email
+ :event_name, :name, :created_at, :updated_at, :path, :group_id,
+ :owner_name, :owner_email
)
end
it do
expect(event_data(group, :destroy)).to include(
- :event_name, :name, :created_at, :path, :group_id, :owner_name,
- :owner_email
+ :event_name, :name, :created_at, :updated_at, :path, :group_id,
+ :owner_name, :owner_email
)
end
it do
expect(event_data(group_member, :create)).to include(
- :event_name, :created_at, :group_name, :group_path, :group_id, :user_id,
- :user_name, :user_email, :group_access
+ :event_name, :created_at, :updated_at, :group_name, :group_path,
+ :group_id, :user_id, :user_username, :user_name, :user_email, :group_access
)
end
it do
expect(event_data(group_member, :destroy)).to include(
- :event_name, :created_at, :group_name, :group_path, :group_id, :user_id,
- :user_name, :user_email, :group_access
+ :event_name, :created_at, :updated_at, :group_name, :group_path,
+ :group_id, :user_id, :user_username, :user_name, :user_email, :group_access
)
end
end
@@ -49,6 +66,8 @@ describe SystemHooksService, services: true do
it { expect(event_name(user, :destroy)).to eq "user_destroy" }
it { expect(event_name(project, :create)).to eq "project_create" }
it { expect(event_name(project, :destroy)).to eq "project_destroy" }
+ it { expect(event_name(project, :rename)).to eq "project_rename" }
+ it { expect(event_name(project, :transfer)).to eq "project_transfer" }
it { expect(event_name(project_member, :create)).to eq "user_add_to_team" }
it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" }
it { expect(event_name(key, :create)).to eq 'key_create' }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index c9f828ae2f7..8e6292014d4 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -171,7 +171,7 @@ describe SystemNoteService, services: true do
context 'when milestone added' do
it 'sets the note text' do
- expect(subject.note).to eq "Milestone changed to #{milestone.title}"
+ expect(subject.note).to eq "Milestone changed to #{milestone.to_reference}"
end
end
@@ -280,6 +280,18 @@ describe SystemNoteService, services: true do
end
end
+ describe '.new_issue_branch' do
+ subject { described_class.new_issue_branch(noteable, project, author, "1-mepmep") }
+
+ it_behaves_like 'a system note'
+
+ context 'when a branch is created from the new branch button' do
+ it 'sets the note text' do
+ expect(subject.note).to match /\AStarted branch [`1-mepmep`]/
+ end
+ end
+ end
+
describe '.cross_reference' do
subject { described_class.cross_reference(noteable, mentioner, author) }
@@ -424,6 +436,21 @@ describe SystemNoteService, services: true do
to be_falsey
end
end
+
+ context 'commit with cross-reference from fork' do
+ let(:author2) { create(:user) }
+ let(:forked_project) { Projects::ForkService.new(project, author2).execute }
+ let(:commit2) { forked_project.commit }
+
+ before do
+ described_class.cross_reference(noteable, commit0, author2)
+ end
+
+ it 'is true when a fork mentions an external issue' do
+ expect(described_class.cross_reference_exists?(noteable, commit2)).
+ to be true
+ end
+ end
end
include JiraServiceHelper
@@ -459,8 +486,8 @@ describe SystemNoteService, services: true do
describe "existing reference" do
before do
- message = "[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]."
- WebMock.stub_request(:get, jira_api_comment_url).to_return(body: "{\"comments\":[{\"body\":\"#{message}\"}]}")
+ message = %Q{[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\\n'#{commit.title}'}
+ WebMock.stub_request(:get, jira_api_comment_url).to_return(body: %Q({"comments":[{"body":"#{message}"}]}))
end
subject { described_class.cross_reference(jira_issue, commit, author) }
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
new file mode 100644
index 00000000000..96420acb31d
--- /dev/null
+++ b/spec/services/todo_service_spec.rb
@@ -0,0 +1,274 @@
+require 'spec_helper'
+
+describe TodoService, services: true do
+ let(:author) { create(:user) }
+ let(:john_doe) { create(:user, username: 'john_doe') }
+ let(:michael) { create(:user, username: 'michael') }
+ let(:stranger) { create(:user, username: 'stranger') }
+ let(:project) { create(:project) }
+ let(:mentions) { [author.to_reference, john_doe.to_reference, michael.to_reference, stranger.to_reference].join(' ') }
+ let(:service) { described_class.new }
+
+ before do
+ project.team << [author, :developer]
+ project.team << [john_doe, :developer]
+ project.team << [michael, :developer]
+ end
+
+ describe 'Issues' do
+ let(:issue) { create(:issue, project: project, assignee: john_doe, author: author, description: mentions) }
+ let(:unassigned_issue) { create(:issue, project: project, assignee: nil) }
+
+ describe '#new_issue' do
+ it 'creates a todo if assigned' do
+ service.new_issue(issue, author)
+
+ should_create_todo(user: john_doe, target: issue, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo if unassigned' do
+ should_not_create_any_todo { service.new_issue(unassigned_issue, author) }
+ end
+
+ it 'does not create a todo if assignee is the current user' do
+ should_not_create_any_todo { service.new_issue(unassigned_issue, john_doe) }
+ end
+
+ it 'creates a todo for each valid mentioned user' do
+ service.new_issue(issue, author)
+
+ should_create_todo(user: michael, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED)
+ end
+ end
+
+ describe '#update_issue' do
+ it 'creates a todo for each valid mentioned user' do
+ service.update_issue(issue, author)
+
+ should_create_todo(user: michael, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
+ should_not_create_todo(user: stranger, target: issue, action: Todo::MENTIONED)
+ end
+
+ it 'does not create a todo if user was already mentioned' do
+ create(:todo, :mentioned, user: michael, project: project, target: issue, author: author)
+
+ expect { service.update_issue(issue, author) }.not_to change(michael.todos, :count)
+ end
+ end
+
+ describe '#close_issue' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+
+ service.close_issue(issue, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+ end
+
+ describe '#reassigned_issue' do
+ it 'creates a pending todo for new assignee' do
+ unassigned_issue.update_attribute(:assignee, john_doe)
+ service.reassigned_issue(unassigned_issue, author)
+
+ should_create_todo(user: john_doe, target: unassigned_issue, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo if unassigned' do
+ issue.update_attribute(:assignee, nil)
+
+ should_not_create_any_todo { service.reassigned_issue(issue, author) }
+ end
+
+ it 'does not create a todo if new assignee is the current user' do
+ unassigned_issue.update_attribute(:assignee, john_doe)
+
+ should_not_create_any_todo { service.reassigned_issue(unassigned_issue, john_doe) }
+ end
+ end
+
+ describe '#mark_pending_todos_as_done' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+
+ service.mark_pending_todos_as_done(issue, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+ end
+
+ describe '#new_note' do
+ let!(:first_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
+ let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
+ let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
+ let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
+ let(:note_on_project_snippet) { create(:note_on_project_snippet, project: project, author: john_doe, note: mentions) }
+ let(:award_note) { create(:note, :award, project: project, noteable: issue, author: john_doe, note: 'thumbsup') }
+ let(:system_note) { create(:system_note, project: project, noteable: issue) }
+
+ it 'mark related pending todos to the noteable for the note author as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author)
+
+ service.new_note(note, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+
+ it 'mark related pending todos to the noteable for the award note author as done' do
+ service.new_note(award_note, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+
+ it 'does not mark related pending todos it is a system note' do
+ service.new_note(system_note, john_doe)
+
+ expect(first_todo.reload).to be_pending
+ expect(second_todo.reload).to be_pending
+ end
+
+ it 'creates a todo for each valid mentioned user' do
+ service.new_note(note, john_doe)
+
+ should_create_todo(user: michael, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_not_create_todo(user: stranger, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ end
+
+ it 'does not create todo when leaving a note on commit' do
+ should_not_create_any_todo { service.new_note(note_on_commit, john_doe) }
+ end
+
+ it 'does not create todo when leaving a note on snippet' do
+ should_not_create_any_todo { service.new_note(note_on_project_snippet, john_doe) }
+ end
+ end
+ end
+
+ describe 'Merge Requests' do
+ let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignee: john_doe, description: mentions) }
+ let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignee: nil) }
+
+ describe '#new_merge_request' do
+ it 'creates a pending todo if assigned' do
+ service.new_merge_request(mr_assigned, author)
+
+ should_create_todo(user: john_doe, target: mr_assigned, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo if unassigned' do
+ should_not_create_any_todo { service.new_merge_request(mr_unassigned, author) }
+ end
+
+ it 'does not create a todo if assignee is the current user' do
+ should_not_create_any_todo { service.new_merge_request(mr_unassigned, john_doe) }
+ end
+
+ it 'creates a todo for each valid mentioned user' do
+ service.new_merge_request(mr_assigned, author)
+
+ should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED)
+ end
+ end
+
+ describe '#update_merge_request' do
+ it 'creates a todo for each valid mentioned user' do
+ service.update_merge_request(mr_assigned, author)
+
+ should_create_todo(user: michael, target: mr_assigned, action: Todo::MENTIONED)
+ should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
+ should_not_create_todo(user: stranger, target: mr_assigned, action: Todo::MENTIONED)
+ end
+
+ it 'does not create a todo if user was already mentioned' do
+ create(:todo, :mentioned, user: michael, project: project, target: mr_assigned, author: author)
+
+ expect { service.update_merge_request(mr_assigned, author) }.not_to change(michael.todos, :count)
+ end
+ end
+
+ describe '#close_merge_request' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
+ service.close_merge_request(mr_assigned, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+ end
+
+ describe '#reassigned_merge_request' do
+ it 'creates a pending todo for new assignee' do
+ mr_unassigned.update_attribute(:assignee, john_doe)
+ service.reassigned_merge_request(mr_unassigned, author)
+
+ should_create_todo(user: john_doe, target: mr_unassigned, action: Todo::ASSIGNED)
+ end
+
+ it 'does not create a todo if unassigned' do
+ mr_assigned.update_attribute(:assignee, nil)
+
+ should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, author) }
+ end
+
+ it 'does not create a todo if new assignee is the current user' do
+ mr_assigned.update_attribute(:assignee, john_doe)
+
+ should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, john_doe) }
+ end
+ end
+
+ describe '#merge_merge_request' do
+ it 'marks related pending todos to the target for the user as done' do
+ first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
+ second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author)
+ service.merge_merge_request(mr_assigned, john_doe)
+
+ expect(first_todo.reload).to be_done
+ expect(second_todo.reload).to be_done
+ end
+ end
+ end
+
+ def should_create_todo(attributes = {})
+ attributes.reverse_merge!(
+ project: project,
+ author: author,
+ state: :pending
+ )
+
+ expect(Todo.where(attributes).count).to eq 1
+ end
+
+ def should_not_create_todo(attributes = {})
+ attributes.reverse_merge!(
+ project: project,
+ author: author,
+ state: :pending
+ )
+
+ expect(Todo.where(attributes).count).to eq 0
+ end
+
+ def should_not_create_any_todo
+ expect { yield }.not_to change(Todo, :count)
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 0225a0ee53f..596d607f2a1 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -14,7 +14,7 @@ require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'shoulda/matchers'
require 'sidekiq/testing/inline'
-require 'benchmark/ips'
+require 'rspec/retry'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
@@ -25,16 +25,19 @@ RSpec.configure do |config|
config.use_instantiated_fixtures = false
config.mock_with :rspec
+ config.verbose_retry = true
+ config.display_try_failure_messages = true
+
config.include Devise::TestHelpers, type: :controller
config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request
config.include StubConfiguration
+ config.include EmailHelpers
config.include RelativeUrl, type: feature
config.include TestEnv
config.include ActiveJob::TestHelper
config.include StubGitlabCalls
config.include StubGitlabData
- config.include BenchmarkMatchers, benchmark: true
config.infer_spec_type_from_file_location!
config.raise_errors_for_deprecations!
@@ -48,4 +51,10 @@ FactoryGirl::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
+# Work around a Rails 4.2.5.1 issue
+# See https://github.com/rspec/rspec-rails/issues/1532
+RSpec::Rails::ViewRendering::EmptyTemplatePathSetDecorator.class_eval do
+ alias_method :find_all_anywhere, :find_all
+end
+
ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/api/pagination_shared_examples.rb b/spec/support/api/pagination_shared_examples.rb
new file mode 100644
index 00000000000..352a6eeec79
--- /dev/null
+++ b/spec/support/api/pagination_shared_examples.rb
@@ -0,0 +1,20 @@
+# Specs for paginated resources.
+#
+# Requires an API request:
+# let(:request) { get api("/projects/#{project.id}/repository/branches", user) }
+shared_examples 'a paginated resources' do
+ before do
+ # Fires the request
+ request
+ end
+
+ it 'has pagination headers' do
+ expect(response.headers).to include('X-Total')
+ expect(response.headers).to include('X-Total-Pages')
+ expect(response.headers).to include('X-Per-Page')
+ expect(response.headers).to include('X-Page')
+ expect(response.headers).to include('X-Next-Page')
+ expect(response.headers).to include('X-Prev-Page')
+ expect(response.headers).to include('Link')
+ end
+end
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index fed1ab6ee33..e1f90e17cce 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -7,10 +7,10 @@ timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 90 : 10
Capybara.javascript_driver = :poltergeist
Capybara.register_driver :poltergeist do |app|
- Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout)
+ Capybara::Poltergeist::Driver.new(app, js_errors: true, timeout: timeout, window_size: [1366, 768])
end
-Capybara.default_wait_time = timeout
+Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = true
unless ENV['CI'] || ENV['CI_SERVER']
@@ -19,3 +19,9 @@ unless ENV['CI'] || ENV['CI_SERVER']
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
end
+
+RSpec.configure do |config|
+ config.before(:suite) do
+ TestEnv.warm_asset_cache
+ end
+end
diff --git a/spec/support/email_format_shared_examples.rb b/spec/support/email_format_shared_examples.rb
new file mode 100644
index 00000000000..b924a208e71
--- /dev/null
+++ b/spec/support/email_format_shared_examples.rb
@@ -0,0 +1,44 @@
+# Specifications for behavior common to all objects with an email attribute.
+# Takes a list of email-format attributes and requires:
+# - subject { "the object with a attribute= setter" }
+# Note: You have access to `email_value` which is the email address value
+# being currently tested).
+
+shared_examples 'an object with email-formated attributes' do |*attributes|
+ attributes.each do |attribute|
+ describe "specifically its :#{attribute} attribute" do
+ %w[
+ info@example.com
+ info+test@example.com
+ o'reilly@example.com
+ mailto:test@example.com
+ lol!'+=?><#$%^&*()@gmail.com
+ ].each do |valid_email|
+ context "with a value of '#{valid_email}'" do
+ let(:email_value) { valid_email }
+
+ it 'is valid' do
+ subject.send("#{attribute}=", valid_email)
+
+ expect(subject).to be_valid
+ end
+ end
+ end
+
+ %w[
+ foobar
+ test@test@example.com
+ ].each do |invalid_email|
+ context "with a value of '#{invalid_email}'" do
+ let(:email_value) { invalid_email }
+
+ it 'is invalid' do
+ subject.send("#{attribute}=", invalid_email)
+
+ expect(subject).to be_invalid
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/email_helpers.rb b/spec/support/email_helpers.rb
new file mode 100644
index 00000000000..a85ab22ce36
--- /dev/null
+++ b/spec/support/email_helpers.rb
@@ -0,0 +1,13 @@
+module EmailHelpers
+ def sent_to_user?(user)
+ ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
+ end
+
+ def should_email(user)
+ expect(sent_to_user?(user)).to be_truthy
+ end
+
+ def should_not_email(user)
+ expect(sent_to_user?(user)).to be_falsey
+ end
+end
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index d6e03cbef3d..ef5ea7d626e 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -67,9 +67,9 @@ module FilterSpecHelper
if reference =~ /\A(.+)?.\d+\z/
# Integer-based reference with optional project prefix
reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
- elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
+ elsif reference =~ /\A(.+@)?(\h{7,40}\z)/
# SHA-based reference with optional prefix
- reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
+ reference.gsub(/\h{7,40}\z/) { |v| v.reverse }
else
reference.gsub(/\w+\z/) { |v| v.reverse }
end
diff --git a/spec/support/gitlab_stubs/gitlab_ci.yml b/spec/support/gitlab_stubs/gitlab_ci.yml
index 3482145404e..a5b256bd3ec 100644
--- a/spec/support/gitlab_stubs/gitlab_ci.yml
+++ b/spec/support/gitlab_stubs/gitlab_ci.yml
@@ -36,8 +36,8 @@ staging:
script: "cap deploy stating"
type: deploy
tags:
- - capistrano
- - debian
+ - ruby
+ - mysql
except:
- stable
@@ -47,8 +47,8 @@ production:
- cap deploy production
- cap notify
tags:
- - capistrano
- - debian
+ - ruby
+ - mysql
only:
- master
- /^deploy-.*$/
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index d6d3062a197..73c6792b65f 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -28,6 +28,10 @@ class MarkdownFeature
end
end
+ def project_wiki
+ @project_wiki ||= ProjectWiki.new(project, user)
+ end
+
def issue
@issue ||= create(:issue, project: project)
end
@@ -59,6 +63,10 @@ class MarkdownFeature
@label ||= create(:label, name: 'awaiting feedback', project: project)
end
+ def milestone
+ @milestone ||= create(:milestone, project: project)
+ end
+
# Cross-references -----------------------------------------------------------
def xproject
@@ -93,6 +101,10 @@ class MarkdownFeature
end
end
+ def xmilestone
+ @xmilestone ||= create(:milestone, project: xproject)
+ end
+
def urls
Gitlab::Application.routes.url_helpers
end
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index 558e8b1612f..4e007c777e3 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -15,6 +15,8 @@ module AccessMatchers
logout
when :admin
login_as(create(:admin))
+ when :external
+ login_as(create(:user, external: true))
when User
login_as(user)
else
diff --git a/spec/support/matchers/benchmark_matchers.rb b/spec/support/matchers/benchmark_matchers.rb
deleted file mode 100644
index 84f655c2119..00000000000
--- a/spec/support/matchers/benchmark_matchers.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-module BenchmarkMatchers
- extend RSpec::Matchers::DSL
-
- def self.included(into)
- into.extend(ClassMethods)
- end
-
- matcher :iterate_per_second do |min_iterations|
- supports_block_expectations
-
- match do |block|
- @max_stddev ||= 30
-
- @entry = benchmark(&block)
-
- expect(@entry.ips).to be >= min_iterations
- expect(@entry.stddev_percentage).to be <= @max_stddev
- end
-
- chain :with_maximum_stddev do |value|
- @max_stddev = value
- end
-
- description do
- "run at least #{min_iterations} iterations per second"
- end
-
- failure_message do
- ips = @entry.ips.round(2)
- stddev = @entry.stddev_percentage.round(2)
-
- "expected at least #{min_iterations} iterations per second " \
- "with a maximum stddev of #{@max_stddev}%, instead of " \
- "#{ips} iterations per second with a stddev of #{stddev}%"
- end
- end
-
- # Benchmarks the given block and returns a Benchmark::IPS::Report::Entry.
- def benchmark(&block)
- report = Benchmark.ips(quiet: true) do |bench|
- bench.report do
- instance_eval(&block)
- end
- end
-
- report.entries[0]
- end
-
- module ClassMethods
- # Wraps around rspec's subject method so you can write:
- #
- # benchmark_subject { SomeClass.some_method }
- #
- # instead of:
- #
- # subject { -> { SomeClass.some_method } }
- def benchmark_subject(&block)
- subject { block }
- end
- end
-end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 7eadcd58c1f..1d52489e804 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -66,6 +66,24 @@ module MarkdownMatchers
end
end
+ # GollumTagsFilter
+ matcher :parse_gollum_tags do
+ def have_image(src)
+ have_css("img[src$='#{src}']")
+ end
+
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_link('linked-resource', href: 'linked-resource')
+ expect(actual).to have_link('link-text', href: 'linked-resource')
+ expect(actual).to have_link('http://example.com', href: 'http://example.com')
+ expect(actual).to have_link('link-text', href: 'http://example.com/pdfs/gollum.pdf')
+ expect(actual).to have_image('/gitlabhq/wikis/images/example.jpg')
+ expect(actual).to have_image('http://example.com/images/example.jpg')
+ end
+ end
+
# UserReferenceFilter
matcher :reference_users do
set_default_markdown_messages
@@ -130,6 +148,15 @@ module MarkdownMatchers
end
end
+ # MilestoneReferenceFilter
+ matcher :reference_milestones do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('a.gfm.gfm-milestone', count: 3)
+ end
+ end
+
# TaskListFilter
matcher :parse_task_lists do
set_default_markdown_messages
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index fce91015fd4..e876d44c166 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -52,6 +52,8 @@ shared_context 'mentionable context' do
end
set_mentionable_text.call(ref_string)
+
+ project.team << [author, :developer]
end
end
diff --git a/spec/support/project_hook_data_shared_example.rb b/spec/support/project_hook_data_shared_example.rb
new file mode 100644
index 00000000000..422083875d7
--- /dev/null
+++ b/spec/support/project_hook_data_shared_example.rb
@@ -0,0 +1,27 @@
+RSpec.shared_examples 'project hook data' do |project_key: :project|
+ it 'contains project data' do
+ expect(data[project_key][:name]).to eq(project.name)
+ expect(data[project_key][:description]).to eq(project.description)
+ expect(data[project_key][:web_url]).to eq(project.web_url)
+ expect(data[project_key][:avatar_url]).to eq(project.avatar_url)
+ expect(data[project_key][:git_http_url]).to eq(project.http_url_to_repo)
+ expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo)
+ expect(data[project_key][:namespace]).to eq(project.namespace.name)
+ expect(data[project_key][:visibility_level]).to eq(project.visibility_level)
+ expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace)
+ expect(data[project_key][:default_branch]).to eq(project.default_branch)
+ expect(data[project_key][:homepage]).to eq(project.web_url)
+ expect(data[project_key][:url]).to eq(project.url_to_repo)
+ expect(data[project_key][:ssh_url]).to eq(project.ssh_url_to_repo)
+ expect(data[project_key][:http_url]).to eq(project.http_url_to_repo)
+ end
+end
+
+RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project|
+ it 'contains deprecated repository data' do
+ expect(data[:repository][:name]).to eq(project.name)
+ expect(data[:repository][:description]).to eq(project.description)
+ expect(data[:repository][:url]).to eq(project.url_to_repo)
+ expect(data[:repository][:homepage]).to eq(project.web_url)
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 4f4743bff6d..0d1bd030f3c 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -146,6 +146,22 @@ module TestEnv
FileUtils.chmod_R 0755, target_repo_path
end
+ # When no cached assets exist, manually hit the root path to create them
+ #
+ # Otherwise they'd be created by the first test, often timing out and
+ # causing a transient test failure
+ def warm_asset_cache
+ return if warm_asset_cache?
+ return unless defined?(Capybara)
+
+ Capybara.current_session.driver.visit '/'
+ end
+
+ def warm_asset_cache?
+ cache = Rails.root.join(*%w(tmp cache assets test))
+ Dir.exist?(cache) && Dir.entries(cache).length > 2
+ end
+
private
def factory_repo_path
@@ -172,7 +188,6 @@ module TestEnv
'gitlab-test-fork'
end
-
# Prevent developer git configurations from being persisted to test
# repositories
def git_env
diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb
index 692d219e9f1..b90fc112671 100644
--- a/spec/support/wait_for_ajax.rb
+++ b/spec/support/wait_for_ajax.rb
@@ -1,6 +1,6 @@
module WaitForAjax
def wait_for_ajax
- Timeout.timeout(Capybara.default_wait_time) do
+ Timeout.timeout(Capybara.default_max_wait_time) do
loop until finished_all_ajax_requests?
end
end
diff --git a/spec/support/workhorse_helpers.rb b/spec/support/workhorse_helpers.rb
new file mode 100644
index 00000000000..107b6e30924
--- /dev/null
+++ b/spec/support/workhorse_helpers.rb
@@ -0,0 +1,16 @@
+module WorkhorseHelpers
+ extend self
+
+ def workhorse_send_data
+ @_workhorse_send_data ||= begin
+ header = response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]
+ split_header = header.split(':')
+ type = split_header.shift
+ header = split_header.join(':')
+ [
+ type,
+ JSON.parse(Base64.urlsafe_decode64(header)),
+ ]
+ end
+ end
+end
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
new file mode 100644
index 00000000000..05a76ee4bdb
--- /dev/null
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+describe 'devise/shared/_signin_box' do
+ describe 'Crowd form' do
+ before do
+ stub_devise
+ assign(:ldap_servers, [])
+ end
+
+ it 'is shown when Crowd is enabled' do
+ enable_crowd
+
+ render
+
+ expect(rendered).to have_selector('#tab-crowd form')
+ end
+
+ it 'is not shown when Crowd is disabled' do
+ render
+
+ expect(rendered).not_to have_selector('#tab-crowd')
+ end
+ end
+
+ def stub_devise
+ allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:user])
+ allow(view).to receive(:resource).and_return(spy)
+ allow(view).to receive(:resource_name).and_return(:user)
+ end
+
+ def enable_crowd
+ allow(view).to receive(:form_based_providers).and_return([:crowd])
+ allow(view).to receive(:crowd_enabled?).and_return(true)
+ allow(view).to receive(:user_omniauth_authorize_path).with('crowd').
+ and_return('/crowd')
+ end
+end
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
new file mode 100644
index 00000000000..14c56521280
--- /dev/null
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe DeleteUserWorker do
+ let!(:user) { create(:user) }
+ let!(:current_user) { create(:user) }
+
+ it "calls the DeleteUserWorker with the params it was given" do
+ expect_any_instance_of(DeleteUserService).to receive(:execute).
+ with(user, {})
+
+ DeleteUserWorker.new.perform(current_user.id, user.id)
+ end
+
+ it "uses symbolized keys" do
+ expect_any_instance_of(DeleteUserService).to receive(:execute).
+ with(user, test: "test")
+
+ DeleteUserWorker.new.perform(current_user.id, user.id, "test" => "test")
+ end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index e4151b9bb6a..0265dbe9c66 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -11,7 +11,7 @@ describe PostReceive do
end
end
- context "web hook" do
+ context "webhook" do
let(:project) { create(:project) }
let(:key) { create(:key, user: project.owner) }
let(:key_id) { key.shell_id }
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index dae31992620..172537474ee 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -19,6 +19,18 @@ describe RepositoryForkWorker do
fork_project.namespace.path)
end
+ it 'flushes the empty caches' do
+ expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).
+ with(project.path_with_namespace, fork_project.namespace.path).
+ and_return(true)
+
+ expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
+ and_call_original
+
+ subject.perform(project.id, project.path_with_namespace,
+ fork_project.namespace.path)
+ end
+
it "handles bad fork" do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
subject.perform(
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
new file mode 100644
index 00000000000..6739063543b
--- /dev/null
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe RepositoryImportWorker do
+ let(:project) { create(:project) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ it 'imports a project' do
+ expect_any_instance_of(Projects::ImportService).to receive(:execute).
+ and_return({ status: :ok })
+
+ expect_any_instance_of(Repository).to receive(:expire_emptiness_caches)
+ expect_any_instance_of(Project).to receive(:import_finish)
+
+ subject.perform(project.id)
+ end
+ end
+end