diff options
Diffstat (limited to 'spec')
71 files changed, 2307 insertions, 700 deletions
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 421ab006792..fbf116e533b 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -162,6 +162,10 @@ describe ApplicationController do describe 'session expiration' do controller(described_class) do + # The anonymous controller will report 401 and fail to run any actions. + # Normally, GitLab will just redirect you to sign in. + skip_before_action :authenticate_user!, only: :index + def index render text: 'authenticated' end diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb index e133950e684..a3356a86d4b 100644 --- a/spec/controllers/notification_settings_controller_spec.rb +++ b/spec/controllers/notification_settings_controller_spec.rb @@ -21,10 +21,11 @@ describe NotificationSettingsController do end context 'when authorized' do + let(:notification_setting) { user.notification_settings_for(source) } let(:custom_events) do events = {} - NotificationSetting::EMAIL_EVENTS.each do |event| + NotificationSetting.email_events(source).each do |event| events[event.to_s] = true end @@ -36,7 +37,7 @@ describe NotificationSettingsController do end context 'for projects' do - let(:notification_setting) { user.notification_settings_for(project) } + let(:source) { project } it 'creates notification setting' do post :create, @@ -67,7 +68,7 @@ describe NotificationSettingsController do end context 'for groups' do - let(:notification_setting) { user.notification_settings_for(group) } + let(:source) { group } it 'creates notification setting' do post :create, @@ -145,7 +146,7 @@ describe NotificationSettingsController do let(:custom_events) do events = {} - NotificationSetting::EMAIL_EVENTS.each do |event| + notification_setting.email_events.each do |event| events[event] = "true" end end diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index a81b2169b89..81c485fba1a 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -46,6 +46,13 @@ FactoryBot.define do secret SecureRandom.hex end + trait :favicon_upload do + model { build(:appearance) } + path { File.join(secret, filename) } + uploader "FaviconUploader" + secret SecureRandom.hex + end + trait :attachment_upload do transient do mount_point :attachment diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index a0af2dea3ad..baa2b1d8af5 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -44,7 +44,7 @@ describe 'Issue Boards', :js do end it 'creates default lists' do - lists = ['Backlog', 'To Do', 'Doing', 'Closed'] + lists = ['Open', 'To Do', 'Doing', 'Closed'] page.within(find('.board-blank-state')) do click_button('Add default lists') diff --git a/spec/features/commits/user_uses_slash_commands_spec.rb b/spec/features/commits/user_uses_slash_commands_spec.rb new file mode 100644 index 00000000000..9a4b7bd2444 --- /dev/null +++ b/spec/features/commits/user_uses_slash_commands_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Commit > User uses quick actions', :js do + include Spec::Support::Helpers::Features::NotesHelpers + include RepoHelpers + + let(:project) { create(:project, :public, :repository) } + let(:user) { project.creator } + let(:commit) { project.commit } + + before do + project.add_maintainer(user) + sign_in(user) + + visit project_commit_path(project, commit.id) + end + + describe 'Tagging a commit' do + let(:tag_name) { 'v1.2.3' } + let(:tag_message) { 'Stable release' } + let(:truncated_commit_sha) { Commit.truncate_sha(commit.sha) } + + it 'tags this commit' do + add_note("/tag #{tag_name} #{tag_message}") + + expect(page).to have_content 'Commands applied' + expect(page).to have_content "tagged commit #{truncated_commit_sha}" + expect(page).to have_content tag_name + + visit project_tag_path(project, tag_name) + expect(page).to have_content tag_name + expect(page).to have_content tag_message + expect(page).to have_content truncated_commit_sha + end + + describe 'preview', :js do + it 'removes quick action from note and explains it' do + preview_note("/tag #{tag_name} #{tag_message}") + + expect(page).not_to have_content '/tag' + expect(page).to have_content %{Tags this commit to #{tag_name} with "#{tag_message}"} + expect(page).to have_content tag_name + end + end + end +end diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb deleted file mode 100644 index bf60b18873c..00000000000 --- a/spec/features/issues/award_emoji_spec.rb +++ /dev/null @@ -1,146 +0,0 @@ -require 'rails_helper' - -describe 'Awards Emoji' do - let!(:project) { create(:project, :public) } - let!(:user) { create(:user) } - let(:issue) do - create(:issue, - assignees: [user], - project: project) - end - - context 'authorized user' do - before do - project.add_maintainer(user) - sign_in(user) - end - - describe 'visiting an issue with a legacy award emoji that is not valid anymore' do - before do - # The `heart_tip` emoji is not valid anymore so we need to skip validation - issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false) - visit project_issue_path(project, issue) - wait_for_requests - end - - # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529 - it 'does not shows a 500 page', :js do - expect(page).to have_text(issue.title) - end - end - - describe 'Click award emoji from issue#show' do - let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") } - - before do - visit project_issue_path(project, issue) - wait_for_requests - end - - it 'increments the thumbsdown emoji', :js do - find('[data-name="thumbsdown"]').click - wait_for_requests - expect(thumbsdown_emoji).to have_text("1") - end - - context 'click the thumbsup emoji' do - it 'increments the thumbsup emoji', :js do - find('[data-name="thumbsup"]').click - wait_for_requests - expect(thumbsup_emoji).to have_text("1") - end - - it 'decrements the thumbsdown emoji', :js do - expect(thumbsdown_emoji).to have_text("0") - end - end - - context 'click the thumbsdown emoji' do - it 'increments the thumbsdown emoji', :js do - find('[data-name="thumbsdown"]').click - wait_for_requests - expect(thumbsdown_emoji).to have_text("1") - end - - it 'decrements the thumbsup emoji', :js do - expect(thumbsup_emoji).to have_text("0") - end - end - - it 'toggles the smiley emoji on a note', :js do - toggle_smiley_emoji(true) - - within('.note-body') do - expect(find(emoji_counter)).to have_text("1") - end - - toggle_smiley_emoji(false) - - within('.note-body') do - expect(page).not_to have_selector(emoji_counter) - end - end - - context 'execute /award quick action' do - it 'toggles the emoji award on noteable', :js do - execute_quick_action('/award :100:') - - expect(find(noteable_award_counter)).to have_text("1") - - execute_quick_action('/award :100:') - - expect(page).not_to have_selector(noteable_award_counter) - end - end - end - end - - context 'unauthorized user', :js do - before do - visit project_issue_path(project, issue) - end - - it 'has disabled emoji button' do - expect(first('.award-control')[:class]).to have_text('disabled') - end - end - - def execute_quick_action(cmd) - within('.js-main-target-form') do - fill_in 'note[note]', with: cmd - click_button 'Comment' - end - - wait_for_requests - end - - def thumbsup_emoji - page.all(emoji_counter).first - end - - def thumbsdown_emoji - page.all(emoji_counter).last - end - - def emoji_counter - 'span.js-counter' - end - - def noteable_award_counter - ".awards .active" - end - - def toggle_smiley_emoji(status) - within('.note') do - find('.note-emoji-button').click - end - - unless status - first('[data-name="smiley"]').click - else - find('[data-name="smiley"]').click - end - - wait_for_requests - end -end diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb deleted file mode 100644 index e53a4ce49c7..00000000000 --- a/spec/features/issues/award_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'rails_helper' - -describe 'Issue awards', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - let(:issue) { create(:issue, project: project) } - - describe 'logged in' do - before do - sign_in(user) - visit project_issue_path(project, issue) - wait_for_requests - end - - it 'adds award to issue' do - first('.js-emoji-btn').click - expect(page).to have_selector('.js-emoji-btn.active') - expect(first('.js-emoji-btn')).to have_content '1' - - visit project_issue_path(project, issue) - expect(first('.js-emoji-btn')).to have_content '1' - end - - it 'removes award from issue' do - first('.js-emoji-btn').click - find('.js-emoji-btn.active').click - expect(first('.js-emoji-btn')).to have_content '0' - - visit project_issue_path(project, issue) - expect(first('.js-emoji-btn')).to have_content '0' - end - - it 'only has one menu on the page' do - first('.js-add-award').click - expect(page).to have_selector('.emoji-menu') - - expect(page).to have_selector('.emoji-menu', count: 1) - end - end - - describe 'logged out' do - before do - visit project_issue_path(project, issue) - wait_for_requests - end - - it 'does not see award menu button' do - expect(page).not_to have_selector('.js-award-holder') - end - end -end diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb new file mode 100644 index 00000000000..afa425c2cec --- /dev/null +++ b/spec/features/issues/user_interacts_with_awards_spec.rb @@ -0,0 +1,347 @@ +require 'spec_helper' + +describe 'User interacts with awards' do + let(:user) { create(:user) } + + describe 'User interacts with awards in an issue', :js do + let(:issue) { create(:issue, project: project)} + let(:project) { create(:project) } + + before do + project.add_maintainer(user) + sign_in(user) + + visit(project_issue_path(project, issue)) + end + + it 'toggles the thumbsup award emoji' do + page.within('.awards') do + thumbsup = page.first('.award-control') + thumbsup.click + thumbsup.hover + + expect(page).to have_selector('.js-emoji-btn') + expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") + expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') + + thumbsup = page.first('.award-control') + thumbsup.click + thumbsup.hover + + expect(page).to have_selector('.award-control.js-emoji-btn') + expect(page.all('.award-control.js-emoji-btn').size).to eq(2) + + page.all('.award-control.js-emoji-btn').each do |element| + expect(element['title']).to eq('') + end + + expect(page.all('.award-control .js-counter')).to all(have_content('0')) + + thumbsup = page.first('.award-control') + thumbsup.click + thumbsup.hover + + expect(page).to have_selector('.js-emoji-btn') + expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") + expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') + end + end + + it 'toggles a custom award emoji' do + page.within('.awards') do + page.find('.js-add-award').click + end + + page.find('.emoji-menu.is-visible') + + expect(page).to have_selector('.js-emoji-menu-search') + expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) + + page.within('.emoji-menu-content') do + emoji_button = page.first('.js-emoji-btn') + emoji_button.hover + emoji_button.click + end + + page.within('.awards') do + expect(page).to have_selector('.js-emoji-btn') + expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') + expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") + + expect do + page.find('.js-emoji-btn.active').click + wait_for_requests + end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2) + end + end + + it 'shows the list of award emoji categories' do + page.within('.awards') do + page.find('.js-add-award').click + end + + page.find('.emoji-menu.is-visible') + + expect(page).to have_selector('.js-emoji-menu-search') + expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) + + fill_in('emoji-menu-search', with: 'hand') + + page.within('.emoji-menu-content') do + expect(page).to have_selector('[data-name="raised_hand"]') + end + end + + it 'adds an award emoji by a comment' do + page.within('.js-main-target-form') do + fill_in('note[note]', with: ':smile:') + + click_button('Comment') + end + + expect(page).to have_emoji('smile') + end + + context 'when a project is archived' do + let(:project) { create(:project, :archived) } + + it 'hides the add award button' do + page.within('.awards') do + expect(page).not_to have_css('.js-add-award') + end + end + end + + context 'User interacts with awards on a note' do + let!(:note) { create(:note, noteable: issue, project: issue.project) } + let!(:award_emoji) { create(:award_emoji, awardable: note, name: '100') } + + it 'shows the award on the note' do + page.within('.note-awards') do + expect(page).to have_emoji('100') + end + end + + it 'allows adding a vote to an award' do + page.within('.note-awards') do + find('gl-emoji[data-name="100"]').click + end + wait_for_requests + + expect(note.reload.award_emoji.size).to eq(2) + end + + it 'allows adding a new emoji' do + page.within('.note-actions') do + find('a.js-add-award').click + end + page.within('.emoji-menu-content') do + find('gl-emoji[data-name="8ball"]').click + end + wait_for_requests + + page.within('.note-awards') do + expect(page).to have_emoji('8ball') + end + expect(note.reload.award_emoji.size).to eq(2) + end + + context 'when the project is archived' do + let(:project) { create(:project, :archived) } + + it 'hides the buttons for adding new emoji' do + page.within('.note-awards') do + expect(page).not_to have_css('.award-menu-holder') + end + + page.within('.note-actions') do + expect(page).not_to have_css('a.js-add-award') + end + end + + it 'does not allow toggling existing emoji' do + page.within('.note-awards') do + find('gl-emoji[data-name="100"]').click + end + wait_for_requests + + expect(note.reload.award_emoji.size).to eq(1) + end + end + end + end + + describe 'User interacts with awards on an issue', :js do + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + + describe 'logged in' do + before do + sign_in(user) + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'adds award to issue' do + first('.js-emoji-btn').click + + expect(page).to have_selector('.js-emoji-btn.active') + expect(first('.js-emoji-btn')).to have_content '1' + + visit project_issue_path(project, issue) + + expect(first('.js-emoji-btn')).to have_content '1' + end + + it 'removes award from issue' do + first('.js-emoji-btn').click + find('.js-emoji-btn.active').click + + expect(first('.js-emoji-btn')).to have_content '0' + + visit project_issue_path(project, issue) + + expect(first('.js-emoji-btn')).to have_content '0' + end + + it 'only has one menu on the page' do + first('.js-add-award').click + + expect(page).to have_selector('.emoji-menu', count: 1) + end + end + + describe 'logged out' do + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + it 'does not see award menu button' do + expect(page).not_to have_selector('.js-award-holder') + end + end + end + + describe 'Awards Emoji' do + let!(:project) { create(:project, :public) } + let(:issue) { create(:issue, assignees: [user], project: project) } + + context 'authorized user' do + before do + project.add_maintainer(user) + sign_in(user) + end + + describe 'visiting an issue with a legacy award emoji that is not valid anymore' do + before do + # The `heart_tip` emoji is not valid anymore so we need to skip validation + issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false) + visit project_issue_path(project, issue) + wait_for_requests + end + + # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529 + it 'does not shows a 500 page', :js do + expect(page).to have_text(issue.title) + end + end + + describe 'Click award emoji from issue#show' do + let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") } + + before do + visit project_issue_path(project, issue) + wait_for_requests + end + + context 'click the thumbsdown emoji' do + it 'increments the thumbsdown emoji', :js do + find('[data-name="thumbsdown"]').click + wait_for_requests + expect(thumbsdown_emoji).to have_text("1") + end + + it 'decrements the thumbsup emoji', :js do + expect(thumbsup_emoji).to have_text("0") + end + end + + it 'toggles the smiley emoji on a note', :js do + toggle_smiley_emoji(true) + + within('.note-body') do + expect(find(emoji_counter)).to have_text("1") + end + + toggle_smiley_emoji(false) + + within('.note-body') do + expect(page).not_to have_selector(emoji_counter) + end + end + + context 'execute /award quick action' do + it 'toggles the emoji award on noteable', :js do + execute_quick_action('/award :100:') + + expect(find(noteable_award_counter)).to have_text("1") + + execute_quick_action('/award :100:') + + expect(page).not_to have_selector(noteable_award_counter) + end + end + end + end + + context 'unauthorized user', :js do + before do + visit project_issue_path(project, issue) + end + + it 'has disabled emoji button' do + expect(first('.award-control')[:class]).to have_text('disabled') + end + end + + def execute_quick_action(cmd) + within('.js-main-target-form') do + fill_in 'note[note]', with: cmd + click_button 'Comment' + end + + wait_for_requests + end + + def thumbsup_emoji + page.all(emoji_counter).first + end + + def thumbsdown_emoji + page.all(emoji_counter).last + end + + def emoji_counter + 'span.js-counter' + end + + def noteable_award_counter + ".awards .active" + end + + def toggle_smiley_emoji(status) + within('.note') do + find('.note-emoji-button').click + end + + if !status + first('[data-name="smiley"]').click + else + find('[data-name="smiley"]').click + end + + wait_for_requests + end + end +end diff --git a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb index c1608be402a..fd4175d5227 100644 --- a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb +++ b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb @@ -28,7 +28,7 @@ describe 'Merge request > User sees MR with deleted source branch', :js do click_on 'Changes' wait_for_requests - expect(page).to have_selector('.diffs.tab-pane .nothing-here-block') + expect(page).to have_selector('.diffs.tab-pane .file-holder') expect(page).to have_content('Source branch does not exist.') end end diff --git a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb b/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb deleted file mode 100644 index 4d860893abe..00000000000 --- a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb +++ /dev/null @@ -1,172 +0,0 @@ -require 'spec_helper' - -describe 'User interacts with awards in an issue', :js do - let(:issue) { create(:issue, project: project)} - let(:project) { create(:project) } - let(:user) { create(:user) } - - before do - project.add_maintainer(user) - sign_in(user) - - visit(project_issue_path(project, issue)) - end - - it 'toggles the thumbsup award emoji' do - page.within('.awards') do - thumbsup = page.first('.award-control') - thumbsup.click - thumbsup.hover - - expect(page).to have_selector('.js-emoji-btn') - expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") - expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') - - thumbsup = page.first('.award-control') - thumbsup.click - thumbsup.hover - - expect(page).to have_selector('.award-control.js-emoji-btn') - expect(page.all('.award-control.js-emoji-btn').size).to eq(2) - - page.all('.award-control.js-emoji-btn').each do |element| - expect(element['title']).to eq('') - end - - page.all('.award-control .js-counter').each do |element| - expect(element).to have_content('0') - end - - thumbsup = page.first('.award-control') - thumbsup.click - thumbsup.hover - - expect(page).to have_selector('.js-emoji-btn') - expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") - expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') - end - end - - it 'toggles a custom award emoji' do - page.within('.awards') do - page.find('.js-add-award').click - end - - page.find('.emoji-menu.is-visible') - - expect(page).to have_selector('.js-emoji-menu-search') - expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) - - page.within('.emoji-menu-content') do - emoji_button = page.first('.js-emoji-btn') - emoji_button.hover - emoji_button.click - end - - page.within('.awards') do - expect(page).to have_selector('.js-emoji-btn') - expect(page.find('.js-emoji-btn.active .js-counter')).to have_content('1') - expect(page).to have_css(".js-emoji-btn.active[data-original-title='You']") - - expect do - page.find('.js-emoji-btn.active').click - wait_for_requests - end.to change { page.all('.award-control.js-emoji-btn').size }.from(3).to(2) - end - end - - it 'shows the list of award emoji categories' do - page.within('.awards') do - page.find('.js-add-award').click - end - - page.find('.emoji-menu.is-visible') - - expect(page).to have_selector('.js-emoji-menu-search') - expect(page.evaluate_script("document.activeElement.classList.contains('js-emoji-menu-search')")).to eq(true) - - fill_in('emoji-menu-search', with: 'hand') - - page.within('.emoji-menu-content') do - expect(page).to have_selector('[data-name="raised_hand"]') - end - end - - it 'adds an award emoji by a comment' do - page.within('.js-main-target-form') do - fill_in('note[note]', with: ':smile:') - - click_button('Comment') - end - - expect(page).to have_emoji('smile') - end - - context 'when a project is archived' do - let(:project) { create(:project, :archived) } - - it 'hides the add award button' do - page.within('.awards') do - expect(page).not_to have_css('.js-add-award') - end - end - end - - context 'awards on a note' do - let!(:note) { create(:note, noteable: issue, project: issue.project) } - let!(:award_emoji) { create(:award_emoji, awardable: note, name: '100') } - - it 'shows the award on the note' do - page.within('.note-awards') do - expect(page).to have_emoji('100') - end - end - - it 'allows adding a vote to an award' do - page.within('.note-awards') do - find('gl-emoji[data-name="100"]').click - end - wait_for_requests - - expect(note.reload.award_emoji.size).to eq(2) - end - - it 'allows adding a new emoji' do - page.within('.note-actions') do - find('a.js-add-award').click - end - page.within('.emoji-menu-content') do - find('gl-emoji[data-name="8ball"]').click - end - wait_for_requests - - page.within('.note-awards') do - expect(page).to have_emoji('8ball') - end - expect(note.reload.award_emoji.size).to eq(2) - end - - context 'when the project is archived' do - let(:project) { create(:project, :archived) } - - it 'hides the buttons for adding new emoji' do - page.within('.note-awards') do - expect(page).not_to have_css('.award-menu-holder') - end - - page.within('.note-actions') do - expect(page).not_to have_css('a.js-add-award') - end - end - - it 'does not allow toggling existing emoji' do - page.within('.note-awards') do - find('gl-emoji[data-name="100"]').click - end - wait_for_requests - - expect(note.reload.award_emoji.size).to eq(1) - end - end - end -end diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb index aeed38aeb76..3925de6cfb9 100644 --- a/spec/features/projects/blobs/shortcuts_blob_spec.rb +++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Blob shortcuts' do +describe 'Blob shortcuts', :js do include TreeHelper let(:project) { create(:project, :public, :repository) } let(:path) { project.repository.ls_files(project.repository.root_ref)[0] } @@ -18,6 +18,7 @@ describe 'Blob shortcuts' do describe 'pressing "y"' do it 'redirects to permalink with commit sha' do visit_blob + wait_for_requests find('body').native.send_key('y') @@ -27,6 +28,7 @@ describe 'Blob shortcuts' do it 'maintains fragment hash when redirecting' do fragment = "L1" visit_blob(fragment) + wait_for_requests find('body').native.send_key('y') diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb index f56174fc85c..f3cf3a282e5 100644 --- a/spec/features/projects/files/user_browses_files_spec.rb +++ b/spec/features/projects/files/user_browses_files_spec.rb @@ -210,9 +210,10 @@ describe "User browses files" do end end - context "when browsing a file content" do + context "when browsing a file content", :js do before do visit(tree_path_root_ref) + wait_for_requests click_link(".gitignore") end diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb index 0e9f83a16ce..dcb7b947c61 100644 --- a/spec/features/projects/files/user_deletes_files_spec.rb +++ b/spec/features/projects/files/user_deletes_files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Projects > Files > User deletes files' do +describe 'Projects > Files > User deletes files', :js do let(:fork_message) do "You're not allowed to make changes to this project directly. "\ "A fork of this project has been created that you can make changes in, so you can submit a merge request." @@ -19,6 +19,7 @@ describe 'Projects > Files > User deletes files' do before do project.add_maintainer(user) visit(project_tree_path_root_ref) + wait_for_requests end it 'deletes the file', :js do @@ -35,10 +36,11 @@ describe 'Projects > Files > User deletes files' do end end - context 'when an user does not have write access' do + context 'when an user does not have write access', :js do before do project2.add_reporter(user) visit(project2_tree_path_root_ref) + wait_for_requests end it 'deletes the file in a forked project', :js do diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index ccc1bc1bc10..9eb65ec159c 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Projects > Files > User edits files' do +describe 'Projects > Files > User edits files', :js do include ProjectForksHelper let(:project) { create(:project, :repository, name: 'Shop') } let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } @@ -29,13 +29,14 @@ describe 'Projects > Files > User edits files' do end end - context 'when an user has write access' do + context 'when an user has write access', :js do before do project.add_maintainer(user) visit(project_tree_path_root_ref) + wait_for_requests end - it 'inserts a content of a file', :js do + it 'inserts a content of a file' do click_link('.gitignore') find('.js-edit-blob').click find('.file-editor', match: :first) @@ -49,13 +50,14 @@ describe 'Projects > Files > User edits files' do it 'does not show the edit link if a file is binary' do binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png') visit(project_blob_path(project, binary_file)) + wait_for_requests page.within '.content' do expect(page).not_to have_link('edit') end end - it 'commits an edited file', :js do + it 'commits an edited file' do click_link('.gitignore') find('.js-edit-blob').click find('.file-editor', match: :first) @@ -72,7 +74,7 @@ describe 'Projects > Files > User edits files' do expect(page).to have_content('*.rbca') end - it 'commits an edited file to a new branch', :js do + it 'commits an edited file to a new branch' do click_link('.gitignore') find('.js-edit-blob').click @@ -91,7 +93,7 @@ describe 'Projects > Files > User edits files' do expect(page).to have_content('*.rbca') end - it 'shows the diff of an edited file', :js do + it 'shows the diff of an edited file' do click_link('.gitignore') find('.js-edit-blob').click find('.file-editor', match: :first) @@ -106,13 +108,14 @@ describe 'Projects > Files > User edits files' do it_behaves_like 'unavailable for an archived project' end - context 'when an user does not have write access' do + context 'when an user does not have write access', :js do before do project2.add_reporter(user) visit(project2_tree_path_root_ref) + wait_for_requests end - it 'inserts a content of a file in a forked project', :js do + it 'inserts a content of a file in a forked project' do click_link('.gitignore') find('.js-edit-blob').click @@ -134,7 +137,7 @@ describe 'Projects > Files > User edits files' do expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca') end - it 'commits an edited file in a forked project', :js do + it 'commits an edited file in a forked project' do click_link('.gitignore') find('.js-edit-blob').click @@ -163,6 +166,7 @@ describe 'Projects > Files > User edits files' do let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) } before do visit(project2_tree_path_root_ref) + wait_for_requests end it 'links to the forked project for editing' do diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb index 3a81e77c4ba..e3da28d73c3 100644 --- a/spec/features/projects/files/user_replaces_files_spec.rb +++ b/spec/features/projects/files/user_replaces_files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Projects > Files > User replaces files' do +describe 'Projects > Files > User replaces files', :js do include DropzoneHelper let(:fork_message) do @@ -21,9 +21,10 @@ describe 'Projects > Files > User replaces files' do before do project.add_maintainer(user) visit(project_tree_path_root_ref) + wait_for_requests end - it 'replaces an existed file with a new one', :js do + it 'replaces an existed file with a new one' do click_link('.gitignore') expect(page).to have_content('.gitignore') @@ -47,9 +48,10 @@ describe 'Projects > Files > User replaces files' do before do project2.add_reporter(user) visit(project2_tree_path_root_ref) + wait_for_requests end - it 'replaces an existed file with a new one in a forked project', :js do + it 'replaces an existed file with a new one in a forked project' do click_link('.gitignore') expect(page).to have_content('.gitignore') diff --git a/spec/features/projects/remote_mirror_spec.rb b/spec/features/projects/remote_mirror_spec.rb index 5259a8942dc..33e9b73efe8 100644 --- a/spec/features/projects/remote_mirror_spec.rb +++ b/spec/features/projects/remote_mirror_spec.rb @@ -17,7 +17,7 @@ describe 'Project remote mirror', :feature do visit project_mirror_path(project) - expect(page).to have_content('The remote repository failed to update.') + expect_mirror_to_have_error_and_timeago('Never') end end @@ -27,8 +27,14 @@ describe 'Project remote mirror', :feature do visit project_mirror_path(project) - expect(page).to have_content('The remote repository failed to update 5 minutes ago.') + expect_mirror_to_have_error_and_timeago('5 minutes ago') end end + + def expect_mirror_to_have_error_and_timeago(timeago) + row = first('.js-mirrors-table-body tr') + expect(row).to have_content('Error') + expect(row).to have_content(timeago) + end end end diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index a0f5b234ebc..377a75cbcb3 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -129,9 +129,8 @@ describe 'Projects > Settings > Repository settings' do visit project_settings_repository_path(project) end - it 'shows push mirror settings' do - expect(page).to have_selector('#project_remote_mirrors_attributes_0_enabled') - expect(page).to have_selector('#project_remote_mirrors_attributes_0_url') + it 'shows push mirror settings', :js do + expect(page).to have_selector('#mirror_direction') end end end diff --git a/spec/finders/license_template_finder_spec.rb b/spec/finders/license_template_finder_spec.rb new file mode 100644 index 00000000000..a97903103c9 --- /dev/null +++ b/spec/finders/license_template_finder_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe LicenseTemplateFinder do + describe '#execute' do + subject(:result) { described_class.new(params).execute } + + let(:categories) { categorised_licenses.keys } + let(:categorised_licenses) { result.group_by(&:category) } + + context 'popular: true' do + let(:params) { { popular: true } } + + it 'only returns popular licenses' do + expect(categories).to contain_exactly(:Popular) + expect(categorised_licenses[:Popular]).to be_present + end + end + + context 'popular: false' do + let(:params) { { popular: false } } + + it 'only returns unpopular licenses' do + expect(categories).to contain_exactly(:Other) + expect(categorised_licenses[:Other]).to be_present + end + end + + context 'popular: nil' do + let(:params) { { popular: nil } } + + it 'returns all licenses known by the Licensee gem' do + from_licensee = Licensee::License.all.map { |l| l.key } + + expect(result.map(&:id)).to match_array(from_licensee) + end + + it 'correctly copies all attributes' do + licensee = Licensee::License.all.first + found = result.find { |r| r.key == licensee.key } + + aggregate_failures do + %i[key name content nickname url meta featured?].each do |k| + expect(found.public_send(k)).to eq(licensee.public_send(k)) + end + end + end + end + end +end diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index 5856bccb5b8..55ee87163f9 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -5,12 +5,67 @@ describe AvatarsHelper do let(:user) { create(:user) } - describe '#project_icon' do - it 'returns an url for the avatar' do - project = create(:project, :public, avatar: File.open(uploaded_image_temp_path)) + describe '#project_icon & #group_icon' do + shared_examples 'resource with a default avatar' do |source_type| + it 'returns a default avatar div' do + expect(public_send("#{source_type}_icon", *helper_args)) + .to match(%r{<div class="identicon bg\d+">F</div>}) + end + end + + shared_examples 'resource with a custom avatar' do |source_type| + it 'returns a custom avatar image' do + expect(public_send("#{source_type}_icon", *helper_args)) + .to eq "<img src=\"#{resource.avatar.url}\" alt=\"Banana sample\" />" + end + end + + context 'when providing a project' do + it_behaves_like 'resource with a default avatar', 'project' do + let(:resource) { create(:project, name: 'foo') } + let(:helper_args) { [resource] } + end + + it_behaves_like 'resource with a custom avatar', 'project' do + let(:resource) { create(:project, :public, avatar: File.open(uploaded_image_temp_path)) } + let(:helper_args) { [resource] } + end + end + + context 'when providing a project path' do + it_behaves_like 'resource with a default avatar', 'project' do + let(:resource) { create(:project, name: 'foo') } + let(:helper_args) { [resource.full_path] } + end - expect(helper.project_icon(project.full_path).to_s) - .to eq "<img data-src=\"#{project.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" + it_behaves_like 'resource with a custom avatar', 'project' do + let(:resource) { create(:project, :public, avatar: File.open(uploaded_image_temp_path)) } + let(:helper_args) { [resource.full_path] } + end + end + + context 'when providing a group' do + it_behaves_like 'resource with a default avatar', 'group' do + let(:resource) { create(:group, name: 'foo') } + let(:helper_args) { [resource] } + end + + it_behaves_like 'resource with a custom avatar', 'group' do + let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) } + let(:helper_args) { [resource] } + end + end + + context 'when providing a group path' do + it_behaves_like 'resource with a default avatar', 'group' do + let(:resource) { create(:group, name: 'foo') } + let(:helper_args) { [resource.full_path] } + end + + it_behaves_like 'resource with a custom avatar', 'group' do + let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) } + let(:helper_args) { [resource.full_path] } + end end end diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index 630f3eff258..0c0a0003231 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -79,6 +79,18 @@ describe ButtonHelper do end end + context 'without an ssh key on the user and user_show_add_ssh_key_message unset' do + before do + stub_application_setting(user_show_add_ssh_key_message: false) + end + + it 'there is no warning on the dropdown description' do + description = element.search('.dropdown-menu-inner-content').first + + expect(description).to be_nil + end + end + context 'with an ssh key on the user' do before do create(:key, user: user) diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 115807f954b..540a8674ec2 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -3,19 +3,6 @@ require 'spec_helper' describe GroupsHelper do include ApplicationHelper - describe 'group_icon' do - it 'returns an url for the avatar' do - avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif') - - group = create(:group) - group.avatar = fixture_file_upload(avatar_file_path) - group.save! - - expect(helper.group_icon(group).to_s) - .to eq "<img data-src=\"#{group.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" - end - end - describe 'group_icon_url' do it 'returns an url for the avatar' do avatar_file_path = File.join('spec', 'fixtures', 'banana_sample.gif') diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb index 82f588d1a08..4b40d523287 100644 --- a/spec/helpers/icons_helper_spec.rb +++ b/spec/helpers/icons_helper_spec.rb @@ -80,6 +80,26 @@ describe IconsHelper do end end + describe 'audit icon' do + it 'returns right icon name for standard auth' do + icon_name = 'standard' + expect(audit_icon(icon_name).to_s) + .to eq '<i class="fa fa-key"></i>' + end + + it 'returns right icon name for two-factor auth' do + icon_name = 'two-factor' + expect(audit_icon(icon_name).to_s) + .to eq '<i class="fa fa-key"></i>' + end + + it 'returns right icon name for google_oauth2 auth' do + icon_name = 'google_oauth2' + expect(audit_icon(icon_name).to_s) + .to eq '<i class="fa fa-google"></i>' + end + end + describe 'file_type_icon_class' do it 'returns folder class' do expect(file_type_icon_class('folder', 0, 'folder_name')).to eq 'folder' diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js index 7a4616ec8eb..44a38f7ca82 100644 --- a/spec/javascripts/diffs/components/diff_file_spec.js +++ b/spec/javascripts/diffs/components/diff_file_spec.js @@ -22,11 +22,18 @@ describe('DiffFile', () => { expect(el.id).toEqual(fileHash); expect(el.classList.contains('diff-file')).toEqual(true); + expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0); expect(el.querySelector('.js-file-title')).toBeDefined(); expect(el.querySelector('.file-title-name').innerText.indexOf(filePath) > -1).toEqual(true); expect(el.querySelector('.js-syntax-highlight')).toBeDefined(); - expect(el.querySelectorAll('.line_content').length > 5).toEqual(true); + + expect(vm.file.renderIt).toEqual(false); + vm.file.renderIt = true; + + vm.$nextTick(() => { + expect(el.querySelectorAll('.line_content').length > 5).toEqual(true); + }); }); describe('collapsed', () => { @@ -34,6 +41,7 @@ describe('DiffFile', () => { expect(vm.$el.querySelectorAll('.diff-content').length).toEqual(1); expect(vm.file.collapsed).toEqual(false); vm.file.collapsed = true; + vm.file.renderIt = true; vm.$nextTick(() => { expect(vm.$el.querySelectorAll('.diff-content').length).toEqual(0); diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js index d3bf9525924..cce36ecc91f 100644 --- a/spec/javascripts/diffs/mock_data/diff_file.js +++ b/spec/javascripts/diffs/mock_data/diff_file.js @@ -39,6 +39,7 @@ export default { viewPath: '/gitlab-org/gitlab-test/blob/spooky-stuff/CHANGELOG', replacedViewPath: null, collapsed: false, + renderIt: false, tooLarge: false, contextLinesPath: '/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff', diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index 1af49f4985c..8f89984c6e5 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -1,6 +1,7 @@ import mutations from '~/diffs/store/mutations'; import * as types from '~/diffs/store/mutation_types'; import { INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants'; +import diffFileMockData from '../mock_data/diff_file'; describe('DiffsStoreMutations', () => { describe('SET_BASE_CONFIG', () => { @@ -24,6 +25,23 @@ describe('DiffsStoreMutations', () => { }); }); + describe('SET_DIFF_DATA', () => { + it('should set diff data type properly', () => { + const state = {}; + const diffMock = { + diff_files: [diffFileMockData], + }; + + mutations[types.SET_DIFF_DATA](state, diffMock); + + const firstLine = state.diffFiles[0].parallelDiffLines[0]; + + expect(firstLine.right.text).toBeUndefined(); + expect(state.diffFiles[0].renderIt).toEqual(true); + expect(state.diffFiles[0].collapsed).toEqual(false); + }); + }); + describe('SET_DIFF_VIEW_TYPE', () => { it('should set diff view type properly', () => { const state = {}; diff --git a/spec/javascripts/ide/components/new_dropdown/button_spec.js b/spec/javascripts/ide/components/new_dropdown/button_spec.js index ef083d06ba7..6a326b5bd92 100644 --- a/spec/javascripts/ide/components/new_dropdown/button_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/button_spec.js @@ -46,4 +46,20 @@ describe('IDE new entry dropdown button component', () => { done(); }); }); + + describe('tooltipTitle', () => { + it('returns empty string when showLabel is true', () => { + expect(vm.tooltipTitle).toBe(''); + }); + + it('returns label', done => { + vm.showLabel = false; + + vm.$nextTick(() => { + expect(vm.tooltipTitle).toBe('Testing'); + + done(); + }); + }); + }); }); diff --git a/spec/javascripts/ide/components/preview/clientside_spec.js b/spec/javascripts/ide/components/preview/clientside_spec.js index 3ec65882418..d6983f5a3b8 100644 --- a/spec/javascripts/ide/components/preview/clientside_spec.js +++ b/spec/javascripts/ide/components/preview/clientside_spec.js @@ -292,6 +292,8 @@ describe('IDE clientside preview', () => { describe('update', () => { beforeEach(() => { jasmine.clock().install(); + + vm.sandpackReady = true; vm.manager.updatePreview = jasmine.createSpy('updatePreview'); vm.manager.listener = jasmine.createSpy('updatePreview'); }); @@ -306,7 +308,7 @@ describe('IDE clientside preview', () => { vm.update(); - jasmine.clock().tick(500); + jasmine.clock().tick(250); expect(vm.initPreview).toHaveBeenCalled(); }); @@ -314,7 +316,7 @@ describe('IDE clientside preview', () => { it('calls updatePreview', () => { vm.update(); - jasmine.clock().tick(500); + jasmine.clock().tick(250); expect(vm.manager.updatePreview).toHaveBeenCalledWith(vm.sandboxOpts); }); diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js index 90c28c769f7..8564f04ce8a 100644 --- a/spec/javascripts/ide/stores/actions/merge_request_spec.js +++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js @@ -1,12 +1,14 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; -import { +import actions, { getMergeRequestData, getMergeRequestChanges, getMergeRequestVersions, + openMergeRequest, } from '~/ide/stores/actions/merge_request'; import service from '~/ide/services'; +import { activityBarViews } from '~/ide/constants'; import { resetStore } from '../../helpers'; describe('IDE store merge request actions', () => { @@ -238,4 +240,101 @@ describe('IDE store merge request actions', () => { }); }); }); + + describe('openMergeRequest', () => { + const mr = { + projectId: 'abcproject', + targetProjectId: 'defproject', + mergeRequestId: 2, + }; + let testMergeRequest; + let testMergeRequestChanges; + + beforeEach(() => { + testMergeRequest = { + source_branch: 'abcbranch', + }; + testMergeRequestChanges = { + changes: [], + }; + store.state.entries = { + foo: {}, + bar: {}, + }; + + spyOn(store, 'dispatch').and.callFake((type) => { + switch (type) { + case 'getMergeRequestData': + return Promise.resolve(testMergeRequest); + case 'getMergeRequestChanges': + return Promise.resolve(testMergeRequestChanges); + default: + return Promise.resolve(); + } + }); + }); + + it('dispatch actions for merge request data', done => { + openMergeRequest(store, mr) + .then(() => { + expect(store.dispatch.calls.allArgs()).toEqual([ + ['getMergeRequestData', mr], + ['setCurrentBranchId', testMergeRequest.source_branch], + ['getBranchData', { + projectId: mr.projectId, + branchId: testMergeRequest.source_branch, + }], + ['getFiles', { + projectId: mr.projectId, + branchId: testMergeRequest.source_branch, + }], + ['getMergeRequestVersions', mr], + ['getMergeRequestChanges', mr], + ]); + }) + .then(done) + .catch(done.fail); + }); + + it('updates activity bar view and gets file data, if changes are found', done => { + testMergeRequestChanges.changes = [ + { new_path: 'foo' }, + { new_path: 'bar' }, + ]; + + openMergeRequest(store, mr) + .then(() => { + expect(store.dispatch).toHaveBeenCalledWith('updateActivityBarView', activityBarViews.review); + + testMergeRequestChanges.changes.forEach((change, i) => { + expect(store.dispatch).toHaveBeenCalledWith('setFileMrChange', { + file: store.state.entries[change.new_path], + mrChange: change, + }); + expect(store.dispatch).toHaveBeenCalledWith('getFileData', { + path: change.new_path, + makeFileActive: i === 0, + }); + }); + }) + .then(done) + .catch(done.fail); + }); + + it('flashes message, if error', done => { + const flashSpy = spyOnDependency(actions, 'flash'); + store.dispatch.and.returnValue(Promise.reject()); + + openMergeRequest(store, mr) + .then(() => { + fail('Expected openMergeRequest to throw an error'); + }) + .catch(() => { + expect(flashSpy).toHaveBeenCalledWith(jasmine.any(String)); + }) + .then(done) + .catch(done.fail); + + }); + }); }); diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index 6a85968e199..667e3e0a7ef 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -5,6 +5,7 @@ import { showBranchNotFoundError, createNewBranchFromDefault, getBranchData, + openBranch, } from '~/ide/stores/actions'; import store from '~/ide/stores'; import service from '~/ide/services'; @@ -224,4 +225,55 @@ describe('IDE store project actions', () => { }); }); }); + + describe('openBranch', () => { + const branch = { + projectId: 'feature/lorem-ipsum', + branchId: '123-lorem', + }; + + beforeEach(() => { + store.state.entries = { + foo: { pending: false }, + 'foo/bar-pending': { pending: true }, + 'foo/bar': { pending: false }, + }; + + spyOn(store, 'dispatch').and.returnValue(Promise.resolve()); + }); + + it('dispatches branch actions', done => { + openBranch(store, branch) + .then(() => { + expect(store.dispatch.calls.allArgs()).toEqual([ + ['setCurrentBranchId', branch.branchId], + ['getBranchData', branch], + ['getFiles', branch], + ]); + }) + .then(done) + .catch(done.fail); + }); + + it('handles tree entry action, if basePath is given', done => { + openBranch(store, { ...branch, basePath: 'foo/bar/' }) + .then(() => { + expect(store.dispatch).toHaveBeenCalledWith( + 'handleTreeEntryAction', + store.state.entries['foo/bar'], + ); + }) + .then(done) + .catch(done.fail); + }); + + it('does not handle tree entry action, if entry is pending', done => { + openBranch(store, { ...branch, basePath: 'foo/bar-pending' }) + .then(() => { + expect(store.dispatch).not.toHaveBeenCalledWith('handleTreeEntryAction', jasmine.anything()); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/ide/stores/modules/branches/actions_spec.js b/spec/javascripts/ide/stores/modules/branches/actions_spec.js index a0fce578958..010f56af03b 100644 --- a/spec/javascripts/ide/stores/modules/branches/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/branches/actions_spec.js @@ -9,7 +9,6 @@ import { receiveBranchesSuccess, fetchBranches, resetBranches, - openBranch, } from '~/ide/stores/modules/branches/actions'; import { branches, projectData } from '../../../mock_data'; @@ -174,20 +173,5 @@ describe('IDE branches actions', () => { ); }); }); - - describe('openBranch', () => { - it('dispatches goToRoute action with path', done => { - const branchId = branches[0].name; - const expectedPath = `/project/${projectData.name_with_namespace}/edit/${branchId}`; - testAction( - openBranch, - branchId, - mockedState, - [], - [{ type: 'goToRoute', payload: expectedPath }], - done, - ); - }); - }); }); }); diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js index 1e836dbc3f9..6ce76aaa03b 100644 --- a/spec/javascripts/ide/stores/mutations_spec.js +++ b/spec/javascripts/ide/stores/mutations_spec.js @@ -213,6 +213,33 @@ describe('Multi-file store mutations', () => { expect(localState.changedFiles).toEqual([localState.entries.filePath]); }); + + it('does not add tempFile into changedFiles', () => { + localState.entries.filePath = { + deleted: false, + type: 'blob', + tempFile: true, + }; + + mutations.DELETE_ENTRY(localState, 'filePath'); + + expect(localState.changedFiles).toEqual([]); + }); + + it('removes tempFile from changedFiles when deleted', () => { + localState.entries.filePath = { + path: 'filePath', + deleted: false, + type: 'blob', + tempFile: true, + }; + + localState.changedFiles.push({ ...localState.entries.filePath }); + + mutations.DELETE_ENTRY(localState, 'filePath'); + + expect(localState.changedFiles).toEqual([]); + }); }); describe('UPDATE_FILE_AFTER_COMMIT', () => { diff --git a/spec/javascripts/jobs/artifacts_block_spec.js b/spec/javascripts/jobs/artifacts_block_spec.js new file mode 100644 index 00000000000..c544c6f3e89 --- /dev/null +++ b/spec/javascripts/jobs/artifacts_block_spec.js @@ -0,0 +1,120 @@ +import Vue from 'vue'; +import { getTimeago } from '~/lib/utils/datetime_utility'; +import component from '~/jobs/components/artifacts_block.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Artifacts block', () => { + const Component = Vue.extend(component); + let vm; + + const expireAt = '2018-08-14T09:38:49.157Z'; + const timeago = getTimeago(); + const formatedDate = timeago.format(expireAt); + + afterEach(() => { + vm.$destroy(); + }); + + describe('with expired artifacts', () => { + it('renders expired artifact date and info', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + }); + + expect(vm.$el.querySelector('.js-artifacts-removed')).not.toBeNull(); + expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).toBeNull(); + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); + + describe('with artifacts that will expire', () => { + it('renders will expire artifact date and info', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: false, + willArtifactsExpire: true, + expireAt, + }); + + expect(vm.$el.querySelector('.js-artifacts-removed')).toBeNull(); + expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).not.toBeNull(); + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); + + describe('when the user can keep the artifacts', () => { + it('renders the keep button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + keepArtifactsPath: '/keep', + }); + + expect(vm.$el.querySelector('.js-keep-artifacts')).not.toBeNull(); + }); + }); + + describe('when the user can not keep the artifacts', () => { + it('does not render the keep button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + }); + + expect(vm.$el.querySelector('.js-keep-artifacts')).toBeNull(); + }); + }); + + describe('when the user can download the artifacts', () => { + it('renders the download button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + downloadArtifactsPath: '/download', + }); + + expect(vm.$el.querySelector('.js-download-artifacts')).not.toBeNull(); + }); + }); + + describe('when the user can not download the artifacts', () => { + it('does not render the keep button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + }); + + expect(vm.$el.querySelector('.js-download-artifacts')).toBeNull(); + }); + }); + + describe('when the user can browse the artifacts', () => { + it('does not render the browse button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + browseArtifactsPath: '/browse', + }); + + expect(vm.$el.querySelector('.js-browse-artifacts')).not.toBeNull(); + }); + }); + + describe('when the user can not browse the artifacts', () => { + it('does not render the browse button', () => { + vm = mountComponent(Component, { + haveArtifactsExpired: true, + willArtifactsExpire: false, + expireAt, + }); + + expect(vm.$el.querySelector('.js-browse-artifacts')).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/jobs/erased_block_spec.js b/spec/javascripts/jobs/erased_block_spec.js new file mode 100644 index 00000000000..7cf32e984a2 --- /dev/null +++ b/spec/javascripts/jobs/erased_block_spec.js @@ -0,0 +1,56 @@ +import Vue from 'vue'; +import { getTimeago } from '~/lib/utils/datetime_utility'; +import component from '~/jobs/components/erased_block.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Erased block', () => { + const Component = Vue.extend(component); + let vm; + + const erasedAt = '2016-11-07T11:11:16.525Z'; + const timeago = getTimeago(); + const formatedDate = timeago.format(erasedAt); + + afterEach(() => { + vm.$destroy(); + }); + + describe('with job erased by user', () => { + beforeEach(() => { + vm = mountComponent(Component, { + erasedByUser: true, + username: 'root', + linkToUser: 'gitlab.com/root', + erasedAt, + }); + }); + + it('renders username and link', () => { + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual('gitlab.com/root'); + + expect(vm.$el.textContent).toContain('Job has been erased by'); + expect(vm.$el.textContent).toContain('root'); + }); + + it('renders erasedAt', () => { + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); + + describe('with erased job', () => { + beforeEach(() => { + vm = mountComponent(Component, { + erasedByUser: false, + erasedAt, + }); + }); + + it('renders username and link', () => { + expect(vm.$el.textContent).toContain('Job has been erased'); + }); + + it('renders erasedAt', () => { + expect(vm.$el.textContent).toContain(formatedDate); + }); + }); +}); diff --git a/spec/javascripts/jobs/job_log_spec.js b/spec/javascripts/jobs/job_log_spec.js new file mode 100644 index 00000000000..406f1c4ccc5 --- /dev/null +++ b/spec/javascripts/jobs/job_log_spec.js @@ -0,0 +1,45 @@ +import Vue from 'vue'; +import component from '~/jobs/components/job_log.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Job Log', () => { + const Component = Vue.extend(component); + let vm; + + const trace = 'Running with gitlab-runner 11.1.0 (081978aa)<br> on docker-auto-scale-com d5ae8d25<br>Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29 ...<br>'; + + afterEach(() => { + vm.$destroy(); + }); + + it('renders provided trace', () => { + vm = mountComponent(Component, { + trace, + isReceivingBuildTrace: true, + }); + + expect(vm.$el.querySelector('code').textContent).toContain('Running with gitlab-runner 11.1.0 (081978aa)'); + }); + + describe('while receiving trace', () => { + it('renders animation', () => { + vm = mountComponent(Component, { + trace, + isReceivingBuildTrace: true, + }); + + expect(vm.$el.querySelector('.js-log-animation')).not.toBeNull(); + }); + }); + + describe('when build trace has finishes', () => { + it('does not render animation', () => { + vm = mountComponent(Component, { + trace, + isReceivingBuildTrace: false, + }); + + expect(vm.$el.querySelector('.js-log-animation')).toBeNull(); + }); + }); +}); diff --git a/spec/javascripts/jobs/sidebar_details_block_spec.js b/spec/javascripts/jobs/sidebar_details_block_spec.js index 9c4454252ce..21ef5650b80 100644 --- a/spec/javascripts/jobs/sidebar_details_block_spec.js +++ b/spec/javascripts/jobs/sidebar_details_block_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import sidebarDetailsBlock from '~/jobs/components/sidebar_details_block.vue'; import job from './mock_data'; +import mountComponent from '../helpers/vue_mount_component_helper'; describe('Sidebar details block', () => { let SidebarComponent; @@ -20,39 +21,53 @@ describe('Sidebar details block', () => { describe('when it is loading', () => { it('should render a loading spinner', () => { - vm = new SidebarComponent({ - propsData: { - job: {}, - isLoading: true, - }, - }).$mount(); - + vm = mountComponent(SidebarComponent, { + job: {}, + isLoading: true, + }); expect(vm.$el.querySelector('.fa-spinner')).toBeDefined(); }); }); - describe("when user can't retry", () => { + describe('when there is no retry path retry', () => { it('should not render a retry button', () => { - vm = new SidebarComponent({ - propsData: { - job: {}, - canUserRetry: false, - isLoading: true, - }, - }).$mount(); + vm = mountComponent(SidebarComponent, { + job: {}, + isLoading: false, + }); expect(vm.$el.querySelector('.js-retry-job')).toBeNull(); }); }); - beforeEach(() => { - vm = new SidebarComponent({ - propsData: { + describe('without terminal path', () => { + it('does not render terminal link', () => { + vm = mountComponent(SidebarComponent, { job, - canUserRetry: true, isLoading: false, - }, - }).$mount(); + }); + + expect(vm.$el.querySelector('.js-terminal-link')).toBeNull(); + }); + }); + + describe('with terminal path', () => { + it('renders terminal link', () => { + vm = mountComponent(SidebarComponent, { + job, + isLoading: false, + terminalPath: 'job/43123/terminal', + }); + + expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull(); + }); + }); + + beforeEach(() => { + vm = mountComponent(SidebarComponent, { + job, + isLoading: false, + }); }); describe('actions', () => { @@ -102,13 +117,15 @@ describe('Sidebar details block', () => { }); it('should render runner ID', () => { - expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual('Runner: local ci runner (#1)'); + expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual( + 'Runner: local ci runner (#1)', + ); }); it('should render timeout information', () => { - expect( - trimWhitespace(vm.$el.querySelector('.js-job-timeout')), - ).toEqual('Timeout: 1m 40s (from runner)'); + expect(trimWhitespace(vm.$el.querySelector('.js-job-timeout'))).toEqual( + 'Timeout: 1m 40s (from runner)', + ); }); it('should render coverage', () => { diff --git a/spec/javascripts/jobs/stuck_block_spec.js b/spec/javascripts/jobs/stuck_block_spec.js new file mode 100644 index 00000000000..4e2108dfdfb --- /dev/null +++ b/spec/javascripts/jobs/stuck_block_spec.js @@ -0,0 +1,81 @@ +import Vue from 'vue'; +import component from '~/jobs/components/stuck_block.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Stuck Block Job component', () => { + const Component = Vue.extend(component); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('with no runners for project', () => { + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: true, + runnersPath: '/root/project/runners#js-runners-settings', + }); + }); + + it('renders only information about project not having runners', () => { + expect(vm.$el.querySelector('.js-stuck-no-runners')).not.toBeNull(); + expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull(); + }); + + it('renders link to runners page', () => { + expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( + '/root/project/runners#js-runners-settings', + ); + }); + }); + + describe('with tags', () => { + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: false, + tags: ['docker', 'gitlab-org'], + runnersPath: '/root/project/runners#js-runners-settings', + }); + }); + + it('renders information about the tags not being set', () => { + expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-with-tags')).not.toBeNull(); + expect(vm.$el.querySelector('.js-stuck-no-active-runner')).toBeNull(); + }); + + it('renders tags', () => { + expect(vm.$el.textContent).toContain('docker'); + expect(vm.$el.textContent).toContain('gitlab-org'); + }); + + it('renders link to runners page', () => { + expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( + '/root/project/runners#js-runners-settings', + ); + }); + }); + + describe('without active runners', () => { + beforeEach(() => { + vm = mountComponent(Component, { + hasNoRunnersForProject: false, + runnersPath: '/root/project/runners#js-runners-settings', + }); + }); + + it('renders information about project not having runners', () => { + expect(vm.$el.querySelector('.js-stuck-no-runners')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-with-tags')).toBeNull(); + expect(vm.$el.querySelector('.js-stuck-no-active-runner')).not.toBeNull(); + }); + + it('renders link to runners page', () => { + expect(vm.$el.querySelector('.js-runners-path').getAttribute('href')).toEqual( + '/root/project/runners#js-runners-settings', + ); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js b/spec/javascripts/reports/components/modal_open_name_spec.js index 8635203c413..b18b3ef03d1 100644 --- a/spec/javascripts/vue_shared/components/reports/modal_open_name_spec.js +++ b/spec/javascripts/reports/components/modal_open_name_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import Vuex from 'vuex'; -import component from '~/vue_shared/components/reports/modal_open_name.vue'; +import component from '~/reports/components/modal_open_name.vue'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; describe('Modal open name', () => { diff --git a/spec/javascripts/vue_shared/components/reports/report_link_spec.js b/spec/javascripts/reports/components/report_link_spec.js index a4691f3712f..cd6911e2f59 100644 --- a/spec/javascripts/vue_shared/components/reports/report_link_spec.js +++ b/spec/javascripts/reports/components/report_link_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import component from '~/vue_shared/components/reports/report_link.vue'; -import mountComponent from '../../../helpers/vue_mount_component_helper'; +import component from '~/reports/components/report_link.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('report link', () => { let vm; diff --git a/spec/javascripts/vue_shared/components/reports/report_section_spec.js b/spec/javascripts/reports/components/report_section_spec.js index 4e3986acb16..6f6eb161d14 100644 --- a/spec/javascripts/vue_shared/components/reports/report_section_spec.js +++ b/spec/javascripts/reports/components/report_section_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import reportSection from '~/vue_shared/components/reports/report_section.vue'; +import reportSection from '~/reports/components/report_section.vue'; import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper'; describe('Report section', () => { diff --git a/spec/javascripts/vue_shared/components/reports/summary_row_spec.js b/spec/javascripts/reports/components/summary_row_spec.js index ac076f05bc0..fab7693581c 100644 --- a/spec/javascripts/vue_shared/components/reports/summary_row_spec.js +++ b/spec/javascripts/reports/components/summary_row_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import component from '~/vue_shared/components/reports/summary_row.vue'; +import component from '~/reports/components/summary_row.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('Summary row', () => { diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb new file mode 100644 index 00000000000..48140305e26 --- /dev/null +++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Filter::ProjectReferenceFilter do + include FilterSpecHelper + + def invalidate_reference(reference) + "#{reference.reverse}" + end + + def get_reference(project) + project.to_reference_with_postfix + end + + let(:project) { create(:project, :public) } + subject { project } + let(:subject_name) { "project" } + let(:reference) { get_reference(project) } + + it_behaves_like 'user reference or project reference' + + it 'ignores invalid projects' do + exp = act = "Hey #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp)) + end + + it 'allows references with text after the > character' do + doc = reference_filter("Hey #{reference}foo") + expect(doc.css('a').first.attr('href')).to eq urls.project_url(subject) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Hey #{CGI.escapeHTML(reference)}</#{elem}>" + expect(reference_filter(act).to_html).to eq exp + end + end + + it 'includes default classes' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project has-tooltip' + end + + context 'in group context' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + + let(:nested_group) { create(:group, :nested) } + let(:nested_project) { create(:project, group: nested_group) } + + it 'supports mentioning a project' do + reference = get_reference(project) + doc = reference_filter("Hey #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.project_url(project) + end + + it 'supports mentioning a project in a nested group' do + reference = get_reference(nested_project) + doc = reference_filter("Hey #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.project_url(nested_project) + end + end + + describe '#projects_hash' do + it 'returns a Hash containing all Projects' do + document = Nokogiri::HTML.fragment("<p>#{get_reference(project)}</p>") + filter = described_class.new(document, project: project) + + expect(filter.projects_hash).to eq({ project.full_path => project }) + end + end + + describe '#projects' do + it 'returns the projects mentioned in a document' do + document = Nokogiri::HTML.fragment("<p>#{get_reference(project)}</p>") + filter = described_class.new(document, project: project) + + expect(filter.projects).to eq([project.full_path]) + end + end +end diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 2f86a046d28..334d29a5368 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -3,9 +3,17 @@ require 'spec_helper' describe Banzai::Filter::UserReferenceFilter do include FilterSpecHelper + def get_reference(user) + user.to_reference + end + let(:project) { create(:project, :public) } let(:user) { create(:user) } - let(:reference) { user.to_reference } + subject { user } + let(:subject_name) { "user" } + let(:reference) { get_reference(user) } + + it_behaves_like 'user reference or project reference' it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) @@ -66,45 +74,6 @@ describe Banzai::Filter::UserReferenceFilter do end end - context 'mentioning a user' do - it_behaves_like 'a reference containing an element node' - - it 'links to a User' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) - end - - it 'links to a User with a period' do - user = create(:user, name: 'alphA.Beta') - - doc = reference_filter("Hey #{user.to_reference}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a User with an underscore' do - user = create(:user, name: 'ping_pong_king') - - doc = reference_filter("Hey #{user.to_reference}") - expect(doc.css('a').length).to eq 1 - end - - it 'links to a User with different case-sensitivity' do - user = create(:user, username: 'RescueRanger') - - doc = reference_filter("Hey #{user.to_reference.upcase}") - expect(doc.css('a').length).to eq 1 - expect(doc.css('a').text).to eq(user.to_reference) - end - - it 'includes a data-user attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-user') - expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s - end - end - context 'mentioning a group' do it_behaves_like 'a reference containing an element node' @@ -154,36 +123,6 @@ describe Banzai::Filter::UserReferenceFilter do expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip' end - it 'supports an :only_path context' do - doc = reference_filter("Hey #{reference}", only_path: true) - link = doc.css('a').first.attr('href') - - expect(link).not_to match %r(https?://) - expect(link).to eq urls.user_path(user) - end - - context 'referencing a user in a link href' do - let(:reference) { %Q{<a href="#{user.to_reference}">User</a>} } - - it 'links to a User' do - doc = reference_filter("Hey #{reference}") - expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) - end - - it 'links with adjacent text' do - doc = reference_filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(%r{\(<a.+>User</a>\.\)}) - end - - it 'includes a data-user attribute' do - doc = reference_filter("Hey #{reference}") - link = doc.css('a').first - - expect(link).to have_attribute('data-user') - expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s - end - end - context 'when a project is not specified' do let(:project) { nil } @@ -227,7 +166,7 @@ describe Banzai::Filter::UserReferenceFilter do end it 'supports mentioning a single user' do - reference = group_member.to_reference + reference = get_reference(group_member) doc = reference_filter("Hey #{reference}", context) expect(doc.css('a').first.attr('href')).to eq urls.user_url(group_member) @@ -243,7 +182,7 @@ describe Banzai::Filter::UserReferenceFilter do describe '#namespaces' do it 'returns a Hash containing all Namespaces' do - document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>") + document = Nokogiri::HTML.fragment("<p>#{get_reference(user)}</p>") filter = described_class.new(document, project: project) ns = user.namespace @@ -253,7 +192,7 @@ describe Banzai::Filter::UserReferenceFilter do describe '#usernames' do it 'returns the usernames mentioned in a document' do - document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>") + document = Nokogiri::HTML.fragment("<p>#{get_reference(user)}</p>") filter = described_class.new(document, project: project) expect(filter.usernames).to eq([user.username]) diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb new file mode 100644 index 00000000000..e4936aa9e57 --- /dev/null +++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::ReferenceParser::ProjectParser do + include ReferenceParserHelpers + + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + subject { described_class.new(Banzai::RenderContext.new(project, user)) } + let(:link) { empty_html_link } + + describe '#referenced_by' do + describe 'when the link has a data-project attribute' do + context 'using an existing project ID' do + it 'returns an Array of projects' do + link['data-project'] = project.id.to_s + + expect(subject.gather_references([link])).to eq([project]) + end + end + + context 'using a non-existing project ID' do + it 'returns an empty Array' do + link['data-project'] = '' + + expect(subject.gather_references([link])).to eq([]) + end + end + + context 'using a private project ID' do + it 'returns an empty Array when unauthorized' do + private_project = create(:project, :private) + + link['data-project'] = private_project.id.to_s + + expect(subject.gather_references([link])).to eq([]) + end + + it 'returns an Array when authorized' do + private_project = create(:project, :private, namespace: user.namespace) + + link['data-project'] = private_project.id.to_s + + expect(subject.gather_references([link])).to eq([private_project]) + end + end + end + end +end diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb index 37b38776775..11e605eece6 100644 --- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb +++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb @@ -244,9 +244,11 @@ describe Gitlab::Cleanup::ProjectUploads do orphaned1 = create(:upload, :personal_snippet_upload, :with_file) orphaned2 = create(:upload, :namespace_upload, :with_file) orphaned3 = create(:upload, :attachment_upload, :with_file) + orphaned4 = create(:upload, :favicon_upload, :with_file) paths << orphaned1.absolute_path paths << orphaned2.absolute_path paths << orphaned3.absolute_path + paths << orphaned4.absolute_path Upload.delete_all expect(logger).not_to receive(:info).with(/move|fix/i) diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index eb7148ff108..4e83b27e4a5 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -48,10 +48,10 @@ describe Gitlab::Database::MigrationHelpers do allow(model).to receive(:transaction_open?).and_return(false) end - context 'using PostgreSQL' do + context 'using PostgreSQL', :postgresql do before do allow(Gitlab::Database).to receive(:postgresql?).and_return(true) - allow(model).to receive(:disable_statement_timeout) + allow(model).to receive(:disable_statement_timeout).and_call_original end it 'creates the index concurrently' do @@ -114,12 +114,12 @@ describe Gitlab::Database::MigrationHelpers do before do allow(model).to receive(:transaction_open?).and_return(false) allow(model).to receive(:index_exists?).and_return(true) + allow(model).to receive(:disable_statement_timeout).and_call_original end context 'using PostgreSQL' do before do allow(model).to receive(:supports_drop_index_concurrently?).and_return(true) - allow(model).to receive(:disable_statement_timeout) end describe 'by column name' do @@ -162,7 +162,7 @@ describe Gitlab::Database::MigrationHelpers do context 'using MySQL' do it 'removes an index' do - expect(Gitlab::Database).to receive(:postgresql?).and_return(false) + expect(Gitlab::Database).to receive(:postgresql?).and_return(false).twice expect(model).to receive(:remove_index) .with(:users, { column: :foo }) @@ -224,21 +224,26 @@ describe Gitlab::Database::MigrationHelpers do context 'using PostgreSQL' do before do + allow(Gitlab::Database).to receive(:postgresql?).and_return(true) allow(Gitlab::Database).to receive(:mysql?).and_return(false) end it 'creates a concurrent foreign key and validates it' do - expect(model).to receive(:disable_statement_timeout) + expect(model).to receive(:disable_statement_timeout).and_call_original + expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).ordered.with(/NOT VALID/) expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) + expect(model).to receive(:execute).with(/RESET ALL/) model.add_concurrent_foreign_key(:projects, :users, column: :user_id) end it 'appends a valid ON DELETE statement' do - expect(model).to receive(:disable_statement_timeout) + expect(model).to receive(:disable_statement_timeout).and_call_original + expect(model).to receive(:execute).with(/statement_timeout/) expect(model).to receive(:execute).with(/ON DELETE SET NULL/) expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/) + expect(model).to receive(:execute).with(/RESET ALL/) model.add_concurrent_foreign_key(:projects, :users, column: :user_id, @@ -291,13 +296,68 @@ describe Gitlab::Database::MigrationHelpers do describe '#disable_statement_timeout' do context 'using PostgreSQL' do - it 'disables statement timeouts' do + it 'disables statement timeouts to current transaction only' do expect(Gitlab::Database).to receive(:postgresql?).and_return(true) - expect(model).to receive(:execute).with('SET statement_timeout TO 0') + expect(model).to receive(:execute).with('SET LOCAL statement_timeout TO 0') model.disable_statement_timeout end + + # this specs runs without an enclosing transaction (:delete truncation method for db_cleaner) + context 'with real environment', :postgresql, :delete do + before do + model.execute("SET statement_timeout TO '20000'") + end + + after do + model.execute('RESET ALL') + end + + it 'defines statement to 0 only for current transaction' do + expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s') + + model.connection.transaction do + model.disable_statement_timeout + expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0') + end + + expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s') + end + end + + context 'when passing a blocks' do + it 'disables statement timeouts on session level and executes the block' do + expect(Gitlab::Database).to receive(:postgresql?).and_return(true) + expect(model).to receive(:execute).with('SET statement_timeout TO 0') + expect(model).to receive(:execute).with('RESET ALL') + + expect { |block| model.disable_statement_timeout(&block) }.to yield_control + end + + # this specs runs without an enclosing transaction (:delete truncation method for db_cleaner) + context 'with real environment', :postgresql, :delete do + before do + model.execute("SET statement_timeout TO '20000'") + end + + after do + model.execute('RESET ALL') + end + + it 'defines statement to 0 for any code run inside the block' do + expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s') + + model.disable_statement_timeout do + model.connection.transaction do + expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0') + end + + expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0') + end + end + end + end end context 'using MySQL' do @@ -308,6 +368,16 @@ describe Gitlab::Database::MigrationHelpers do model.disable_statement_timeout end + + context 'when passing a blocks' do + it 'executes the block of code' do + expect(Gitlab::Database).to receive(:postgresql?).and_return(false) + + expect(model).not_to receive(:execute) + + expect { |block| model.disable_statement_timeout(&block) }.to yield_control + end + end end end diff --git a/spec/lib/gitlab/git/merge_base_spec.rb b/spec/lib/gitlab/git/merge_base_spec.rb new file mode 100644 index 00000000000..2f4e043a20f --- /dev/null +++ b/spec/lib/gitlab/git/merge_base_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Git::MergeBase do + set(:project) { create(:project, :repository) } + let(:repository) { project.repository } + subject(:merge_base) { described_class.new(repository, refs) } + + shared_context 'existing refs with a merge base', :existing_refs do + let(:refs) do + %w(304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209) + end + end + + shared_context 'when passing a missing ref', :missing_ref do + let(:refs) do + %w(304d257dcb821665ab5110318fc58a007bd104ed aaaa) + end + end + + shared_context 'when passing refs that do not have a common ancestor', :no_common_ancestor do + let(:refs) { ['304d257dcb821665ab5110318fc58a007bd104ed', TestEnv::BRANCH_SHA['orphaned-branch']] } + end + + describe '#sha' do + context 'when the refs exist', :existing_refs do + it 'returns the SHA of the merge base' do + expect(merge_base.sha).not_to be_nil + end + + it 'memoizes the result' do + expect(repository).to receive(:merge_base).once.and_call_original + + 2.times { merge_base.sha } + end + end + + context 'when passing a missing ref', :missing_ref do + it 'does not call merge_base on the repository but raises an error' do + expect(repository).not_to receive(:merge_base) + + expect { merge_base.sha }.to raise_error(Gitlab::Git::UnknownRef) + end + end + + it 'returns `nil` when the refs do not have a common ancestor', :no_common_ancestor do + expect(merge_base.sha).to be_nil + end + + it 'returns a merge base when passing 2 branch names' do + merge_base = described_class.new(repository, %w(master feature)) + + expect(merge_base.sha).to be_present + end + + it 'returns a merge base when passing a tag name' do + merge_base = described_class.new(repository, %w(master v1.0.0)) + + expect(merge_base.sha).to be_present + end + end + + describe '#commit' do + context 'for existing refs with a merge base', :existing_refs do + it 'finds the commit for the merge base' do + expect(merge_base.commit).to be_a(Commit) + end + + it 'only looks up the commit once' do + expect(repository).to receive(:commit_by).once.and_call_original + + 2.times { merge_base.commit } + end + end + + it 'does not try to find the commit when there is no sha', :no_common_ancestor do + expect(repository).not_to receive(:commit_by) + + merge_base.commit + end + end + + describe '#unknown_refs', :missing_ref do + it 'returns the the refs passed that are not part of the repository' do + expect(merge_base.unknown_refs).to contain_exactly('aaaa') + end + + it 'only looks up the commits once' do + expect(merge_base).to receive(:commits_for_refs).once.and_call_original + + 2.times { merge_base.unknown_refs } + end + end +end diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb new file mode 100644 index 00000000000..2eabccd5dff --- /dev/null +++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Gitlab::Template::Finders::RepoTemplateFinder do + set(:project) { create(:project, :repository) } + + let(:categories) { { 'HTML' => 'html' } } + + subject(:finder) { described_class.new(project, 'files/', '.html', categories) } + + describe '#read' do + it 'returns the content of the given path' do + result = finder.read('files/html/500.html') + + expect(result).to be_present + end + + it 'raises an error if the path does not exist' do + expect { finder.read('does/not/exist') }.to raise_error(described_class::FileNotFoundError) + end + end + + describe '#find' do + it 'returns the full path of the found template' do + result = finder.find('500') + + expect(result).to eq('files/html/500.html') + end + end + + describe '#list_files_for' do + it 'returns the full path of the found files' do + result = finder.list_files_for('files/html') + + expect(result).to contain_exactly('files/html/500.html') + end + end +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 32b8755ee9a..42b627b6823 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1103,6 +1103,12 @@ describe Ci::Build do it { is_expected.to be_cancelable } end + + context 'when build is created' do + let(:build) { create(:ci_build, :created) } + + it { is_expected.to be_cancelable } + end end context 'when build is not cancelable' do diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index 20600f5fa38..f2aad455d5f 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -30,7 +30,7 @@ describe InternalId do context 'with existing issues' do before do - rand(1..10).times { create(:issue, project: project) } + create_list(:issue, 2, project: project) described_class.delete_all end @@ -54,7 +54,7 @@ describe InternalId do end it 'generates a strictly monotone, gapless sequence' do - seq = (0..rand(100)).map do + seq = Array.new(10).map do described_class.generate_next(issue, scope, usage, init) end normalized = seq.map { |i| i - seq.min } diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb new file mode 100644 index 00000000000..c633e1908d4 --- /dev/null +++ b/spec/models/license_template_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe LicenseTemplate do + describe '#content' do + it 'calls a proc exactly once if provided' do + lazy = build_template(-> { 'bar' }) + content = lazy.content + + expect(content).to eq('bar') + expect(content.object_id).to eq(lazy.content.object_id) + + content.replace('foo') + expect(lazy.content).to eq('foo') + end + + it 'returns a string if provided' do + lazy = build_template('bar') + + expect(lazy.content).to eq('bar') + end + end + + describe '#resolve!' do + let(:content) do + <<~TEXT + Pretend License + + [project] + + Copyright (c) [year] [fullname] + TEXT + end + + let(:expected) do + <<~TEXT + Pretend License + + Foo Project + + Copyright (c) 1985 Nick Thomas + TEXT + end + + let(:template) { build_template(content) } + + it 'updates placeholders in a copy of the template content' do + expect(template.content.object_id).to eq(content.object_id) + + template.resolve!(project_name: "Foo Project", fullname: "Nick Thomas", year: "1985") + + expect(template.content).to eq(expected) + expect(template.content.object_id).not_to eq(content.object_id) + end + end + + def build_template(content) + described_class.new(id: 'foo', name: 'foo', category: :Other, content: content) + end +end diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 77c475b9f52..e545b674b4f 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -94,9 +94,39 @@ RSpec.describe NotificationSetting do end end - context 'email events' do - it 'includes EXCLUDED_WATCHER_EVENTS in EMAIL_EVENTS' do - expect(described_class::EMAIL_EVENTS).to include(*described_class::EXCLUDED_WATCHER_EVENTS) + describe '.email_events' do + subject { described_class.email_events } + + it 'returns email events' do + expect(subject).to include( + :new_note, + :new_issue, + :reopen_issue, + :close_issue, + :reassign_issue, + :new_merge_request, + :reopen_merge_request, + :close_merge_request, + :reassign_merge_request, + :merge_merge_request, + :failed_pipeline, + :success_pipeline + ) + end + + it 'includes EXCLUDED_WATCHER_EVENTS' do + expect(subject).to include(*described_class::EXCLUDED_WATCHER_EVENTS) + end + end + + describe '#email_events' do + let(:source) { build(:group) } + + subject { build(:notification_setting, source: source) } + + it 'calls email_events' do + expect(described_class).to receive(:email_events).with(source) + subject.email_events end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 03beb9187ed..076de06cf99 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -364,6 +364,15 @@ describe Project do it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } end + describe '#to_reference_with_postfix' do + it 'returns the full path with reference_postfix' do + namespace = create(:namespace, path: 'sample-namespace') + project = create(:project, path: 'sample-project', namespace: namespace) + + expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>' + end + end + describe '#to_reference' do let(:owner) { create(:user, name: 'Gitlab') } let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 52ec8dbe25a..2859d5149ec 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -296,41 +296,31 @@ describe Repository do end describe '#new_commits' do - shared_examples 'finding unreferenced commits' do - set(:project) { create(:project, :repository) } - let(:repository) { project.repository } + set(:project) { create(:project, :repository) } + let(:repository) { project.repository } - subject { repository.new_commits(rev) } + subject { repository.new_commits(rev) } - context 'when there are no new commits' do - let(:rev) { repository.commit.id } + context 'when there are no new commits' do + let(:rev) { repository.commit.id } - it 'returns an empty array' do - expect(subject).to eq([]) - end + it 'returns an empty array' do + expect(subject).to eq([]) end + end - context 'when new commits are found' do - let(:branch) { 'orphaned-branch' } - let!(:rev) { repository.commit(branch).id } + context 'when new commits are found' do + let(:branch) { 'orphaned-branch' } + let!(:rev) { repository.commit(branch).id } - it 'returns the commits' do - repository.delete_branch(branch) + it 'returns the commits' do + repository.delete_branch(branch) - expect(subject).not_to be_empty - expect(subject).to all( be_a(::Commit) ) - expect(subject.size).to eq(1) - end + expect(subject).not_to be_empty + expect(subject).to all( be_a(::Commit) ) + expect(subject.size).to eq(1) end end - - context 'when Gitaly handles the request' do - it_behaves_like 'finding unreferenced commits' - end - - context 'when Gitaly is disabled', :disable_gitaly do - it_behaves_like 'finding unreferenced commits' - end end describe '#commits_by' do diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 5814d834572..6adbbb40489 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -3,6 +3,32 @@ require 'spec_helper' describe API::Jobs do include HttpIOHelpers + shared_examples 'a job with artifacts and trace' do |result_is_array: true| + context 'with artifacts and trace' do + let!(:second_job) { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) } + + it 'returns artifacts and trace data', :skip_before_request do + get api(api_endpoint, api_user) + json_job = result_is_array ? json_response.select { |job| job['id'] == second_job.id }.first : json_response + + expect(json_job['artifacts_file']).not_to be_nil + expect(json_job['artifacts_file']).not_to be_empty + expect(json_job['artifacts_file']['filename']).to eq(second_job.artifacts_file.filename) + expect(json_job['artifacts_file']['size']).to eq(second_job.artifacts_file.size) + expect(json_job['artifacts']).not_to be_nil + expect(json_job['artifacts']).to be_an Array + expect(json_job['artifacts'].size).to eq(second_job.job_artifacts.length) + json_job['artifacts'].each do |artifact| + expect(artifact).not_to be_nil + file_type = Ci::JobArtifact.file_types[artifact['file_type']] + expect(artifact['size']).to eq(second_job.job_artifacts.where(file_type: file_type).first.size) + expect(artifact['filename']).to eq(second_job.job_artifacts.where(file_type: file_type).first.filename) + expect(artifact['file_format']).to eq(second_job.job_artifacts.where(file_type: file_type).first.file_format) + end + end + end + end + set(:project) do create(:project, :repository, public_builds: false) end @@ -49,6 +75,20 @@ describe API::Jobs do expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) end + context 'without artifacts and trace' do + it 'returns no artifacts nor trace data' do + json_job = json_response.first + + expect(json_job['artifacts_file']).to be_nil + expect(json_job['artifacts']).to be_an Array + expect(json_job['artifacts']).to be_empty + end + end + + it_behaves_like 'a job with artifacts and trace' do + let(:api_endpoint) { "/projects/#{project.id}/jobs" } + end + it 'returns pipeline data' do json_job = json_response.first @@ -60,7 +100,7 @@ describe API::Jobs do end it 'avoids N+1 queries', :skip_before_request do - first_build = create(:ci_build, :artifacts, pipeline: pipeline) + first_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) first_build.runner = create(:ci_runner) first_build.user = create(:user) first_build.save @@ -68,7 +108,7 @@ describe API::Jobs do control_count = ActiveRecord::QueryRecorder.new { go }.count second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) - second_build = create(:ci_build, :artifacts, pipeline: second_pipeline) + second_build = create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: second_pipeline) second_build.runner = create(:ci_runner) second_build.user = create(:user) second_build.save @@ -117,9 +157,11 @@ describe API::Jobs do describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do let(:query) { Hash.new } - before do - job - get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query + before do |example| + unless example.metadata[:skip_before_request] + job + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query + end end context 'authorized user' do @@ -133,6 +175,13 @@ describe API::Jobs do expect(json_response).not_to be_empty expect(json_response.first['commit']['id']).to eq project.commit.id expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) + expect(json_response.first['artifacts_file']).to be_nil + expect(json_response.first['artifacts']).to be_an Array + expect(json_response.first['artifacts']).to be_empty + end + + it_behaves_like 'a job with artifacts and trace' do + let(:api_endpoint) { "/projects/#{project.id}/pipelines/#{pipeline.id}/jobs" } end it 'returns pipeline data' do @@ -183,7 +232,7 @@ describe API::Jobs do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query end.count - 3.times { create(:ci_build, :artifacts, pipeline: pipeline) } + 3.times { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) } expect do get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query @@ -201,8 +250,10 @@ describe API::Jobs do end describe 'GET /projects/:id/jobs/:job_id' do - before do - get api("/projects/#{project.id}/jobs/#{job.id}", api_user) + before do |example| + unless example.metadata[:skip_before_request] + get api("/projects/#{project.id}/jobs/#{job.id}", api_user) + end end context 'authorized user' do @@ -219,10 +270,17 @@ describe API::Jobs do expect(Time.parse(json_response['started_at'])).to be_like_time(job.started_at) expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at) expect(Time.parse(json_response['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) + expect(json_response['artifacts_file']).to be_nil + expect(json_response['artifacts']).to be_an Array + expect(json_response['artifacts']).to be_empty expect(json_response['duration']).to eq(job.duration) expect(json_response['web_url']).to be_present end + it_behaves_like 'a job with artifacts and trace', result_is_array: false do + let(:api_endpoint) { "/projects/#{project.id}/jobs/#{second_job.id}" } + end + it 'returns pipeline data' do json_job = json_response diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 6063afc213d..519638ebb82 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -465,4 +465,77 @@ describe API::Repositories do end end end + + describe 'GET :id/repository/merge_base' do + let(:refs) do + %w(304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209) + end + + subject(:request) do + get(api("/projects/#{project.id}/repository/merge_base", current_user), refs: refs) + end + + shared_examples 'merge base' do + it 'returns the common ancestor' do + request + + expect(response).to have_gitlab_http_status(:success) + expect(json_response['id']).to be_present + end + end + + context 'when unauthenticated', 'and project is public' do + it_behaves_like 'merge base' do + let(:project) { create(:project, :public, :repository) } + let(:current_user) { nil } + end + end + + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:current_user) { nil } + let(:message) { '404 Project Not Found' } + end + end + + context 'when authenticated', 'as a developer' do + it_behaves_like 'merge base' do + let(:current_user) { user } + end + end + + context 'when authenticated', 'as a guest' do + it_behaves_like '403 response' do + let(:current_user) { guest } + end + end + + context 'when passing refs that do not exist' do + it_behaves_like '400 response' do + let(:refs) { %w(304d257dcb821665ab5110318fc58a007bd104ed missing) } + let(:current_user) { user } + let(:message) { 'Could not find ref: missing' } + end + end + + context 'when passing refs that do not have a merge base' do + it_behaves_like '404 response' do + let(:refs) { ['304d257dcb821665ab5110318fc58a007bd104ed', TestEnv::BRANCH_SHA['orphaned-branch']] } + let(:current_user) { user } + let(:message) { '404 Merge Base Not Found' } + end + end + + context 'when not enough refs are passed' do + let(:refs) { %w(only-one) } + let(:current_user) { user } + + it 'renders a bad request error' do + request + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq('Provide exactly 2 refs') + end + end + end end diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb index 6bb53fdc98d..d1e16ab9ca9 100644 --- a/spec/requests/api/templates_spec.rb +++ b/spec/requests/api/templates_spec.rb @@ -56,6 +56,8 @@ describe API::Templates do end it 'returns a license template' do + expect(response).to have_gitlab_http_status(200) + expect(json_response['key']).to eq('mit') expect(json_response['name']).to eq('MIT License') expect(json_response['nickname']).to be_nil @@ -181,6 +183,7 @@ describe API::Templates do it 'replaces the copyright owner placeholder with the name of the current user' do get api('/templates/licenses/mit', user) + expect(response).to have_gitlab_http_status(200) expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") end end diff --git a/spec/rubocop/cop/migration/add_reference_spec.rb b/spec/rubocop/cop/migration/add_reference_spec.rb new file mode 100644 index 00000000000..8f795bb561e --- /dev/null +++ b/spec/rubocop/cop/migration/add_reference_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/add_reference' + +describe RuboCop::Cop::Migration::AddReference do + include CopHelper + + let(:cop) { described_class.new } + + context 'outside of a migration' do + it 'does not register any offenses' do + expect_no_offenses(<<~RUBY) + def up + add_reference(:projects, :users) + end + RUBY + end + end + + context 'in a migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when using add_reference without index' do + expect_offense(<<~RUBY) + call do + add_reference(:projects, :users) + ^^^^^^^^^^^^^ `add_reference` requires `index: true` + end + RUBY + end + + it 'registers an offense when using add_reference index disabled' do + expect_offense(<<~RUBY) + def up + add_reference(:projects, :users, index: false) + ^^^^^^^^^^^^^ `add_reference` requires `index: true` + end + RUBY + end + + it 'does not register an offense when using add_reference with index enabled' do + expect_no_offenses(<<~RUBY) + def up + add_reference(:projects, :users, index: true) + end + RUBY + end + end +end diff --git a/spec/serializers/project_mirror_serializer_spec.rb b/spec/serializers/project_mirror_serializer_spec.rb new file mode 100644 index 00000000000..5e47163532a --- /dev/null +++ b/spec/serializers/project_mirror_serializer_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe ProjectMirrorSerializer do + it 'represents ProjectMirror entities' do + expect(described_class.entity_class).to eq(ProjectMirrorEntity) + end +end diff --git a/spec/services/ci/enqueue_build_service_spec.rb b/spec/services/ci/enqueue_build_service_spec.rb new file mode 100644 index 00000000000..e41b8e4800b --- /dev/null +++ b/spec/services/ci/enqueue_build_service_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Ci::EnqueueBuildService, '#execute' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:ci_build) { create(:ci_build, :created) } + + subject { described_class.new(project, user).execute(ci_build) } + + it 'enqueues the build' do + subject + + expect(ci_build.pending?).to be_truthy + end +end diff --git a/spec/services/commits/tag_service_spec.rb b/spec/services/commits/tag_service_spec.rb new file mode 100644 index 00000000000..82377a8dace --- /dev/null +++ b/spec/services/commits/tag_service_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Commits::TagService do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + let(:commit) { project.commit } + + before do + project.add_maintainer(user) + end + + describe '#execute' do + let(:service) { described_class.new(project, user, opts) } + + shared_examples 'tag failure' do + it 'returns a hash with the :error status' do + result = service.execute(commit) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq(error_message) + end + + it 'does not add a system note' do + service.execute(commit) + + description_notes = find_notes('tag') + expect(description_notes).to be_empty + end + end + + def find_notes(action) + commit + .notes + .joins(:system_note_metadata) + .where(system_note_metadata: { action: action }) + end + + context 'valid params' do + let(:opts) do + { + tag_name: 'v1.2.3', + tag_message: 'Release' + } + end + + def find_notes(action) + commit + .notes + .joins(:system_note_metadata) + .where(system_note_metadata: { action: action }) + end + + context 'when tagging succeeds' do + it 'returns a hash with the :success status and created tag' do + result = service.execute(commit) + + expect(result[:status]).to eq(:success) + + tag = result[:tag] + expect(tag.name).to eq(opts[:tag_name]) + expect(tag.message).to eq(opts[:tag_message]) + end + + it 'adds a system note' do + service.execute(commit) + + description_notes = find_notes('tag') + expect(description_notes.length).to eq(1) + end + end + + context 'when tagging fails' do + let(:tag_error) { 'GitLab: You are not allowed to push code to this project.' } + + before do + tag_stub = instance_double(Tags::CreateService) + allow(Tags::CreateService).to receive(:new).and_return(tag_stub) + allow(tag_stub).to receive(:execute).and_return({ + status: :error, message: tag_error + }) + end + + it_behaves_like 'tag failure' do + let(:error_message) { tag_error } + end + end + end + + context 'invalid params' do + let(:opts) do + {} + end + + it_behaves_like 'tag failure' do + let(:error_message) { 'Missing parameter tag_name' } + end + end + end +end diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index 784dac55454..a8c994c101c 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -11,40 +11,6 @@ describe Notes::QuickActionsService do end end - shared_examples 'note on noteable that does not support quick actions' do - include_context 'note on noteable' - - before do - note.note = note_text - end - - describe 'note with only command' do - describe '/close, /label, /assign & /milestone' do - let(:note_text) { %(/close\n/assign @#{assignee.username}") } - - it 'saves the note and does not alter the note text' do - content, command_params = service.extract_commands(note) - - expect(content).to eq note_text - expect(command_params).to be_empty - end - end - end - - describe 'note with command & text' do - describe '/close, /label, /assign & /milestone' do - let(:note_text) { %(HELLO\n/close\n/assign @#{assignee.username}\nWORLD) } - - it 'saves the note and does not alter the note text' do - content, command_params = service.extract_commands(note) - - expect(content).to eq note_text - expect(command_params).to be_empty - end - end - end - end - shared_examples 'note on noteable that supports quick actions' do include_context 'note on noteable' @@ -147,16 +113,16 @@ describe Notes::QuickActionsService do expect(described_class.noteable_update_service(note)).to eq(Issues::UpdateService) end - it 'returns Issues::UpdateService for a note on a merge request' do + it 'returns MergeRequests::UpdateService for a note on a merge request' do note = create(:note_on_merge_request, project: project) expect(described_class.noteable_update_service(note)).to eq(MergeRequests::UpdateService) end - it 'returns nil for a note on a commit' do + it 'returns Commits::TagService for a note on a commit' do note = create(:note_on_commit, project: project) - expect(described_class.noteable_update_service(note)).to be_nil + expect(described_class.noteable_update_service(note)).to eq(Commits::TagService) end end @@ -175,7 +141,7 @@ describe Notes::QuickActionsService do let(:note) { create(:note_on_commit, project: project) } it 'returns false' do - expect(described_class.supported?(note)).to be_falsy + expect(described_class.supported?(note)).to be_truthy end end end @@ -203,10 +169,6 @@ describe Notes::QuickActionsService do it_behaves_like 'note on noteable that supports quick actions' do let(:note) { build(:note_on_merge_request, project: project) } end - - it_behaves_like 'note on noteable that does not support quick actions' do - let(:note) { build(:note_on_commit, project: project) } - end end context 'CE restriction for issue assignees' do diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index 81dc7c57f4a..507909d9231 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -65,6 +65,31 @@ describe PreviewMarkdownService do end end + context 'commit description' do + let(:project) { create(:project, :repository) } + let(:commit) { project.commit } + let(:params) do + { + text: "My work\n/tag v1.2.3 Stable release", + quick_actions_target_type: 'Commit', + quick_actions_target_id: commit.id + } + end + let(:service) { described_class.new(project, user, params) } + + it 'removes quick actions from text' do + result = service.execute + + expect(result[:text]).to eq 'My work' + end + + it 'explains quick actions effect' do + result = service.execute + + expect(result[:commands]).to eq 'Tags this commit to v1.2.3 with "Stable release".' + end + end + it 'sets correct markdown engine' do service = described_class.new(project, user, { markdown_version: CacheMarkdownField::CACHE_REDCARPET_VERSION }) result = service.execute diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 743e281183e..bf1c157c4a2 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -6,6 +6,7 @@ describe QuickActions::InterpretService do let(:developer2) { create(:user) } let(:issue) { create(:issue, project: project) } let(:milestone) { create(:milestone, project: project, title: '9.10') } + let(:commit) { create(:commit, project: project) } let(:inprogress) { create(:label, project: project, title: 'In Progress') } let(:bug) { create(:label, project: project, title: 'Bug') } let(:note) { build(:note, commit_id: merge_request.diff_head_sha) } @@ -347,6 +348,14 @@ describe QuickActions::InterpretService do end end + shared_examples 'tag command' do + it 'tags a commit' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(tag_name: tag_name, tag_message: tag_message) + end + end + it_behaves_like 'reopen command' do let(:content) { '/reopen' } let(:issuable) { issue } @@ -628,16 +637,6 @@ describe QuickActions::InterpretService do let(:issuable) { merge_request } end - it_behaves_like 'todo command' do - let(:content) { '/todo' } - let(:issuable) { issue } - end - - it_behaves_like 'todo command' do - let(:content) { '/todo' } - let(:issuable) { merge_request } - end - it_behaves_like 'done command' do let(:content) { '/done' } let(:issuable) { issue } @@ -787,6 +786,28 @@ describe QuickActions::InterpretService do let(:issuable) { issue } end + context '/todo' do + let(:content) { '/todo' } + + context 'if issuable is an Issue' do + it_behaves_like 'todo command' do + let(:issuable) { issue } + end + end + + context 'if issuable is a MergeRequest' do + it_behaves_like 'todo command' do + let(:issuable) { merge_request } + end + end + + context 'if issuable is a Commit' do + it_behaves_like 'empty command' do + let(:issuable) { commit } + end + end + end + context '/copy_metadata command' do let(:todo_label) { create(:label, project: project, title: 'To Do') } let(:inreview_label) { create(:label, project: project, title: 'In Review') } @@ -971,6 +992,12 @@ describe QuickActions::InterpretService do let(:issuable) { issue } end end + + context 'if issuable is a Commit' do + let(:content) { '/award :100:' } + let(:issuable) { commit } + it_behaves_like 'empty command' + end end context '/shrug command' do @@ -1102,6 +1129,32 @@ describe QuickActions::InterpretService do it_behaves_like 'empty command' end end + + context '/tag command' do + let(:issuable) { commit } + + context 'ignores command with no argument' do + it_behaves_like 'empty command' do + let(:content) { '/tag' } + end + end + + context 'tags a commit with a tag name' do + it_behaves_like 'tag command' do + let(:tag_name) { 'v1.2.3' } + let(:tag_message) { nil } + let(:content) { "/tag #{tag_name}" } + end + end + + context 'tags a commit with a tag name and message' do + it_behaves_like 'tag command' do + let(:tag_name) { 'v1.2.3' } + let(:tag_message) { 'Stable release' } + let(:content) { "/tag #{tag_name} #{tag_message}" } + end + end + end end describe '#explain' do @@ -1319,5 +1372,39 @@ describe QuickActions::InterpretService do expect(explanations).to eq(["Moves this issue to test/project."]) end end + + describe 'tag a commit' do + describe 'with a tag name' do + context 'without a message' do + let(:content) { '/tag v1.2.3' } + + it 'includes the tag name only' do + _, explanations = service.explain(content, commit) + + expect(explanations).to eq(["Tags this commit to v1.2.3."]) + end + end + + context 'with an empty message' do + let(:content) { '/tag v1.2.3 ' } + + it 'includes the tag name only' do + _, explanations = service.explain(content, commit) + + expect(explanations).to eq(["Tags this commit to v1.2.3."]) + end + end + end + + describe 'with a tag name and message' do + let(:content) { '/tag v1.2.3 Stable release' } + + it 'includes the tag name and message' do + _, explanations = service.explain(content, commit) + + expect(explanations).to eq(["Tags this commit to v1.2.3 with \"Stable release\"."]) + end + end + end end end diff --git a/spec/services/quick_actions/target_service_spec.rb b/spec/services/quick_actions/target_service_spec.rb new file mode 100644 index 00000000000..0aeb29cbeec --- /dev/null +++ b/spec/services/quick_actions/target_service_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe QuickActions::TargetService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { described_class.new(project, user) } + + before do + project.add_maintainer(user) + end + + describe '#execute' do + shared_examples 'no target' do |type_id:| + it 'returns nil' do + target = service.execute(type, type_id) + + expect(target).to be_nil + end + end + + shared_examples 'find target' do + it 'returns the target' do + found_target = service.execute(type, target_id) + + expect(found_target).to eq(target) + end + end + + shared_examples 'build target' do |type_id:| + it 'builds a new target' do + target = service.execute(type, type_id) + + expect(target.project).to eq(project) + expect(target).to be_new_record + end + end + + context 'for issue' do + let(:target) { create(:issue, project: project) } + let(:target_id) { target.iid } + let(:type) { 'Issue' } + + it_behaves_like 'find target' + it_behaves_like 'build target', type_id: nil + it_behaves_like 'build target', type_id: -1 + end + + context 'for merge request' do + let(:target) { create(:merge_request, source_project: project) } + let(:target_id) { target.iid } + let(:type) { 'MergeRequest' } + + it_behaves_like 'find target' + it_behaves_like 'build target', type_id: nil + it_behaves_like 'build target', type_id: -1 + end + + context 'for commit' do + let(:project) { create(:project, :repository) } + let(:target) { project.commit.parent } + let(:target_id) { target.sha } + let(:type) { 'Commit' } + + it_behaves_like 'find target' + it_behaves_like 'no target', type_id: 'invalid_sha' + + context 'with nil target_id' do + let(:target) { project.commit } + let(:target_id) { nil } + + it_behaves_like 'find target' + end + end + + context 'for unknown type' do + let(:type) { 'unknown' } + + it_behaves_like 'no target', type_id: :unused + end + end +end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 57d081cffb3..442de61f69b 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -108,6 +108,25 @@ describe SystemNoteService do end end + describe '.tag_commit' do + let(:noteable) do + project.commit + end + let(:tag_name) { 'v1.2.3' } + + subject { described_class.tag_commit(noteable, project, author, tag_name) } + + it_behaves_like 'a system note' do + let(:action) { 'tag' } + end + + it 'sets the note text' do + link = "http://localhost/#{project.full_path}/tags/#{tag_name}" + + expect(subject.note).to eq "tagged commit #{noteable.sha} to [`#{tag_name}`](#{link})" + end + end + describe '.change_assignee' do subject { described_class.change_assignee(noteable, project, author, assignee) } diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb index eb5da662ab5..476d80f3a93 100644 --- a/spec/support/banzai/reference_filter_shared_examples.rb +++ b/spec/support/banzai/reference_filter_shared_examples.rb @@ -11,3 +11,76 @@ shared_examples 'a reference containing an element node' do expect(doc.children.first.inner_html).to eq(inner_html) end end + +# Requires a reference, subject and subject_name: +# subject { create(:user) } +# let(:reference) { subject.to_reference } +# let(:subject_name) { 'user' } +shared_examples 'user reference or project reference' do + shared_examples 'it contains a data- attribute' do + it 'includes a data- attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute("data-#{subject_name}") + expect(link.attr("data-#{subject_name}")).to eq subject.id.to_s + end + end + + context 'mentioning a resource' do + it_behaves_like 'a reference containing an element node' + it_behaves_like 'it contains a data- attribute' + + it "links to a resource" do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.send("#{subject_name}_url", subject) + end + + it 'links to a resource with a period' do + subject = create(subject_name.to_sym, name: 'alphA.Beta') + + doc = reference_filter("Hey #{get_reference(subject)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a resource with an underscore' do + subject = create(subject_name.to_sym, name: 'ping_pong_king') + + doc = reference_filter("Hey #{get_reference(subject)}") + expect(doc.css('a').length).to eq 1 + end + + it 'links to a resource with different case-sensitivity' do + subject = create(subject_name.to_sym, name: 'RescueRanger') + reference = get_reference(subject) + + doc = reference_filter("Hey #{reference.upcase}") + expect(doc.css('a').length).to eq 1 + expect(doc.css('a').text).to eq(reference) + end + end + + it 'supports an :only_path context' do + doc = reference_filter("Hey #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.send "#{subject_name}_path", subject + end + + context 'referencing a resource in a link href' do + let(:reference) { %Q{<a href="#{get_reference(subject)}">Some text</a>} } + + it_behaves_like 'it contains a data- attribute' + + it 'links to the resource' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.send "#{subject_name}_url", subject + end + + it 'links with adjacent text' do + doc = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(%r{\(<a.+>Some text</a>\.\)}) + end + end +end diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb index 2b9f8b30c60..89517fde6e2 100644 --- a/spec/support/helpers/features/notes_helpers.rb +++ b/spec/support/helpers/features/notes_helpers.rb @@ -20,6 +20,13 @@ module Spec end end end + + def preview_note(text) + page.within('.js-main-target-form') do + fill_in('note[note]', with: text) + click_on('Preview') + end + end end end end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index f392660d2c7..21103771d1f 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -67,6 +67,7 @@ module TestEnv TMP_TEST_PATH = Rails.root.join('tmp', 'tests', '**') REPOS_STORAGE = 'default'.freeze + BROKEN_STORAGE = 'broken'.freeze # Test environment # @@ -157,10 +158,11 @@ module TestEnv component_timed_setup('Gitaly', install_dir: gitaly_dir, version: Gitlab::GitalyClient.expected_server_version, - task: "gitlab:gitaly:install[#{gitaly_dir}]") do + task: "gitlab:gitaly:install[#{gitaly_dir},#{repos_path}]") do - # Always re-create config, in case it's outdated. This is fast anyway. - Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, force: true) + # Re-create config, to specify the broken storage path + storage_paths = { 'default' => repos_path, 'broken' => broken_path } + Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, storage_paths, force: true) start_gitaly(gitaly_dir) end @@ -256,6 +258,10 @@ module TestEnv @repos_path ||= Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path end + def broken_path + @broken_path ||= Gitlab.config.repositories.storages[BROKEN_STORAGE].legacy_disk_path + end + def backup_path Gitlab.config.backup.path end diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb index bbac6ca6a9c..b4164cff922 100644 --- a/spec/support/import_export/configuration_helper.rb +++ b/spec/support/import_export/configuration_helper.rb @@ -9,7 +9,7 @@ module ConfigurationHelper end def relation_class_for_name(relation_name) - relation_name = Gitlab::ImportExport::RelationFactory::OVERRIDES[relation_name.to_sym] || relation_name + relation_name = Gitlab::ImportExport::RelationFactory.overrides[relation_name.to_sym] || relation_name Gitlab::ImportExport::RelationFactory.relation_class(relation_name) end diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb index 4545226d78c..e6e4d9504d9 100644 --- a/spec/tasks/gitlab/gitaly_rake_spec.rb +++ b/spec/tasks/gitlab/gitaly_rake_spec.rb @@ -8,13 +8,23 @@ describe 'gitlab:gitaly namespace rake task' do describe 'install' do let(:repo) { 'https://gitlab.com/gitlab-org/gitaly.git' } let(:clone_path) { Rails.root.join('tmp/tests/gitaly').to_s } + let(:storage_path) { Rails.root.join('tmp/tests/repositories').to_s } let(:version) { File.read(Rails.root.join(Gitlab::GitalyClient::SERVER_VERSION_FILE)).chomp } + subject { run_rake_task('gitlab:gitaly:install', clone_path, storage_path) } + context 'no dir given' do it 'aborts and display a help message' do # avoid writing task output to spec progress allow($stderr).to receive :write - expect { run_rake_task('gitlab:gitaly:install') }.to raise_error /Please specify the directory where you want to install gitaly/ + expect { run_rake_task('gitlab:gitaly:install') }.to raise_error /Please specify the directory where you want to install gitaly and the path for the default storage/ + end + end + + context 'no storage path given' do + it 'aborts and display a help message' do + allow($stderr).to receive :write + expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error /Please specify the directory where you want to install gitaly and the path for the default storage/ end end @@ -23,7 +33,7 @@ describe 'gitlab:gitaly namespace rake task' do expect(main_object) .to receive(:checkout_or_clone_version).and_raise 'Git error' - expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error' + expect { subject }.to raise_error 'Git error' end end @@ -36,7 +46,7 @@ describe 'gitlab:gitaly namespace rake task' do expect(main_object) .to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path) - run_rake_task('gitlab:gitaly:install', clone_path) + subject end end @@ -59,7 +69,7 @@ describe 'gitlab:gitaly namespace rake task' do expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0]) expect(main_object).to receive(:run_command!).with(command_preamble + %w[gmake]).and_return(true) - run_rake_task('gitlab:gitaly:install', clone_path) + subject end end @@ -72,7 +82,7 @@ describe 'gitlab:gitaly namespace rake task' do it 'calls make in the gitaly directory' do expect(main_object).to receive(:run_command!).with(command_preamble + %w[make]).and_return(true) - run_rake_task('gitlab:gitaly:install', clone_path) + subject end context 'when Rails.env is test' do @@ -89,55 +99,10 @@ describe 'gitlab:gitaly namespace rake task' do it 'calls make in the gitaly directory with --no-deployment flag for bundle' do expect(main_object).to receive(:run_command!).with(command_preamble + command).and_return(true) - run_rake_task('gitlab:gitaly:install', clone_path) + subject end end end end end - - describe 'storage_config' do - it 'prints storage configuration in a TOML format' do - config = { - 'default' => Gitlab::GitalyClient::StorageSettings.new( - 'path' => '/path/to/default', - 'gitaly_address' => 'unix:/path/to/my.socket' - ), - 'nfs_01' => Gitlab::GitalyClient::StorageSettings.new( - 'path' => '/path/to/nfs_01', - 'gitaly_address' => 'unix:/path/to/my.socket' - ) - } - allow(Gitlab.config.repositories).to receive(:storages).and_return(config) - allow(Rails.env).to receive(:test?).and_return(false) - - expected_output = '' - Timecop.freeze do - expected_output = <<~TOML - # Gitaly storage configuration generated from #{Gitlab.config.source} on #{Time.current.to_s(:long)} - # This is in TOML format suitable for use in Gitaly's config.toml file. - bin_dir = "tmp/tests/gitaly" - socket_path = "/path/to/my.socket" - [gitlab-shell] - dir = "#{Gitlab.config.gitlab_shell.path}" - [[storage]] - name = "default" - path = "/path/to/default" - [[storage]] - name = "nfs_01" - path = "/path/to/nfs_01" - TOML - end - - expect { run_rake_task('gitlab:gitaly:storage_config')} - .to output(expected_output).to_stdout - - parsed_output = TomlRB.parse(expected_output) - config.each do |name, params| - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - expect(parsed_output['storage']).to include({ 'name' => name, 'path' => params.legacy_disk_path }) - end - end - end - end end diff --git a/spec/views/shared/notes/_form.html.haml_spec.rb b/spec/views/shared/notes/_form.html.haml_spec.rb index c57319869f3..0189f926a5f 100644 --- a/spec/views/shared/notes/_form.html.haml_spec.rb +++ b/spec/views/shared/notes/_form.html.haml_spec.rb @@ -16,7 +16,7 @@ describe 'shared/notes/_form' do render end - %w[issue merge_request].each do |noteable| + %w[issue merge_request commit].each do |noteable| context "with a note on #{noteable}" do let(:note) { build(:"note_on_#{noteable}", project: project) } @@ -25,12 +25,4 @@ describe 'shared/notes/_form' do end end end - - context 'with a note on a commit' do - let(:note) { build(:note_on_commit, project: project) } - - it 'says that only markdown is supported, not quick actions' do - expect(rendered).to have_content('Markdown is supported') - end - end end |