diff options
author | Ahmad Hassan <ahmad.hassan612@gmail.com> | 2018-12-17 13:18:50 +0200 |
---|---|---|
committer | Ahmad Hassan <ahmad.hassan612@gmail.com> | 2018-12-17 13:18:50 +0200 |
commit | d6886506a0bb8b7db3e6f4fa7dc4d5aa1632b84f (patch) | |
tree | 04b5625a927fb37e684d026cb5161b78f993b72e /spec | |
parent | dfc54352c001e8544972c3d40bfc82e55a11c6a0 (diff) | |
parent | 32b6129d8cef406b1cafa455fcf0bf83ddba46de (diff) | |
download | gitlab-ce-d6886506a0bb8b7db3e6f4fa7dc4d5aa1632b84f.tar.gz |
Merge remote-tracking branch 'origin/master' into support-gitaly-tls
Diffstat (limited to 'spec')
116 files changed, 2745 insertions, 626 deletions
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index c2bd7fd9808..945c9f97fc3 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -551,14 +551,7 @@ describe ApplicationController do subject { get :index, text: "hi \255" } it 'renders 412' do - if Gitlab.rails5? - expect { subject }.to raise_error(ActionController::BadRequest) - else - subject - - expect(response).to have_gitlab_http_status(412) - expect(response).to render_template :precondition_failed - end + expect { subject }.to raise_error(ActionController::BadRequest) end end @@ -566,16 +559,7 @@ describe ApplicationController do subject { get :index, text: "hi \255", format: :js } it 'renders 412' do - if Gitlab.rails5? - expect { subject }.to raise_error(ActionController::BadRequest) - else - subject - - json_response = JSON.parse(response.body) - - expect(response).to have_gitlab_http_status(412) - expect(json_response['error']).to eq('Invalid UTF-8') - end + expect { subject }.to raise_error(ActionController::BadRequest) end end end diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb index 80631d2efb0..16ccf405247 100644 --- a/spec/controllers/boards/lists_controller_spec.rb +++ b/spec/controllers/boards/lists_controller_spec.rb @@ -163,11 +163,7 @@ describe Boards::ListsController do list: { position: position }, format: :json } - if Gitlab.rails5? - patch :update, params: params, as: :json - else - patch :update, params - end + patch :update, params: params, as: :json end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 02930edbf72..6240ab6d867 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -42,6 +42,8 @@ describe Projects::IssuesController do it_behaves_like "issuables list meta-data", :issue + it_behaves_like 'set sort order from user preference' + it "returns index" do get :index, namespace_id: project.namespace, project_id: project diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 7f15da859e5..e837c99d19c 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -160,6 +160,8 @@ describe Projects::MergeRequestsController do it_behaves_like "issuables list meta-data", :merge_request + it_behaves_like 'set sort order from user preference' + context 'when page param' do let(:last_page) { project.merge_requests.page().total_pages } let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } @@ -355,11 +357,7 @@ describe Projects::MergeRequestsController do context 'when the sha parameter matches the source SHA' do def merge_with_sha(params = {}) post_params = base_params.merge(sha: merge_request.diff_head_sha).merge(params) - if Gitlab.rails5? - post :merge, params: post_params, as: :json - else - post :merge, post_params - end + post :merge, params: post_params, as: :json end it 'returns :success' do diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 7179423dde2..08e2c957d69 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -310,19 +310,11 @@ describe Projects::PipelineSchedulesController do end def go - if Gitlab.rails5? - put :update, params: { namespace_id: project.namespace.to_param, - project_id: project, - id: pipeline_schedule, - schedule: schedule }, - as: :html - - else - put :update, namespace_id: project.namespace.to_param, - project_id: project, - id: pipeline_schedule, - schedule: schedule - end + put :update, params: { namespace_id: project.namespace.to_param, + project_id: project, + id: pipeline_schedule, + schedule: schedule }, + as: :html end end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 45cea8c1351..0941af6779f 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -3,9 +3,8 @@ require 'spec_helper' describe Projects::ServicesController do let(:project) { create(:project, :repository) } let(:user) { create(:user) } - let(:service) { create(:hipchat_service, project: project) } - let(:hipchat_client) { { '#room' => double(send: true) } } - let(:service_params) { { token: 'hipchat_token_p', room: '#room' } } + let(:service) { create(:jira_service, project: project) } + let(:service_params) { { username: 'username', password: 'password', url: 'http://example.com' } } before do sign_in(user) @@ -24,13 +23,13 @@ describe Projects::ServicesController do end context 'when validations fail' do - let(:service_params) { { active: 'true', token: '' } } + let(:service_params) { { active: 'true', url: '' } } it 'returns error messages in JSON response' do - put :test, namespace_id: project.namespace, project_id: project, id: :hipchat, service: service_params + put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params expect(json_response['message']).to eq "Validations failed." - expect(json_response['service_response']).to eq "Token can't be blank" + expect(json_response['service_response']).to include "Url can't be blank" expect(response).to have_gitlab_http_status(200) end end @@ -52,7 +51,8 @@ describe Projects::ServicesController do end it 'returns success' do - expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client) + stub_request(:get, 'http://example.com/rest/api/2/serverInfo') + .to_return(status: 200, body: '{}') put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params @@ -61,7 +61,8 @@ describe Projects::ServicesController do end it 'returns success' do - expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client) + stub_request(:get, 'http://example.com/rest/api/2/serverInfo') + .to_return(status: 200, body: '{}') put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params @@ -76,12 +77,16 @@ describe Projects::ServicesController do it 'persist the object' do do_put + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_empty expect(BuildkiteService.first).to be_present end it 'creates the ServiceHook object' do do_put + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_empty expect(BuildkiteService.first.service_hook).to be_present end @@ -96,13 +101,18 @@ describe Projects::ServicesController do context 'failure' do it 'returns success status code and the error message' do - expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_raise('Bad test') + stub_request(:get, 'http://example.com/rest/api/2/serverInfo') + .to_return(status: 404) put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params - expect(response.status).to eq(200) - expect(JSON.parse(response.body)) - .to eq('error' => true, 'message' => 'Test failed.', 'service_response' => 'Bad test', 'test_failed' => true) + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq( + 'error' => true, + 'message' => 'Test failed.', + 'service_response' => '', + 'test_failed' => true + ) end end end @@ -114,7 +124,7 @@ describe Projects::ServicesController do namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } expect(response).to redirect_to(project_settings_integrations_path(project)) - expect(flash[:notice]).to eq 'HipChat activated.' + expect(flash[:notice]).to eq 'JIRA activated.' end end @@ -123,7 +133,7 @@ describe Projects::ServicesController do put :update, namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: false } - expect(flash[:notice]).to eq 'HipChat settings saved, but not activated.' + expect(flash[:notice]).to eq 'JIRA settings saved, but not activated.' end end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 6420b70a54f..832649e5886 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -8,11 +8,7 @@ end shared_examples 'content not cached without revalidation and no-store' do it 'ensures content will not be cached without revalidation' do # Fixed in newer versions of ActivePack, it will only output a single `private`. - if Gitlab.rails5? - expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store') - else - expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, private, no-store') - end + expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store') end end diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index e8584846b56..7c505ee0d43 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -54,7 +54,8 @@ describe 'Database schema' do user_agent_details: %w[subject_id], users: %w[color_scheme_id created_by_id theme_id], users_star_projects: %w[user_id], - web_hooks: %w[service_id] + web_hooks: %w[service_id], + suggestions: %w[commit_id] }.with_indifferent_access.freeze context 'for table' do diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb new file mode 100644 index 00000000000..5f83b80ad7b --- /dev/null +++ b/spec/factories/ci/bridge.rb @@ -0,0 +1,17 @@ +FactoryBot.define do + factory :ci_bridge, class: Ci::Bridge do + name ' bridge' + stage 'test' + stage_idx 0 + ref 'master' + tag false + created_at 'Di 29. Okt 09:50:00 CET 2013' + status :success + + pipeline factory: :ci_pipeline + + after(:build) do |bridge, evaluator| + bridge.project ||= bridge.pipeline.project + end + end +end diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index d80c65cf8bb..18047c74a5d 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -1,6 +1,7 @@ FactoryBot.define do factory :release do tag "v1.1.0" + name { tag } description "Awesome release" project end diff --git a/spec/factories/suggestions.rb b/spec/factories/suggestions.rb new file mode 100644 index 00000000000..307523cc061 --- /dev/null +++ b/spec/factories/suggestions.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :suggestion do + relative_order 0 + association :note, factory: :diff_note_on_merge_request + from_content " vars = {\n" + to_content " vars = [\n" + + trait :unappliable do + from_content "foo" + to_content "foo" + end + + trait :applied do + applied true + commit_id { RepoHelpers.sample_commit.id } + end + end +end diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index c29dfb01381..e24b1f4349d 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Help Pages' do @@ -52,23 +54,21 @@ describe 'Help Pages' do end end - context 'in a production environment with version check enabled', :js do + context 'in a production environment with version check enabled' do before do - allow(Rails.env).to receive(:production?) { true } stub_application_setting(version_check_enabled: true) - allow_any_instance_of(VersionCheck).to receive(:url) { '/version-check-url' } + + allow(Rails.env).to receive(:production?).and_return(true) + allow(VersionCheck).to receive(:url).and_return('/version-check-url') sign_in(create(:user)) visit help_path end it 'has a version check image' do - expect(find('.js-version-status-badge', visible: false)['src']).to end_with('/version-check-url') - end - - it 'hides the version check image if the image request fails' do - # We use '--load-images=yes' with poltergeist so the image fails to load - expect(page).to have_selector('.js-version-status-badge', visible: false) + # Check `data-src` due to lazy image loading + expect(find('.js-version-status-badge', visible: false)['data-src']) + .to end_with('/version-check-url') end end diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 687a6f1eafc..830d56035aa 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe "User creates issue" do @@ -64,10 +66,10 @@ describe "User creates issue" do end context "with labels" do - LABEL_TITLES = %w(bug feature enhancement).freeze + let(:label_titles) { %w(bug feature enhancement) } before do - LABEL_TITLES.each do |title| + label_titles.each do |title| create(:label, project: project, title: title) end end @@ -77,13 +79,13 @@ describe "User creates issue" do fill_in("Title", with: issue_title) click_button("Label") - click_link(LABEL_TITLES.first) + click_link(label_titles.first) click_button("Submit issue") expect(page).to have_content(issue_title) .and have_content(user.name) .and have_content(project.name) - .and have_content(LABEL_TITLES.first) + .and have_content(label_titles.first) end end end diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb index 859a4c65562..93376bc8ce0 100644 --- a/spec/features/merge_request/user_awards_emoji_spec.rb +++ b/spec/features/merge_request/user_awards_emoji_spec.rb @@ -4,11 +4,14 @@ describe 'Merge request > User awards emoji', :js do let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } let(:merge_request) { create(:merge_request, source_project: project, author: create(:user)) } + let!(:note) { create(:note, noteable: merge_request, project: merge_request.project) } describe 'logged in' do before do sign_in(user) visit project_merge_request_path(project, merge_request) + + wait_for_requests end it 'adds award to merge request' do @@ -36,6 +39,15 @@ describe 'Merge request > User awards emoji', :js do expect(page).to have_selector('.emoji-menu', count: 1) end + it 'adds awards to note' do + first('.js-note-emoji').click + first('.emoji-menu .js-emoji-btn').click + + wait_for_requests + + expect(page).to have_selector('.js-awards-block') + end + describe 'the project is archived' do let(:project) { create(:project, :public, :repository, :archived) } diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 53ed5d78598..29b3d2b629b 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -88,6 +88,8 @@ describe 'Merge request > User merges when pipeline succeeds', :js do describe 'enabling Merge when pipeline succeeds via dropdown' do it 'activates the Merge when pipeline succeeds feature' do + wait_for_requests + find('.js-merge-moment').click click_link 'Merge when pipeline succeeds' diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb new file mode 100644 index 00000000000..c19e299097e --- /dev/null +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'User comments on a diff', :js do + include MergeRequestDiffHelpers + include RepoHelpers + + let(:project) { create(:project, :repository) } + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test') + end + let(:user) { create(:user) } + + before do + project.add_maintainer(user) + sign_in(user) + + visit(diffs_project_merge_request_path(project, merge_request)) + end + + context 'single suggestion note' do + it 'suggestion is presented' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: "```suggestion\n# change to a comment\n```") + click_button('Comment') + end + + wait_for_requests + + page.within('.diff-discussions') do + expect(page).to have_button('Apply suggestion') + expect(page).to have_content('Suggested change') + expect(page).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git') + expect(page).to have_content('# change to a comment') + end + end + + it 'suggestion is appliable' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: "```suggestion\n# change to a comment\n```") + click_button('Comment') + end + + wait_for_requests + + page.within('.diff-discussions') do + expect(page).not_to have_content('Applied') + + click_button('Apply suggestion') + wait_for_requests + + expect(page).to have_content('Applied') + end + end + end + + context 'multiple suggestions in a single note' do + it 'suggestions are presented' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion\n# or that\n```") + click_button('Comment') + end + + wait_for_requests + + page.within('.diff-discussions') do + suggestion_1 = page.all(:css, '.md-suggestion-diff')[0] + suggestion_2 = page.all(:css, '.md-suggestion-diff')[1] + + expect(suggestion_1).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git') + expect(suggestion_1).to have_content('# change to a comment') + + expect(suggestion_2).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git') + expect(suggestion_2).to have_content('# or that') + end + end + end +end diff --git a/spec/features/projects/labels/user_views_labels_spec.rb b/spec/features/projects/labels/user_views_labels_spec.rb index 0cbeca4e392..2c8267764bd 100644 --- a/spec/features/projects/labels/user_views_labels_spec.rb +++ b/spec/features/projects/labels/user_views_labels_spec.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + require "spec_helper" describe "User views labels" do set(:project) { create(:project_empty_repo, :public) } set(:user) { create(:user) } - LABEL_TITLES = %w[bug enhancement feature].freeze + let(:label_titles) { %w[bug enhancement feature] } before do - LABEL_TITLES.each { |title| create(:label, project: project, title: title) } + label_titles.each { |title| create(:label, project: project, title: title) } project.add_guest(user) sign_in(user) @@ -17,7 +19,7 @@ describe "User views labels" do it "shows all labels" do page.within('.other-labels .manage-labels-list') do - LABEL_TITLES.each { |title| expect(page).to have_content(title) } + label_titles.each { |title| expect(page).to have_content(title) } end end end diff --git a/spec/finders/cluster_ancestors_finder_spec.rb b/spec/finders/cluster_ancestors_finder_spec.rb new file mode 100644 index 00000000000..332086c42e2 --- /dev/null +++ b/spec/finders/cluster_ancestors_finder_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ClusterAncestorsFinder, '#execute' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:user) { create(:user) } + + let!(:project_cluster) do + create(:cluster, :provided_by_user, cluster_type: :project_type, projects: [project]) + end + + let!(:group_cluster) do + create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) + end + + subject { described_class.new(clusterable, user).execute } + + context 'for a project' do + let(:clusterable) { project } + + before do + project.add_maintainer(user) + end + + it 'returns the project clusters followed by group clusters' do + is_expected.to eq([project_cluster, group_cluster]) + end + + context 'nested groups', :nested_groups do + let(:group) { create(:group, parent: parent_group) } + let(:parent_group) { create(:group) } + + let!(:parent_group_cluster) do + create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group]) + end + + it 'returns the project clusters followed by group clusters ordered ascending the hierarchy' do + is_expected.to eq([project_cluster, group_cluster, parent_group_cluster]) + end + end + end + + context 'user cannot read clusters for clusterable' do + let(:clusterable) { project } + + it 'returns nothing' do + is_expected.to be_empty + end + end + + context 'for a group' do + let(:clusterable) { group } + + before do + group.add_maintainer(user) + end + + it 'returns the list of group clusters' do + is_expected.to eq([group_cluster]) + end + + context 'nested groups', :nested_groups do + let(:group) { create(:group, parent: parent_group) } + let(:parent_group) { create(:group) } + + let!(:parent_group_cluster) do + create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group]) + end + + it 'returns the list of group clusters ordered ascending the hierarchy' do + is_expected.to eq([group_cluster, parent_group_cluster]) + end + end + end +end diff --git a/spec/fixtures/api/schemas/entities/diff_line.json b/spec/fixtures/api/schemas/entities/diff_line.json index 66e8b443e1b..9657004cd2d 100644 --- a/spec/fixtures/api/schemas/entities/diff_line.json +++ b/spec/fixtures/api/schemas/entities/diff_line.json @@ -8,7 +8,8 @@ "new_line": { "type": ["integer", "null"] }, "text": { "type": ["string"] }, "rich_text": { "type": ["string"] }, - "meta_data": { "type": ["object", "null"] } + "meta_data": { "type": ["object", "null"] }, + "can_receive_suggestion": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 35971d564d5..193ab6821a5 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -119,7 +119,8 @@ "can_push_to_source_branch": { "type": "boolean" }, "rebase_path": { "type": ["string", "null"] }, "squash": { "type": "boolean" }, - "test_reports_path": { "type": ["string", "null"] } + "test_reports_path": { "type": ["string", "null"] }, + "can_receive_suggestion": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/frontend/dummy_spec.js b/spec/frontend/dummy_spec.js deleted file mode 100644 index 2bfef25e9c6..00000000000 --- a/spec/frontend/dummy_spec.js +++ /dev/null @@ -1 +0,0 @@ -it('does nothing', () => {}); diff --git a/spec/javascripts/vue_shared/components/notes/timeline_entry_item_spec.js b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js index c15635f2105..c15635f2105 100644 --- a/spec/javascripts/vue_shared/components/notes/timeline_entry_item_spec.js +++ b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb index 139387e0b24..3820cf5cb9d 100644 --- a/spec/helpers/emails_helper_spec.rb +++ b/spec/helpers/emails_helper_spec.rb @@ -73,4 +73,59 @@ describe EmailsHelper do end end end + + describe '#create_list_id_string' do + using RSpec::Parameterized::TableSyntax + + where(:full_path, :list_id_path) do + "01234" | "01234" + "5/0123" | "012.." + "45/012" | "012.." + "012" | "012" + "23/01" | "01.23" + "2/01" | "01.2" + "234/01" | "01.." + "4/2/0" | "0.2.4" + "45/2/0" | "0.2.." + "5/23/0" | "0.." + "0-2/5" | "5.0-2" + "0_2/5" | "5.0-2" + "0.2/5" | "5.0-2" + end + + with_them do + it 'ellipcizes different variants' do + project = double("project") + allow(project).to receive(:full_path).and_return(full_path) + allow(project).to receive(:id).and_return(12345) + # Set a max length that gives only 5 chars for the project full path + max_length = "12345..#{Gitlab.config.gitlab.host}".length + 5 + list_id = create_list_id_string(project, max_length) + + expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}") + expect(list_id).to satisfy { |s| s.length <= max_length } + end + end + end + + describe 'Create realistic List-Id identifier' do + using RSpec::Parameterized::TableSyntax + + where(:full_path, :list_id_path) do + "gitlab-org/gitlab-ce" | "gitlab-ce.gitlab-org" + "project-name/subproject_name/my.project" | "my-project.subproject-name.project-name" + end + + with_them do + it 'Produces the right List-Id' do + project = double("project") + allow(project).to receive(:full_path).and_return(full_path) + allow(project).to receive(:id).and_return(12345) + list_id = create_list_id_string(project) + + expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}") + expect(list_id).to satisfy { |s| s.length <= 255 } + end + end + end end diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb index cba0d93e144..f405268d198 100644 --- a/spec/helpers/sorting_helper_spec.rb +++ b/spec/helpers/sorting_helper_spec.rb @@ -21,7 +21,11 @@ describe SortingHelper do describe '#issuable_sort_direction_button' do before do - allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: {})) + allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: 'test_label' })) + end + + it 'keeps label filter param' do + expect(issuable_sort_direction_button('created_date')).to include('label_name=test_label') end it 'returns icon with sort-highest when sort is created_date' do diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb index c580b78c908..03df9deafa1 100644 --- a/spec/helpers/storage_helper_spec.rb +++ b/spec/helpers/storage_helper_spec.rb @@ -15,11 +15,7 @@ describe StorageHelper do end it "uses commas as thousands separator" do - if Gitlab.rails5? - expect(helper.storage_counter(100_000_000_000_000_000_000_000)).to eq("86,736.2 EB") - else - expect(helper.storage_counter(100_000_000_000_000_000)).to eq("90,949.5 TB") - end + expect(helper.storage_counter(100_000_000_000_000_000_000_000)).to eq("86,736.2 EB") end end end diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb index 9d4e34abef5..bfec7ad4bba 100644 --- a/spec/helpers/version_check_helper_spec.rb +++ b/spec/helpers/version_check_helper_spec.rb @@ -13,21 +13,21 @@ describe VersionCheckHelper do before do allow(Rails.env).to receive(:production?) { true } allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { true } - allow_any_instance_of(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' } - - @image_tag = helper.version_status_badge + allow(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' } end it 'should return an image tag' do - expect(@image_tag).to match(/^<img/) + expect(helper.version_status_badge).to start_with('<img') end it 'should have a js prefixed css class' do - expect(@image_tag).to match(/class="js-version-status-badge lazy"/) + expect(helper.version_status_badge) + .to match(/class="js-version-status-badge lazy"/) end it 'should have a VersionCheck url as the src' do - expect(@image_tag).to match(%r{src="https://version\.host\.com/check\.svg\?gitlab_info=xxx"}) + expect(helper.version_status_badge) + .to include(%{src="https://version.host.com/check.svg?gitlab_info=xxx"}) end end end diff --git a/spec/javascripts/blob_edit/blob_bundle_spec.js b/spec/javascripts/blob_edit/blob_bundle_spec.js index 759d170af77..57f60a4a3dd 100644 --- a/spec/javascripts/blob_edit/blob_bundle_spec.js +++ b/spec/javascripts/blob_edit/blob_bundle_spec.js @@ -14,6 +14,7 @@ describe('EditBlob', () => { setFixtures(` <div class="js-edit-blob-form"> <button class="js-commit-button"></button> + <a class="btn btn-cancel" href="#"></a> </div>`); blobBundle(); }); @@ -27,4 +28,10 @@ describe('EditBlob', () => { expect(window.onbeforeunload).toBeNull(); }); + + it('removes beforeunload listener when cancel link is clicked', () => { + $('.btn.btn-cancel').click(); + + expect(window.onbeforeunload).toBeNull(); + }); }); diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 54f1edfb1f9..22f192bc7f3 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -65,6 +65,13 @@ describe('Store', () => { expect(list).toBeDefined(); }); + it('finds list by label ID', () => { + boardsStore.addList(listObj); + const list = boardsStore.findListByLabelId(listObj.label.id); + + expect(list.id).toBe(listObj.id); + }); + it('gets issue when new list added', done => { boardsStore.addList(listObj); const list = boardsStore.findList('id', listObj.id); diff --git a/spec/javascripts/boards/components/issue_due_date_spec.js b/spec/javascripts/boards/components/issue_due_date_spec.js index 9e49330c052..054cf8c5b7d 100644 --- a/spec/javascripts/boards/components/issue_due_date_spec.js +++ b/spec/javascripts/boards/components/issue_due_date_spec.js @@ -49,10 +49,11 @@ describe('Issue Due Date component', () => { it('should render month and day for other dates', () => { date.setDate(date.getDate() + 17); vm = createComponent(date); + const today = new Date(); + const isDueInCurrentYear = today.getFullYear() === date.getFullYear(); + const format = isDueInCurrentYear ? 'mmm d' : 'mmm d, yyyy'; - expect(vm.$el.querySelector('time').textContent.trim()).toEqual( - dateFormat(date, 'mmm d', true), - ); + expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, format, true)); }); it('should contain the correct `.text-danger` css class for overdue issue', () => { diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 437ab4bb3df..54fb0e8228b 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -55,15 +55,27 @@ describe('Issue model', () => { expect(issue.labels.length).toBe(2); }); - it('does not add existing label', () => { + it('does not add label if label id exists', () => { + issue.addLabel({ + id: 1, + title: 'test 2', + color: 'blue', + description: 'testing', + }); + + expect(issue.labels.length).toBe(1); + expect(issue.labels[0].color).toBe('red'); + }); + + it('adds other label with same title', () => { issue.addLabel({ id: 2, title: 'test', color: 'blue', - description: 'bugs!', + description: 'other test', }); - expect(issue.labels.length).toBe(1); + expect(issue.labels.length).toBe(2); }); it('finds label', () => { diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 1e2f7ff4fd8..a2cbc0f3c72 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -1,33 +1,44 @@ -import Vue from 'vue'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import { TEST_HOST } from 'spec/test_constants'; import App from '~/diffs/components/app.vue'; +import NoChanges from '~/diffs/components/no_changes.vue'; +import DiffFile from '~/diffs/components/diff_file.vue'; import createDiffsStore from '../create_diffs_store'; describe('diffs/components/app', () => { const oldMrTabs = window.mrTabs; - const Component = Vue.extend(App); - + let store; let vm; - beforeEach(() => { - // setup globals (needed for component to mount :/) - window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']); - window.mrTabs.expandViewContainer = jasmine.createSpy(); - window.location.hash = 'ABC_123'; + function createComponent(props = {}, extendStore = () => {}) { + const localVue = createLocalVue(); - // setup component - const store = createDiffsStore(); + localVue.use(Vuex); + + store = createDiffsStore(); store.state.diffs.isLoading = false; - vm = mountComponentWithStore(Component, { - store, - props: { + extendStore(store); + + vm = shallowMount(localVue.extend(App), { + localVue, + propsData: { endpoint: `${TEST_HOST}/diff/endpoint`, projectPath: 'namespace/project', currentUser: {}, + changesEmptyStateIllustration: '', + ...props, }, + store, }); + } + + beforeEach(() => { + // setup globals (needed for component to mount :/) + window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']); + window.mrTabs.expandViewContainer = jasmine.createSpy(); + window.location.hash = 'ABC_123'; }); afterEach(() => { @@ -35,21 +46,53 @@ describe('diffs/components/app', () => { window.mrTabs = oldMrTabs; // reset component - vm.$destroy(); + vm.destroy(); }); it('does not show commit info', () => { - expect(vm.$el).not.toContainElement('.blob-commit-info'); + createComponent(); + + expect(vm.contains('.blob-commit-info')).toBe(false); }); it('sets highlighted row if hash exists in location object', done => { - vm.$props.shouldShow = true; - - vm.$nextTick() - .then(() => { - expect(vm.$store.state.diffs.highlightedRow).toBe('ABC_123'); - }) - .then(done) - .catch(done.fail); + createComponent({ + shouldShow: true, + }); + + // Component uses $nextTick so we wait until that has finished + setTimeout(() => { + expect(store.state.diffs.highlightedRow).toBe('ABC_123'); + + done(); + }); + }); + + describe('empty state', () => { + it('renders empty state when no diff files exist', () => { + createComponent(); + + expect(vm.contains(NoChanges)).toBe(true); + }); + + it('does not render empty state when diff files exist', () => { + createComponent({}, () => { + store.state.diffs.diffFiles.push({ + id: 1, + }); + }); + + expect(vm.contains(NoChanges)).toBe(false); + expect(vm.findAll(DiffFile).length).toBe(1); + }); + + it('does not render empty state when versions match', () => { + createComponent({}, () => { + store.state.diffs.startVersion = { version_index: 1 }; + store.state.diffs.mergeRequestDiff = { version_index: 1 }; + }); + + expect(vm.contains(NoChanges)).toBe(false); + }); }); }); diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js index c25f6167163..9e158327a77 100644 --- a/spec/javascripts/diffs/components/diff_content_spec.js +++ b/spec/javascripts/diffs/components/diff_content_spec.js @@ -17,6 +17,7 @@ describe('DiffContent', () => { current_user: { can_create_note: false, }, + preview_note_path: 'path/to/preview', }; vm = mountComponentWithStore(Component, { @@ -49,6 +50,45 @@ describe('DiffContent', () => { }); }); + describe('empty files', () => { + beforeEach(() => { + vm.diffFile.empty = true; + vm.diffFile.highlighted_diff_lines = []; + vm.diffFile.parallel_diff_lines = []; + }); + + it('should render a message', done => { + vm.$nextTick(() => { + const block = vm.$el.querySelector('.diff-viewer .nothing-here-block'); + + expect(block).not.toBe(null); + expect(block.textContent.trim()).toContain('Empty file'); + + done(); + }); + }); + + it('should not render multiple messages', done => { + vm.diffFile.mode_changed = true; + vm.diffFile.b_mode = '100755'; + vm.diffFile.viewer.name = 'mode_changed'; + + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.nothing-here-block').length).toBe(1); + + done(); + }); + }); + + it('should not render diff table', done => { + vm.$nextTick(() => { + expect(vm.$el.querySelector('table')).toBe(null); + + done(); + }); + }); + }); + describe('Non-Text diffs', () => { beforeEach(() => { vm.diffFile.viewer.name = 'image'; diff --git a/spec/javascripts/diffs/components/no_changes_spec.js b/spec/javascripts/diffs/components/no_changes_spec.js index 7237274eb43..e45d34bf9d5 100644 --- a/spec/javascripts/diffs/components/no_changes_spec.js +++ b/spec/javascripts/diffs/components/no_changes_spec.js @@ -1 +1,40 @@ -// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { createStore } from '~/mr_notes/stores'; +import NoChanges from '~/diffs/components/no_changes.vue'; + +describe('Diff no changes empty state', () => { + let vm; + + function createComponent(extendStore = () => {}) { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const store = createStore(); + extendStore(store); + + vm = shallowMount(localVue.extend(NoChanges), { + localVue, + store, + propsData: { + changesEmptyStateIllustration: '', + }, + }); + } + + afterEach(() => { + vm.destroy(); + }); + + it('prevents XSS', () => { + createComponent(store => { + // eslint-disable-next-line no-param-reassign + store.state.notes.noteableData = { + source_branch: '<script>alert("test");</script>', + target_branch: '<script>alert("test");</script>', + }; + }); + + expect(vm.contains('script')).toBe(false); + }); +}); diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index 44313caba29..c1e9f791925 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -487,8 +487,19 @@ export default { ], }, diff_discussion: true, - truncated_diff_lines: - '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n', + truncated_diff_lines: [ + { + text: 'line', + rich_text: + '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n', + can_receive_suggestion: true, + line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1', + type: 'new', + old_line: null, + new_line: 1, + meta_data: null, + }, + ], }; export const imageDiffDiscussions = [ diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index 23e8761bc55..f3449bec6ec 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -277,6 +277,87 @@ describe('DiffsStoreMutations', () => { expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1); }); + it('updates existing discussion', () => { + const diffPosition = { + base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', + head_sha: 'b921914f9a834ac47e6fd9420f78db0f83559130', + new_line: null, + new_path: '500-lines-4.txt', + old_line: 5, + old_path: '500-lines-4.txt', + start_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', + }; + + const state = { + latestDiff: true, + diffFiles: [ + { + file_hash: 'ABC', + parallel_diff_lines: [ + { + left: { + line_code: 'ABC_1', + discussions: [], + }, + right: { + line_code: 'ABC_1', + discussions: [], + }, + }, + ], + highlighted_diff_lines: [ + { + line_code: 'ABC_1', + discussions: [], + }, + ], + }, + ], + }; + const discussion = { + id: 1, + line_code: 'ABC_1', + diff_discussion: true, + resolvable: true, + original_position: diffPosition, + position: diffPosition, + diff_file: { + file_hash: state.diffFiles[0].file_hash, + }, + }; + + const diffPositionByLineCode = { + ABC_1: diffPosition, + }; + + mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { + discussion, + diffPositionByLineCode, + }); + + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1); + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1); + expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]); + + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1); + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1); + + mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { + discussion: { + ...discussion, + resolved: true, + notes: ['test'], + }, + diffPositionByLineCode, + }); + + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].notes.length).toBe(1); + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].notes.length).toBe(1); + + expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].resolved).toBe(true); + expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].resolved).toBe(true); + }); + it('should add legacy discussions to the given line', () => { const diffPosition = { base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910', @@ -356,10 +437,12 @@ describe('DiffsStoreMutations', () => { { id: 1, line_code: 'ABC_1', + notes: [], }, { id: 2, line_code: 'ABC_1', + notes: [], }, ], }, @@ -376,10 +459,12 @@ describe('DiffsStoreMutations', () => { { id: 1, line_code: 'ABC_1', + notes: [], }, { id: 2, line_code: 'ABC_1', + notes: [], }, ], }, diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index 6605b0a30d7..cfd0b96ec43 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -211,132 +211,6 @@ describe('Dropdown Utils', () => { }); }); - describe('mergeDuplicateLabels', () => { - const dataMap = { - label: { - title: 'label', - color: '#FFFFFF', - }, - }; - - it('should add label to dataMap if it is not a duplicate', () => { - const newLabel = { - title: 'new-label', - color: '#000000', - }; - - const updated = DropdownUtils.mergeDuplicateLabels(dataMap, newLabel); - - expect(updated[newLabel.title]).toEqual(newLabel); - }); - - it('should merge colors if label is a duplicate', () => { - const duplicate = { - title: 'label', - color: '#000000', - }; - - const updated = DropdownUtils.mergeDuplicateLabels(dataMap, duplicate); - - expect(updated.label.multipleColors).toEqual([dataMap.label.color, duplicate.color]); - }); - }); - - describe('duplicateLabelColor', () => { - it('should linear-gradient 2 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000']); - - expect(gradient).toEqual( - 'linear-gradient(#FFFFFF 0%, #FFFFFF 50%, #000000 50%, #000000 100%)', - ); - }); - - it('should linear-gradient 3 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333']); - - expect(gradient).toEqual( - 'linear-gradient(#FFFFFF 0%, #FFFFFF 33%, #000000 33%, #000000 66%, #333333 66%, #333333 100%)', - ); - }); - - it('should linear-gradient 4 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor([ - '#FFFFFF', - '#000000', - '#333333', - '#DDDDDD', - ]); - - expect(gradient).toEqual( - 'linear-gradient(#FFFFFF 0%, #FFFFFF 25%, #000000 25%, #000000 50%, #333333 50%, #333333 75%, #DDDDDD 75%, #DDDDDD 100%)', - ); - }); - - it('should not linear-gradient more than 4 colors', () => { - const gradient = DropdownUtils.duplicateLabelColor([ - '#FFFFFF', - '#000000', - '#333333', - '#DDDDDD', - '#EEEEEE', - ]); - - expect(gradient.indexOf('#EEEEEE')).toBe(-1); - }); - }); - - describe('duplicateLabelPreprocessing', () => { - it('should set preprocessed to true', () => { - const results = DropdownUtils.duplicateLabelPreprocessing([]); - - expect(results.preprocessed).toEqual(true); - }); - - it('should not mutate existing data if there are no duplicates', () => { - const data = [ - { - title: 'label1', - color: '#FFFFFF', - }, - { - title: 'label2', - color: '#000000', - }, - ]; - const results = DropdownUtils.duplicateLabelPreprocessing(data); - - expect(results.length).toEqual(2); - expect(results[0]).toEqual(data[0]); - expect(results[1]).toEqual(data[1]); - }); - - describe('duplicate labels', () => { - const data = [ - { - title: 'label', - color: '#FFFFFF', - }, - { - title: 'label', - color: '#000000', - }, - ]; - const results = DropdownUtils.duplicateLabelPreprocessing(data); - - it('should merge duplicate labels', () => { - expect(results.length).toEqual(1); - }); - - it('should convert multiple colored labels into linear-gradient', () => { - expect(results[0].color).toEqual(DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000'])); - }); - - it('should set multiple colored label text color to black', () => { - expect(results[0].text_color).toEqual('#000000'); - }); - }); - }); - describe('setDataValueIfSelected', () => { beforeEach(() => { spyOn(FilteredSearchDropdownManager, 'addWordToInput').and.callFake(() => {}); diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index 4f561df7943..9aa3cbaa231 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -909,16 +909,6 @@ describe('Filtered Search Visual Tokens', () => { expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor); }); - it('should not set backgroundColor when it is a linear-gradient', () => { - const token = subject.setTokenStyle( - bugLabelToken, - 'linear-gradient(135deg, red, blue)', - 'white', - ); - - expect(token.style.backgroundColor).toEqual(bugLabelToken.style.backgroundColor); - }); - it('should set textColor', () => { const token = subject.setTokenStyle(bugLabelToken, 'white', 'black'); @@ -935,39 +925,6 @@ describe('Filtered Search Visual Tokens', () => { }); }); - describe('preprocessLabel', () => { - const endpoint = 'endpoint'; - - it('does not preprocess more than once', () => { - let labels = []; - - spyOn(DropdownUtils, 'duplicateLabelPreprocessing').and.callFake(() => []); - - labels = FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); - FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); - - expect(DropdownUtils.duplicateLabelPreprocessing.calls.count()).toEqual(1); - }); - - describe('not preprocessed before', () => { - it('returns preprocessed labels', () => { - let labels = []; - - expect(labels.preprocessed).not.toEqual(true); - labels = FilteredSearchVisualTokens.preprocessLabel(endpoint, labels); - - expect(labels.preprocessed).toEqual(true); - }); - - it('overrides AjaxCache with preprocessed results', () => { - spyOn(AjaxCache, 'override').and.callFake(() => {}); - FilteredSearchVisualTokens.preprocessLabel(endpoint, []); - - expect(AjaxCache.override.calls.count()).toEqual(1); - }); - }); - }); - describe('updateLabelTokenColor', () => { const jsonFixtureName = 'labels/project_labels.json'; const dummyEndpoint = '/dummy/endpoint'; diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index 0081f42c330..22bee049f9c 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -30,6 +30,8 @@ describe('note_app', () => { jasmine.addMatchers(vueMatchers); $('body').attr('data-page', 'projects:merge_requests:show'); + setFixtures('<div class="js-vue-notes-event"><div id="app"></div></div>'); + const IssueNotesApp = Vue.extend(notesApp); store = createStore(); @@ -43,6 +45,7 @@ describe('note_app', () => { return mountComponentWithStore(IssueNotesApp, { props, store, + el: document.getElementById('app'), }); }; }); @@ -283,4 +286,24 @@ describe('note_app', () => { }, 0); }); }); + + describe('emoji awards', () => { + it('dispatches toggleAward after toggleAward event', () => { + const toggleAwardEvent = new CustomEvent('toggleAward', { + detail: { + awardName: 'test', + noteId: 1, + }, + }); + + spyOn(vm.$store, 'dispatch'); + + vm.$el.parentElement.dispatchEvent(toggleAwardEvent); + + expect(vm.$store.dispatch).toHaveBeenCalledWith('toggleAward', { + awardName: 'test', + noteId: 1, + }); + }); + }); }); diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js new file mode 100644 index 00000000000..c0cd15b7507 --- /dev/null +++ b/spec/javascripts/releases/components/release_block_spec.js @@ -0,0 +1,148 @@ +import Vue from 'vue'; +import component from '~/releases/components/release_block.vue'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Release block', () => { + const Component = Vue.extend(component); + + const release = { + name: 'Bionic Beaver', + tag_name: '18.04', + description: '## changelog\n\n* line 1\n* line2', + description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', + author_name: 'Release bot', + author_email: 'release-bot@example.com', + created_at: '2012-05-28T05:00:00-07:00', + commit: { + id: '2695effb5807a22ff3d138d593fd856244e155e7', + short_id: '2695effb', + title: 'Initial commit', + created_at: '2017-07-26T11:08:53.000+02:00', + parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'], + message: 'Initial commit', + author_name: 'John Smith', + author_email: 'john@example.com', + authored_date: '2012-05-28T04:42:42-07:00', + committer_name: 'Jack Smith', + committer_email: 'jack@example.com', + committed_date: '2012-05-28T04:42:42-07:00', + }, + assets: { + count: 6, + sources: [ + { + format: 'zip', + url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip', + }, + { + format: 'tar.gz', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz', + }, + { + format: 'tar.bz2', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2', + }, + { + format: 'tar', + url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar', + }, + ], + links: [ + { + name: 'release-18.04.dmg', + url: 'https://my-external-hosting.example.com/scrambled-url/', + external: true, + }, + { + name: 'binary-linux-amd64', + url: + 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50', + external: false, + }, + ], + }, + }; + + const props = { + name: release.name, + tag: release.tag_name, + commit: release.commit, + description: release.description_html, + author: { + avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', + id: 482476, + name: 'John Doe', + path: '/johndoe', + state: 'active', + status_tooltip_html: null, + username: 'johndoe', + web_url: 'https://gitlab.com/johndoe', + }, + createdAt: release.created_at, + assetsCount: release.assets.count, + sources: release.assets.sources, + links: release.assets.links, + }; + + let vm; + + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders release name', () => { + expect(vm.$el.textContent).toContain(release.name); + }); + + it('renders commit sha', () => { + expect(vm.$el.textContent).toContain(release.commit.short_id); + }); + + it('renders tag name', () => { + expect(vm.$el.textContent).toContain(release.tag_name); + }); + + it('renders release date', () => { + expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.created_at)); + }); + + it('renders number of assets provided', () => { + expect(vm.$el.querySelector('.js-assets-count').textContent).toContain(release.assets.count); + }); + + it('renders dropdown with the sources', () => { + expect(vm.$el.querySelectorAll('.js-sources-dropdown li').length).toEqual( + release.assets.sources.length, + ); + + expect(vm.$el.querySelector('.js-sources-dropdown li a').getAttribute('href')).toEqual( + release.assets.sources[0].url, + ); + + expect(vm.$el.querySelector('.js-sources-dropdown li a').textContent).toContain( + release.assets.sources[0].format, + ); + }); + + it('renders list with the links provided', () => { + expect(vm.$el.querySelectorAll('.js-assets-list li').length).toEqual( + release.assets.links.length, + ); + + expect(vm.$el.querySelector('.js-assets-list li a').getAttribute('href')).toEqual( + release.assets.links[0].url, + ); + + expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain( + release.assets.links[0].name, + ); + }); +}); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js index 300133dc602..212519743aa 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js @@ -114,7 +114,7 @@ describe('Merge request widget rebase component', () => { // Wait for the eventHub to be called .then(vm.$nextTick()) .then(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess'); }) .then(done) .catch(done.fail); diff --git a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js index 9d34bdd1084..61ef26cd080 100644 --- a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js +++ b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js @@ -35,7 +35,7 @@ describe('getStateKey', () => { expect(bound()).toEqual('mergeWhenPipelineSucceeds'); - context.hasSHAChanged = true; + context.isSHAMismatch = true; expect(bound()).toEqual('shaMismatch'); diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js index f5079147f60..c226704694c 100644 --- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js +++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js @@ -3,23 +3,30 @@ import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; import mockData from '../mock_data'; describe('MergeRequestStore', () => { - describe('setData', () => { - let store; + let store; - beforeEach(() => { - store = new MergeRequestStore(mockData); - }); + beforeEach(() => { + store = new MergeRequestStore(mockData); + }); - it('should set hasSHAChanged when the diff SHA changes', () => { + describe('setData', () => { + it('should set isSHAMismatch when the diff SHA changes', () => { store.setData({ ...mockData, diff_head_sha: 'a-different-string' }); - expect(store.hasSHAChanged).toBe(true); + expect(store.isSHAMismatch).toBe(true); }); - it('should not set hasSHAChanged when other data changes', () => { + it('should not set isSHAMismatch when other data changes', () => { store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress }); - expect(store.hasSHAChanged).toBe(false); + expect(store.isSHAMismatch).toBe(false); + }); + + it('should update cached sha after rebasing', () => { + store.setData({ ...mockData, diff_head_sha: 'abc123' }, true); + + expect(store.isSHAMismatch).toBe(false); + expect(store.sha).toBe('abc123'); }); describe('isPipelinePassing', () => { diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js index abb17440c0e..79e0e756a7a 100644 --- a/spec/javascripts/vue_shared/components/markdown/field_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js @@ -80,7 +80,7 @@ describe('Markdown field component', () => { previewLink.click(); Vue.nextTick(() => { - expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading...'); + expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading…'); done(); }); diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index 59613faa49f..e733a95288e 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -28,6 +28,7 @@ describe('Markdown field header component', () => { 'Add a numbered list', 'Add a task list', 'Add a table', + 'Insert suggestion', 'Go full screen', ]; const elements = vm.$el.querySelectorAll('.toolbar-btn'); @@ -93,4 +94,18 @@ describe('Markdown field header component', () => { '| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |', ); }); + + it('renders suggestion template', () => { + vm.lineContent = 'Some content'; + + expect(vm.mdSuggestion).toEqual('```suggestion\n{text}\n```'); + }); + + it('does not render suggestion button if `canSuggest` is set to false', () => { + vm.canSuggest = false; + + Vue.nextTick(() => { + expect(vm.$el.querySelector('.qa-suggestion-btn')).toBe(null); + }); + }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js new file mode 100644 index 00000000000..8187b3204b1 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -0,0 +1,69 @@ +import Vue from 'vue'; +import SuggestionDiffHeaderComponent from '~/vue_shared/components/markdown/suggestion_diff_header.vue'; + +const MOCK_DATA = { + canApply: true, + isApplied: false, + helpPagePath: 'path_to_docs', +}; + +describe('Suggestion Diff component', () => { + let vm; + + function createComponent(propsData) { + const Component = Vue.extend(SuggestionDiffHeaderComponent); + + return new Component({ + propsData, + }).$mount(); + } + + beforeEach(done => { + vm = createComponent(MOCK_DATA); + Vue.nextTick(done); + }); + + describe('init', () => { + it('renders a suggestion header', () => { + const header = vm.$el.querySelector('.qa-suggestion-diff-header'); + + expect(header).not.toBeNull(); + expect(header.innerHTML.includes('Suggested change')).toBe(true); + }); + + it('renders an apply button', () => { + const applyBtn = vm.$el.querySelector('.qa-apply-btn'); + + expect(applyBtn).not.toBeNull(); + expect(applyBtn.innerHTML.includes('Apply suggestion')).toBe(true); + }); + + it('does not render an apply button if `canApply` is set to false', () => { + const props = Object.assign(MOCK_DATA, { canApply: false }); + + vm = createComponent(props); + + expect(vm.$el.querySelector('.qa-apply-btn')).toBeNull(); + }); + }); + + describe('applySuggestion', () => { + it('emits when the apply button is clicked', () => { + const props = Object.assign(MOCK_DATA, { canApply: true }); + + vm = createComponent(props); + spyOn(vm, '$emit'); + vm.applySuggestion(); + + expect(vm.$emit).toHaveBeenCalled(); + }); + + it('does not emit when the canApply is set to false', () => { + spyOn(vm, '$emit'); + vm.canApply = false; + vm.applySuggestion(); + + expect(vm.$emit).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js new file mode 100644 index 00000000000..d4ed8f2f7a4 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js @@ -0,0 +1,79 @@ +import Vue from 'vue'; +import SuggestionDiffComponent from '~/vue_shared/components/markdown/suggestion_diff.vue'; + +const MOCK_DATA = { + canApply: true, + newLines: [ + { content: 'Line 1\n', lineNumber: 1 }, + { content: 'Line 2\n', lineNumber: 2 }, + { content: 'Line 3\n', lineNumber: 3 }, + ], + fromLine: 1, + fromContent: 'Old content', + suggestion: { + id: 1, + }, + helpPagePath: 'path_to_docs', +}; + +describe('Suggestion Diff component', () => { + let vm; + + beforeEach(done => { + const Component = Vue.extend(SuggestionDiffComponent); + + vm = new Component({ + propsData: MOCK_DATA, + }).$mount(); + + Vue.nextTick(done); + }); + + describe('init', () => { + it('renders a suggestion header', () => { + expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull(); + }); + + it('renders a diff table', () => { + expect(vm.$el.querySelector('table.md-suggestion-diff')).not.toBeNull(); + }); + + it('renders the oldLineNumber', () => { + const fromLine = vm.$el.querySelector('.qa-old-diff-line-number').innerHTML; + + expect(parseInt(fromLine, 10)).toBe(vm.fromLine); + }); + + it('renders the oldLineContent', () => { + const fromContent = vm.$el.querySelector('.line_content.old').innerHTML; + + expect(fromContent.includes(vm.fromContent)).toBe(true); + }); + + it('renders the contents of newLines', () => { + const newLines = vm.$el.querySelectorAll('.line_holder.new'); + + newLines.forEach((line, i) => { + expect(newLines[i].innerHTML.includes(vm.newLines[i].content)).toBe(true); + }); + }); + + it('renders a line number for each line', () => { + const newLineNumbers = vm.$el.querySelectorAll('.qa-new-diff-line-number'); + + newLineNumbers.forEach((line, i) => { + expect(newLineNumbers[i].innerHTML.includes(vm.newLines[i].lineNumber)).toBe(true); + }); + }); + }); + + describe('applySuggestion', () => { + it('emits apply event when applySuggestion is called', () => { + const callback = () => {}; + spyOn(vm, '$emit'); + vm.applySuggestion(callback); + + expect(vm.$emit).toHaveBeenCalledWith('apply', { suggestionId: vm.suggestion.id, callback }); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js new file mode 100644 index 00000000000..ab1b747c360 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js @@ -0,0 +1,125 @@ +import Vue from 'vue'; +import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue'; + +const MOCK_DATA = { + fromLine: 1, + fromContent: 'Old content', + suggestions: [], + noteHtml: ` + <div class="suggestion"> + <div class="line">Suggestion 1</div> + </div> + + <div class="suggestion"> + <div class="line">Suggestion 2</div> + </div> + `, + isApplied: false, + helpPagePath: 'path_to_docs', +}; + +const generateLine = content => { + const line = document.createElement('div'); + line.className = 'line'; + line.innerHTML = content; + + return line; +}; + +const generateMockLines = () => { + const line1 = generateLine('Line 1'); + const line2 = generateLine('Line 2'); + const line3 = generateLine('Line 3'); + const container = document.createElement('div'); + + container.appendChild(line1); + container.appendChild(line2); + container.appendChild(line3); + + return container; +}; + +describe('Suggestion component', () => { + let vm; + let extractedLines; + let diffTable; + + beforeEach(done => { + const Component = Vue.extend(SuggestionsComponent); + + vm = new Component({ + propsData: MOCK_DATA, + }).$mount(); + + extractedLines = vm.extractNewLines(generateMockLines()); + diffTable = vm.generateDiff(extractedLines).$mount().$el; + + spyOn(vm, 'renderSuggestions'); + vm.renderSuggestions(); + Vue.nextTick(done); + }); + + describe('mounted', () => { + it('renders a flash container', () => { + expect(vm.$el.querySelector('.flash-container')).not.toBeNull(); + }); + + it('renders a container for suggestions', () => { + expect(vm.$refs.container).not.toBeNull(); + }); + + it('renders suggestions', () => { + expect(vm.renderSuggestions).toHaveBeenCalled(); + expect(vm.$el.innerHTML.includes('Suggestion 1')).toBe(true); + expect(vm.$el.innerHTML.includes('Suggestion 2')).toBe(true); + }); + }); + + describe('extractNewLines', () => { + it('extracts suggested lines', () => { + const expectedReturn = [ + { content: 'Line 1\n', lineNumber: 1 }, + { content: 'Line 2\n', lineNumber: 2 }, + { content: 'Line 3\n', lineNumber: 3 }, + ]; + + expect(vm.extractNewLines(generateMockLines())).toEqual(expectedReturn); + }); + + it('increments line number for each extracted line', () => { + expect(extractedLines[0].lineNumber).toEqual(1); + expect(extractedLines[1].lineNumber).toEqual(2); + expect(extractedLines[2].lineNumber).toEqual(3); + }); + + it('returns empty array if no lines are found', () => { + const el = document.createElement('div'); + + expect(vm.extractNewLines(el)).toEqual([]); + }); + }); + + describe('generateDiff', () => { + it('generates a diff table', () => { + expect(diffTable.querySelector('.md-suggestion-diff')).not.toBeNull(); + }); + + it('generates a diff table that contains contents of `oldLineContent`', () => { + expect(diffTable.innerHTML.includes(vm.fromContent)).toBe(true); + }); + + it('generates a diff table that contains contents the suggested lines', () => { + extractedLines.forEach((line, i) => { + expect(diffTable.innerHTML.includes(extractedLines[i].content)).toBe(true); + }); + }); + + it('generates a diff table with the correct line number for each suggested line', () => { + const lines = diffTable.getElementsByClassName('qa-new-diff-line-number'); + + expect([...lines][0].innerHTML).toBe('1'); + expect([...lines][1].innerHTML).toBe('2'); + expect([...lines][2].innerHTML).toBe('3'); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js index 1578b0f81f9..e16ab156679 100644 --- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js @@ -101,6 +101,19 @@ describe('User Popover Component', () => { expect(vm.$el.textContent).toContain('Engineer at GitLab'); }); + + it('should not encode special characters when we have bio and organization', () => { + const testProps = Object.assign({}, DEFAULT_PROPS); + testProps.user.bio = 'Manager & Team Lead'; + testProps.user.organization = 'GitLab'; + + vm = mountComponent(UserPopover, { + ...DEFAULT_PROPS, + target: document.querySelector('.js-user-link'), + }); + + expect(vm.$el.textContent).toContain('Manager & Team Lead at GitLab'); + }); }); describe('status data', () => { diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index fdeea814bb2..5ace5c5b1a2 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -67,6 +67,19 @@ describe Backup::Repository do end end end + + context 'restoring object pools' do + it 'schedules restoring of the pool' do + pool_repository = create(:pool_repository, :failed) + pool_repository.delete_object_pool + + subject.restore + + pool_repository.reload + expect(pool_repository).not_to be_failed + expect(pool_repository.object_pool.exists?).to be(true) + end + end end describe '#prepare_directories', :seed_helper do diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb new file mode 100644 index 00000000000..55a141bf315 --- /dev/null +++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::Filter::SuggestionFilter do + include FilterSpecHelper + + let(:input) { "<pre class='code highlight js-syntax-highlight suggestion'><code>foo\n</code></pre>" } + let(:default_context) do + { suggestions_filter_enabled: true } + end + + it 'includes `js-render-suggestion` class' do + doc = filter(input, default_context) + result = doc.css('code').first + + expect(result[:class]).to include('js-render-suggestion') + end + + it 'includes no `js-render-suggestion` when feature disabled' do + stub_feature_flags(diff_suggestions: false) + + doc = filter(input, default_context) + result = doc.css('code').first + + expect(result[:class]).to be_nil + end + + it 'includes no `js-render-suggestion` when filter is disabled' do + doc = filter(input) + result = doc.css('code').first + + expect(result[:class]).to be_nil + end +end diff --git a/spec/lib/banzai/suggestions_parser_spec.rb b/spec/lib/banzai/suggestions_parser_spec.rb new file mode 100644 index 00000000000..79658d710ce --- /dev/null +++ b/spec/lib/banzai/suggestions_parser_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Banzai::SuggestionsParser do + describe '.parse' do + it 'returns a list of suggestion contents' do + markdown = <<-MARKDOWN.strip_heredoc + ```suggestion + foo + bar + ``` + + ``` + nothing + ``` + + ```suggestion + xpto + baz + ``` + + ```thing + this is not a suggestion, it's a thing + ``` + MARKDOWN + + expect(described_class.parse(markdown)).to eq([" foo\n bar", + " xpto\n baz"]) + end + end +end diff --git a/spec/lib/constraints/feature_constrainer_spec.rb b/spec/lib/constraints/feature_constrainer_spec.rb new file mode 100644 index 00000000000..42efc164f81 --- /dev/null +++ b/spec/lib/constraints/feature_constrainer_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Constraints::FeatureConstrainer do + describe '#matches' do + it 'calls Feature.enabled? with the correct arguments' do + expect(Feature).to receive(:enabled?).with(:feature_name, "an object", default_enabled: true) + + described_class.new(:feature_name, "an object", default_enabled: true).matches?(double('request')) + end + end +end diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb index 5ce84c61042..7c7e58d6bb7 100644 --- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb @@ -6,8 +6,18 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201 let(:stages) { table(:ci_stages) } let(:jobs) { table(:ci_builds) } - STATUSES = { created: 0, pending: 1, running: 2, success: 3, - failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze + let(:statuses) do + { + created: 0, + pending: 1, + running: 2, + success: 3, + failed: 4, + canceled: 5, + skipped: 6, + manual: 7 + } + end before do projects.create!(id: 123, name: 'gitlab', path: 'gitlab-ce') @@ -36,9 +46,9 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201 expect(stages.all.pluck(:name)).to match_array %w[test build deploy] expect(jobs.where(stage_id: nil)).to be_one expect(jobs.find_by(stage_id: nil).id).to eq 6 - expect(stages.all.pluck(:status)).to match_array [STATUSES[:success], - STATUSES[:failed], - STATUSES[:pending]] + expect(stages.all.pluck(:status)).to match_array [statuses[:success], + statuses[:failed], + statuses[:pending]] end it 'recovers from unique constraint violation only twice' do diff --git a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb index 878158910be..89b56906ed0 100644 --- a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20170711145320 do @@ -6,8 +8,18 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 let(:stages) { table(:ci_stages) } let(:jobs) { table(:ci_builds) } - STATUSES = { created: 0, pending: 1, running: 2, success: 3, - failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze + let(:statuses) do + { + created: 0, + pending: 1, + running: 2, + success: 3, + failed: 4, + canceled: 5, + skipped: 6, + manual: 7 + } + end before do projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1') @@ -26,8 +38,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 it 'sets a correct stage status' do described_class.new.perform(1, 2) - expect(stages.first.status).to eq STATUSES[:running] - expect(stages.second.status).to eq STATUSES[:failed] + expect(stages.first.status).to eq statuses[:running] + expect(stages.second.status).to eq statuses[:failed] end end @@ -35,8 +47,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 it 'sets a skipped stage status' do described_class.new.perform(1, 2) - expect(stages.first.status).to eq STATUSES[:skipped] - expect(stages.second.status).to eq STATUSES[:skipped] + expect(stages.first.status).to eq statuses[:skipped] + expect(stages.second.status).to eq statuses[:skipped] end end @@ -50,8 +62,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 it 'sets a correct stage status' do described_class.new.perform(1, 2) - expect(stages.first.status).to eq STATUSES[:canceled] - expect(stages.second.status).to eq STATUSES[:success] + expect(stages.first.status).to eq statuses[:canceled] + expect(stages.second.status).to eq statuses[:success] end end @@ -65,8 +77,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20 it 'sets a correct stage status' do described_class.new.perform(1, 2) - expect(stages.first.status).to eq STATUSES[:manual] - expect(stages.second.status).to eq STATUSES[:success] + expect(stages.first.status).to eq statuses[:manual] + expect(stages.second.status).to eq statuses[:success] end end diff --git a/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb deleted file mode 100644 index d036bf2f4d1..00000000000 --- a/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Ci::Config::Entry::ExceptPolicy do - let(:entry) { described_class.new(config) } - - it_behaves_like 'correct only except policy' - - describe '.default' do - it 'does not have a default value' do - expect(described_class.default).to be_nil - end - end -end diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 12f4b9dc624..61d78f86b51 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -161,7 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do variables: { 'VAR' => 'value' }, ignore: false, after_script: ['make clean'], - only: { refs: %w[branches tags] } }, + only: { refs: %w[branches tags] }, + except: {} }, spinach: { name: :spinach, before_script: [], script: %w[spinach], @@ -173,7 +174,8 @@ describe Gitlab::Ci::Config::Entry::Global do variables: {}, ignore: false, after_script: ['make clean'], - only: { refs: %w[branches tags] } } + only: { refs: %w[branches tags] }, + except: {} } ) end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index c1f4a060063..8e32cede3b5 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -259,7 +259,8 @@ describe Gitlab::Ci::Config::Entry::Job do stage: 'test', ignore: false, after_script: %w[cleanup], - only: { refs: %w[branches tags] }) + only: { refs: %w[branches tags] }, + except: {}) end end end diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index 2a753408f54..1a2c30d3571 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -68,13 +68,15 @@ describe Gitlab::Ci::Config::Entry::Jobs do commands: 'rspec', ignore: false, stage: 'test', - only: { refs: %w[branches tags] } }, + only: { refs: %w[branches tags] }, + except: {} }, spinach: { name: :spinach, script: %w[spinach], commands: 'spinach', ignore: false, stage: 'test', - only: { refs: %w[branches tags] } }) + only: { refs: %w[branches tags] }, + except: {} }) end end diff --git a/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb deleted file mode 100644 index 5518b68e51a..00000000000 --- a/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::Ci::Config::Entry::OnlyPolicy do - let(:entry) { described_class.new(config) } - - it_behaves_like 'correct only except policy' - - describe '.default' do - it 'haa a default value' do - expect(described_class.default).to eq( { refs: %w[branches tags] } ) - end - end -end diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index cf40a22af2e..83001b7fdd8 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -1,8 +1,173 @@ -require 'spec_helper' +require 'fast_spec_helper' +require_dependency 'active_model' describe Gitlab::Ci::Config::Entry::Policy do let(:entry) { described_class.new(config) } + context 'when using simplified policy' do + describe 'validations' do + context 'when entry config value is valid' do + context 'when config is a branch or tag name' do + let(:config) { %w[master feature/branch] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns refs hash' do + expect(entry.value).to eq(refs: config) + end + end + end + + context 'when config is a regexp' do + let(:config) { ['/^issue-.*$/'] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when config is a special keyword' do + let(:config) { %w[tags triggers branches] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + end + + context 'when entry value is not valid' do + let(:config) { [1] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include /policy config should be an array of strings or regexps/ + end + end + end + end + end + + context 'when using complex policy' do + context 'when specifying refs policy' do + let(:config) { { refs: ['master'] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(refs: %w[master]) + end + end + + context 'when specifying kubernetes policy' do + let(:config) { { kubernetes: 'active' } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(kubernetes: 'active') + end + end + + context 'when specifying invalid kubernetes policy' do + let(:config) { { kubernetes: 'something' } } + + it 'reports an error about invalid policy' do + expect(entry.errors).to include /unknown value: something/ + end + end + + context 'when specifying valid variables expressions policy' do + let(:config) { { variables: ['$VAR == null'] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when specifying variables expressions in invalid format' do + let(:config) { { variables: '$MY_VAR' } } + + it 'reports an error about invalid format' do + expect(entry.errors).to include /should be an array of strings/ + end + end + + context 'when specifying invalid variables expressions statement' do + let(:config) { { variables: ['$MY_VAR =='] } } + + it 'reports an error about invalid statement' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when specifying invalid variables expressions token' do + let(:config) { { variables: ['$MY_VAR == 123'] } } + + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when using invalid variables expressions regexp' do + let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } + + it 'reports an error about invalid expression' do + expect(entry.errors).to include /invalid expression syntax/ + end + end + + context 'when specifying a valid changes policy' do + let(:config) { { changes: %w[some/* paths/**/*.rb] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when changes policy is invalid' do + let(:config) { { changes: [1, 2] } } + + it 'returns errors' do + expect(entry.errors).to include /changes should be an array of strings/ + end + end + + context 'when specifying unknown policy' do + let(:config) { { refs: ['master'], invalid: :something } } + + it 'returns error about invalid key' do + expect(entry.errors).to include /unknown keys: invalid/ + end + end + + context 'when policy is empty' do + let(:config) { {} } + + it 'is not a valid configuration' do + expect(entry.errors).to include /can't be blank/ + end + end + end + + context 'when policy strategy does not match' do + let(:config) { 'string strategy' } + + it 'returns information about errors' do + expect(entry.errors) + .to include /has to be either an array of conditions or a hash/ + end + end + describe '.default' do it 'does not have a default value' do expect(described_class.default).to be_nil diff --git a/spec/lib/gitlab/ci/parsers/test_spec.rb b/spec/lib/gitlab/ci/parsers_spec.rb index 0b85b432677..4b647bffe59 100644 --- a/spec/lib/gitlab/ci/parsers/test_spec.rb +++ b/spec/lib/gitlab/ci/parsers_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'spec_helper' -describe Gitlab::Ci::Parsers::Test do +describe Gitlab::Ci::Parsers do describe '.fabricate!' do subject { described_class.fabricate!(file_type) } @@ -8,7 +10,7 @@ describe Gitlab::Ci::Parsers::Test do let(:file_type) { 'junit' } it 'fabricates the class' do - is_expected.to be_a(described_class::Junit) + is_expected.to be_a(described_class::Test::Junit) end end @@ -16,7 +18,7 @@ describe Gitlab::Ci::Parsers::Test do let(:file_type) { 'undefined' } it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Ci::Parsers::Test::ParserNotFoundError) + expect { subject }.to raise_error(Gitlab::Ci::Parsers::ParserNotFoundError) end end end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 23f27939dd2..4e83b27e4a5 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -1338,12 +1338,7 @@ describe Gitlab::Database::MigrationHelpers do end describe '#index_exists_by_name?' do - # TODO: remove rails5-only after removing rails4 tests - # rails 4 can not handle multiple indexes on the same column set if - # index was added by 't.index' - t.index is used by default in schema.rb in - # rails 5. Let's run this test only in rails 5 env: - # see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21492#note_113602758 - it 'returns true if an index exists', :rails5 do + it 'returns true if an index exists' do expect(model.index_exists_by_name?(:projects, 'index_projects_on_path')) .to be_truthy end diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index fc295b2deff..60106ee3c0b 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -400,13 +400,8 @@ describe Gitlab::Database do describe '.cached_table_exists?' do it 'only retrieves data once per table' do - if Gitlab.rails5? - expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:projects).once.and_call_original - expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:bogus_table_name).once.and_call_original - else - expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original - expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original - end + expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:projects).once.and_call_original + expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:bogus_table_name).once.and_call_original 2.times do expect(described_class.cached_table_exists?(:projects)).to be_truthy @@ -462,8 +457,7 @@ describe Gitlab::Database do expect(described_class.db_read_only?).to be_truthy end - # TODO: remove rails5-only tag after removing rails4 tests - it 'detects a read only database', :rails5 do + it 'detects a read only database' do allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }]) expect(described_class.db_read_only?).to be_truthy @@ -475,8 +469,7 @@ describe Gitlab::Database do expect(described_class.db_read_only?).to be_falsey end - # TODO: remove rails5-only tag after removing rails4 tests - it 'detects a read write database', :rails5 do + it 'detects a read write database' do allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }]) expect(described_class.db_read_only?).to be_falsey diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 3417896e259..b15d22c634a 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -583,6 +583,12 @@ describe Gitlab::Diff::File do end end + describe '#empty?' do + it 'returns true' do + expect(diff_file.empty?).to be_truthy + end + end + describe '#different_type?' do it 'returns false' do expect(diff_file).not_to be_different_type @@ -662,4 +668,87 @@ describe Gitlab::Diff::File do end end end + + describe '#empty?' do + let(:project) do + create(:project, :custom_repo, files: {}) + end + let(:branch_name) { 'master' } + + def create_file(file_name, content) + Files::CreateService.new( + project, + project.owner, + commit_message: 'Update', + start_branch: branch_name, + branch_name: branch_name, + file_path: file_name, + file_content: content + ).execute + + project.commit(branch_name).diffs.diff_files.first + end + + def update_file(file_name, content) + Files::UpdateService.new( + project, + project.owner, + commit_message: 'Update', + start_branch: branch_name, + branch_name: branch_name, + file_path: file_name, + file_content: content + ).execute + + project.commit(branch_name).diffs.diff_files.first + end + + def delete_file(file_name) + Files::DeleteService.new( + project, + project.owner, + commit_message: 'Update', + start_branch: branch_name, + branch_name: branch_name, + file_path: file_name + ).execute + + project.commit(branch_name).diffs.diff_files.first + end + + context 'when empty file is created' do + it 'returns true' do + diff_file = create_file('empty.md', '') + + expect(diff_file.empty?).to be_truthy + end + end + + context 'when empty file is deleted' do + it 'returns true' do + create_file('empty.md', '') + diff_file = delete_file('empty.md') + + expect(diff_file.empty?).to be_truthy + end + end + + context 'when file with content is truncated' do + it 'returns false' do + create_file('with-content.md', 'file content') + diff_file = update_file('with-content.md', '') + + expect(diff_file.empty?).to be_falsey + end + end + + context 'when empty file has content added' do + it 'returns false' do + create_file('empty.md', '') + diff_file = update_file('empty.md', 'new content') + + expect(diff_file.empty?).to be_falsey + end + end + end end diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb index efca8564894..25684ea9e2c 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb @@ -240,12 +240,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi .and_return(user.id) end - # TODO: remove rails5-only after removing rails4 tests - # rails 4 can not handle multiple indexes on the same column set if - # index was added by 't.index' - t.index is used by default in schema.rb in - # rails 5. Let's run this test only in rails 5 env: - # see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21492#note_113602758 - it 'returns the existing merge request', :rails5 do + it 'returns the existing merge request' do mr1, exists1 = importer.create_merge_request mr2, exists2 = importer.create_merge_request diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index bae5b21c26f..c8c74883640 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -37,6 +37,7 @@ notes: - events - system_note_metadata - note_diff_file +- suggestions label_links: - target - label @@ -63,6 +64,7 @@ snippets: - award_emoji - user_agent_detail releases: +- author - project project_members: - created_by diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index d3bfde181bc..24b1f2d995b 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -112,8 +112,11 @@ ProjectSnippet: - visibility_level Release: - id +- name - tag +- sha - description +- author_id - project_id - created_at - updated_at diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb index 1cf8935bfe3..3147b52dcc5 100644 --- a/spec/lib/gitlab/sql/glob_spec.rb +++ b/spec/lib/gitlab/sql/glob_spec.rb @@ -35,9 +35,8 @@ describe Gitlab::SQL::Glob do value = query("SELECT #{quote(string)} LIKE #{pattern}") .rows.flatten.first - check = Gitlab.rails5? ? true : 't' case value - when check, 1 + when true, 1 true else false diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index deb19fe1a4b..2a09f581f68 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -117,6 +117,7 @@ describe Gitlab::UsageData do releases remote_mirrors snippets + suggestions todos uploads web_hooks diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index b3f55a2e1bd..7213eee5675 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -246,7 +246,6 @@ describe Gitlab::Workhorse do GL_ID: "user-#{user.id}", GL_USERNAME: user.username, GL_REPOSITORY: "project-#{project.id}", - RepoPath: repo_path, ShowAllRefs: false } end @@ -261,7 +260,6 @@ describe Gitlab::Workhorse do GL_ID: "user-#{user.id}", GL_USERNAME: user.username, GL_REPOSITORY: "wiki-#{project.id}", - RepoPath: repo_path, ShowAllRefs: false } end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 1d17aec0ded..f6e5c9d33ac 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -4,6 +4,7 @@ require 'email_spec' describe Notify do include EmailSpec::Helpers include EmailSpec::Matchers + include EmailHelpers include RepoHelpers include_context 'gitlab email notification' @@ -27,15 +28,6 @@ describe Notify do description: 'My awesome description!') end - def have_referable_subject(referable, reply: false) - prefix = (referable.project ? "#{referable.project.name} | " : '').freeze - prefix = "Re: #{prefix}" if reply - - suffix = "#{referable.title} (#{referable.to_reference})" - - have_subject [prefix, suffix].compact.join - end - context 'for a project' do shared_examples 'an assignee email' do it 'is sent to the assignee as the author' do diff --git a/spec/migrations/backfill_releases_name_with_tag_name_spec.rb b/spec/migrations/backfill_releases_name_with_tag_name_spec.rb new file mode 100644 index 00000000000..6f436de84b7 --- /dev/null +++ b/spec/migrations/backfill_releases_name_with_tag_name_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20181212104941_backfill_releases_name_with_tag_name.rb') + +describe BackfillReleasesNameWithTagName, :migration do + let(:releases) { table(:releases) } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + + let(:namespace) { namespaces.create(name: 'foo', path: 'foo') } + let(:project) { projects.create!(namespace_id: namespace.id) } + let(:release) { releases.create!(project_id: project.id, tag: 'v1.0.0') } + + it 'defaults name to tag value' do + expect(release.tag).to be_present + + migrate! + + release.reload + expect(release.name).to eq(release.tag) + end +end diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb new file mode 100644 index 00000000000..741cdfef1a5 --- /dev/null +++ b/spec/models/ci/bridge_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Ci::Bridge do + set(:project) { create(:project) } + set(:pipeline) { create(:ci_pipeline, project: project) } + + let(:bridge) do + create(:ci_bridge, pipeline: pipeline) + end + + describe '#tags' do + it 'only has a bridge tag' do + expect(bridge.tags).to eq [:bridge] + end + end + + describe '#detailed_status' do + let(:user) { create(:user) } + let(:status) { bridge.detailed_status(user) } + + it 'returns detailed status object' do + expect(status).to be_a Gitlab::Ci::Status::Success + end + end +end diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index a1579b90436..809880f5969 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -33,10 +33,10 @@ describe Clusters::Applications::Knative do end context 'application install previously errored with older version' do - let(:application) { create(:clusters_applications_knative, :scheduled, version: '0.1.3') } + let(:application) { create(:clusters_applications_knative, :scheduled, version: '0.2.2') } it 'updates the application version' do - expect(application.reload.version).to eq('0.1.3') + expect(application.reload.version).to eq('0.2.2') end end end @@ -105,7 +105,7 @@ describe Clusters::Applications::Knative do it 'should be initialized with knative arguments' do expect(subject.name).to eq('knative') expect(subject.chart).to eq('knative/knative') - expect(subject.version).to eq('0.1.3') + expect(subject.version).to eq('0.2.2') expect(subject.files).to eq(knative.files) end end diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 8624f0daa4d..40ce8ab736a 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -318,6 +318,24 @@ describe DiffNote do end end + describe '#supports_suggestion?' do + context 'when noteable does not support suggestions' do + it 'returns false' do + allow(subject.noteable).to receive(:supports_suggestion?) { false } + + expect(subject.supports_suggestion?).to be(false) + end + end + + context 'when line is not suggestible' do + it 'returns false' do + allow_any_instance_of(Gitlab::Diff::Line).to receive(:suggestible?) { false } + + expect(subject.supports_suggestion?).to be(false) + end + end + end + describe "image diff notes" do let(:path) { "files/images/any_image.png" } diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index cbe60b3a4a5..33e984dc399 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -105,7 +105,7 @@ describe MergeRequestDiff do context 'when the raw diffs are empty' do before do - MergeRequestDiffFile.delete_all(merge_request_diff_id: diff_with_commits.id) + MergeRequestDiffFile.where(merge_request_diff_id: diff_with_commits.id).delete_all end it 'returns an empty DiffCollection' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c3152d2021b..bf4117fbcaf 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1339,6 +1339,30 @@ describe MergeRequest do end end + describe '#calculate_reactive_cache' do + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, source_project: project) } + subject { merge_request.calculate_reactive_cache(service_class_name) } + + context 'when given an unknown service class name' do + let(:service_class_name) { 'Integer' } + + it 'raises a NameError exception' do + expect { subject }.to raise_error(NameError, service_class_name) + end + end + + context 'when given a known service class name' do + let(:service_class_name) { 'Ci::CompareTestReportsService' } + + it 'does not raises a NameError exception' do + allow_any_instance_of(service_class_name.constantize).to receive(:execute).and_return(nil) + + expect { subject }.not_to raise_error(NameError) + end + end + end + describe '#compare_test_reports' do subject { merge_request.compare_test_reports } diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index 771d834c4bc..c8ab564e3bc 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -42,12 +42,7 @@ RSpec.describe NotificationSetting do expect(notification_setting.new_issue).to eq(true) expect(notification_setting.close_issue).to eq(true) expect(notification_setting.merge_merge_request).to eq(true) - - # In Rails 5 assigning a value which is not explicitly `true` or `false` ("nil" in this case) - # to a boolean column transforms it to `true`. - # In Rails 4 it transforms the value to `false` with deprecation warning. - # Replace `eq(Gitlab.rails5?)` with `eq(true)` when removing rails5? code. - expect(notification_setting.close_merge_request).to eq(Gitlab.rails5?) + expect(notification_setting.close_merge_request).to eq(true) expect(notification_setting.reopen_merge_request).to eq(false) end end diff --git a/spec/models/project_import_data_spec.rb b/spec/models/project_import_data_spec.rb new file mode 100644 index 00000000000..e9910c0a5d1 --- /dev/null +++ b/spec/models/project_import_data_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ProjectImportData do + describe '#merge_data' do + it 'writes the Hash to the attribute if it is nil' do + row = described_class.new + + row.merge_data('number' => 10) + + expect(row.data).to eq({ 'number' => 10 }) + end + + it 'merges the Hash into an existing Hash if one was present' do + row = described_class.new(data: { 'number' => 10 }) + + row.merge_data('foo' => 'bar') + + expect(row.data).to eq({ 'number' => 10, 'foo' => 'bar' }) + end + end + + describe '#merge_credentials' do + it 'writes the Hash to the attribute if it is nil' do + row = described_class.new + + row.merge_credentials('number' => 10) + + expect(row.credentials).to eq({ 'number' => 10 }) + end + + it 'merges the Hash into an existing Hash if one was present' do + row = described_class.new + + row.credentials = { 'number' => 10 } + row.merge_credentials('foo' => 'bar') + + expect(row.credentials).to eq({ 'number' => 10, 'foo' => 'bar' }) + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9e5b06b745a..5e63f14b720 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -4102,6 +4102,29 @@ describe Project do end end + describe '#object_pool_params' do + let(:project) { create(:project, :repository, :public) } + + subject { project.object_pool_params } + + before do + stub_application_setting(hashed_storage_enabled: true) + end + + context 'when the objects cannot be pooled' do + let(:project) { create(:project, :repository, :private) } + + it { is_expected.to be_empty } + end + + context 'when a pool is created' do + it 'returns that pool repository' do + expect(subject).not_to be_empty + expect(subject[:pool_repository]).to be_persisted + end + end + end + describe '#git_objects_poolable?' do subject { project } diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 3f86347c3ae..51725eeacac 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Release do describe 'associations' do it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:author).class_name('User') } end describe 'validation' do diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index b12ca79847c..5d3c25062d5 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe RemoteMirror do +describe RemoteMirror, :mailer do include GitHelpers describe 'URL validation' do @@ -137,6 +137,43 @@ describe RemoteMirror do end end + describe '#mark_as_failed' do + let(:remote_mirror) { create(:remote_mirror) } + let(:error_message) { 'http://user:pass@test.com/root/repoC.git/' } + let(:sanitized_error_message) { 'http://*****:*****@test.com/root/repoC.git/' } + + subject do + remote_mirror.update_start + remote_mirror.mark_as_failed(error_message) + end + + it 'sets the update_status to failed' do + subject + + expect(remote_mirror.reload.update_status).to eq('failed') + end + + it 'saves the sanitized error' do + subject + + expect(remote_mirror.last_error).to eq(sanitized_error_message) + end + + context 'notifications' do + let(:user) { create(:user) } + + before do + remote_mirror.project.add_maintainer(user) + end + + it 'notifies the project maintainers' do + perform_enqueued_jobs { subject } + + should_email(user) + end + end + end + context 'when remote mirror gets destroyed' do it 'removes remote' do mirror = create_mirror(url: 'http://foo:bar@test.com') diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb new file mode 100644 index 00000000000..cafc725dddb --- /dev/null +++ b/spec/models/suggestion_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Suggestion do + let(:suggestion) { create(:suggestion) } + + describe 'associations' do + it { is_expected.to belong_to(:note) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:note) } + + context 'when suggestion is applied' do + before do + allow(subject).to receive(:applied?).and_return(true) + end + + it { is_expected.to validate_presence_of(:commit_id) } + end + end + + describe '#appliable?' do + context 'when note does not support suggestions' do + it 'returns false' do + expect_next_instance_of(DiffNote) do |note| + allow(note).to receive(:supports_suggestion?) { false } + end + + expect(suggestion).not_to be_appliable + end + end + + context 'when patch is already applied' do + let(:suggestion) { create(:suggestion, :applied) } + + it 'returns false' do + expect(suggestion).not_to be_appliable + end + end + + context 'when merge request is not opened' do + let(:merge_request) { create(:merge_request, :merged) } + let(:note) do + create(:diff_note_on_merge_request, project: merge_request.project, + noteable: merge_request) + end + + let(:suggestion) { create(:suggestion, note: note) } + + it 'returns false' do + expect(suggestion).not_to be_appliable + end + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ff075e65c76..8b3021113bc 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -45,6 +45,7 @@ describe User do it { is_expected.to have_many(:uploads) } it { is_expected.to have_many(:reported_abuse_reports).dependent(:destroy).class_name('AbuseReport') } it { is_expected.to have_many(:custom_attributes).class_name('UserCustomAttribute') } + it { is_expected.to have_many(:releases).dependent(:nullify) } describe "#abuse_report" do let(:current_user) { create(:user) } diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb index 72c5eac3ede..754ba0a594c 100644 --- a/spec/presenters/clusters/cluster_presenter_spec.rb +++ b/spec/presenters/clusters/cluster_presenter_spec.rb @@ -4,9 +4,10 @@ describe Clusters::ClusterPresenter do include Gitlab::Routing.url_helpers let(:cluster) { create(:cluster, :provided_by_gcp, :project) } + let(:user) { create(:user) } subject(:presenter) do - described_class.new(cluster) + described_class.new(cluster, current_user: user) end it 'inherits from Gitlab::View::Presenter::Delegated' do @@ -27,6 +28,129 @@ describe Clusters::ClusterPresenter do end end + describe '#item_link' do + let(:clusterable_presenter) { double('ClusterablePresenter', subject: clusterable) } + + subject { presenter.item_link(clusterable_presenter) } + + context 'for a group cluster' do + let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [group]) } + let(:group) { create(:group, name: 'Foo') } + let(:cluster_link) { "<a href=\"#{group_cluster_path(cluster.group, cluster)}\">#{cluster.name}</a>" } + + before do + group.add_maintainer(user) + end + + shared_examples 'ancestor clusters' do + context 'ancestor clusters', :nested_groups do + let(:root_group) { create(:group, name: 'Root Group') } + let(:parent) { create(:group, name: 'parent', parent: root_group) } + let(:child) { create(:group, name: 'child', parent: parent) } + let(:group) { create(:group, name: 'group', parent: child) } + + before do + root_group.add_maintainer(user) + end + + context 'top level group cluster' do + let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [root_group]) } + + it 'returns full group names and link for cluster' do + expect(subject).to eq("Root Group / #{cluster_link}") + end + + it 'is html safe' do + expect(presenter).to receive(:sanitize).with('Root Group').and_call_original + + expect(subject).to be_html_safe + end + end + + context 'first level group cluster' do + let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [parent]) } + + it 'returns full group names and link for cluster' do + expect(subject).to eq("Root Group / parent / #{cluster_link}") + end + + it 'is html safe' do + expect(presenter).to receive(:sanitize).with('Root Group / parent').and_call_original + + expect(subject).to be_html_safe + end + end + + context 'second level group cluster' do + let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [child]) } + + let(:ellipsis_h) do + /.*ellipsis_h.*/ + end + + it 'returns clipped group names and link for cluster' do + expect(subject).to match("Root Group / #{ellipsis_h} / child / #{cluster_link}") + end + + it 'is html safe' do + expect(presenter).to receive(:sanitize).with('Root Group / parent / child').and_call_original + + expect(subject).to be_html_safe + end + end + end + end + + context 'for a project clusterable' do + let(:clusterable) { project } + let(:project) { create(:project, group: group) } + + it 'returns the group name and the link for cluster' do + expect(subject).to eq("Foo / #{cluster_link}") + end + + it 'is html safe' do + expect(presenter).to receive(:sanitize).with('Foo').and_call_original + + expect(subject).to be_html_safe + end + + include_examples 'ancestor clusters' + end + + context 'for the group clusterable for the cluster' do + let(:clusterable) { group } + + it 'returns link for cluster' do + expect(subject).to eq(cluster_link) + end + + include_examples 'ancestor clusters' + + it 'is html safe' do + expect(subject).to be_html_safe + end + end + end + + context 'for a project cluster' do + let(:cluster) { create(:cluster, :project) } + let(:cluster_link) { "<a href=\"#{project_cluster_path(cluster.project, cluster)}\">#{cluster.name}</a>" } + + before do + cluster.project.add_maintainer(user) + end + + context 'for the project clusterable' do + let(:clusterable) { cluster.project } + + it 'returns link for cluster' do + expect(subject).to eq(cluster_link) + end + end + end + end + describe '#gke_cluster_url' do subject { described_class.new(cluster).gke_cluster_url } @@ -74,6 +198,20 @@ describe Clusters::ClusterPresenter do end end + describe '#cluster_type_description' do + subject { described_class.new(cluster).cluster_type_description } + + context 'project_type cluster' do + it { is_expected.to eq('Project cluster') } + end + + context 'group_type cluster' do + let(:cluster) { create(:cluster, :provided_by_gcp, :group) } + + it { is_expected.to eq('Group cluster') } + end + end + describe '#show_path' do subject { described_class.new(cluster).show_path } diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 2ebcb787d06..3304dfdd909 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -969,17 +969,10 @@ describe API::Internal do env: env } - if Gitlab.rails5? - post( - api("/internal/allowed"), - params: params - ) - else - post( - api("/internal/allowed"), - params - ) - end + post( + api("/internal/allowed"), + params: params + ) end def archive(key, project) diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb new file mode 100644 index 00000000000..8e9f737fbd5 --- /dev/null +++ b/spec/requests/api/suggestions_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Suggestions do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs) + end + + let(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + describe "PUT /suggestions/:id/apply" do + let(:url) { "/suggestions/#{suggestion.id}/apply" } + + context 'when successfully applies patch' do + let(:suggestion) do + create(:suggestion, note: diff_note, + from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n", + to_content: " raise RuntimeError, 'Explosion'\n # explosion?") + end + + it 'returns 200 with json content' do + project.add_maintainer(user) + + put api(url, user), id: suggestion.id + + expect(response).to have_gitlab_http_status(200) + expect(json_response) + .to include('id', 'from_original_line', 'to_original_line', + 'from_line', 'to_line', 'appliable', 'applied', + 'from_content', 'to_content') + end + end + + context 'when not able to apply patch' do + let(:suggestion) do + create(:suggestion, :unappliable, note: diff_note) + end + + it 'returns 400 with json content' do + project.add_maintainer(user) + + put api(url, user), id: suggestion.id + + expect(response).to have_gitlab_http_status(400) + expect(json_response).to eq({ 'message' => 'Suggestion is not appliable' }) + end + end + + context 'when unauthorized' do + let(:suggestion) do + create(:suggestion, note: diff_note, + from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n", + to_content: " raise RuntimeError, 'Explosion'\n # explosion?") + end + + it 'returns 403 with json content' do + project.add_reporter(user) + + put api(url, user), id: suggestion.id + + expect(response).to have_gitlab_http_status(403) + expect(json_response).to eq({ 'message' => '403 Forbidden' }) + end + end + end +end diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb new file mode 100644 index 00000000000..08ffc3c3a53 --- /dev/null +++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../rubocop/cop/inject_enterprise_edition_module' + +describe RuboCop::Cop::InjectEnterpriseEditionModule do + include CopHelper + + subject(:cop) { described_class.new } + + it 'flags the use of `prepend EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + prepend EE::Foo + ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `prepend ::EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + prepend ::EE::Foo + ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `include EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + include EE::Foo + ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `include ::EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + include ::EE::Foo + ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `extend EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + extend EE::Foo + ^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'flags the use of `extend ::EE` in the middle of a file' do + expect_offense(<<~SOURCE) + class Foo + extend ::EE::Foo + ^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions + end + SOURCE + end + + it 'does not flag prepending of regular modules' do + expect_no_offenses(<<~SOURCE) + class Foo + prepend Foo + end + SOURCE + end + + it 'does not flag including of regular modules' do + expect_no_offenses(<<~SOURCE) + class Foo + include Foo + end + SOURCE + end + + it 'does not flag extending using regular modules' do + expect_no_offenses(<<~SOURCE) + class Foo + extend Foo + end + SOURCE + end + + it 'does not flag the use of `prepend EE` on the last line' do + expect_no_offenses(<<~SOURCE) + class Foo + end + + Foo.prepend(EE::Foo) + SOURCE + end + + it 'does not flag the use of `include EE` on the last line' do + expect_no_offenses(<<~SOURCE) + class Foo + end + + Foo.include(EE::Foo) + SOURCE + end + + it 'does not flag the use of `extend EE` on the last line' do + expect_no_offenses(<<~SOURCE) + class Foo + end + + Foo.extend(EE::Foo) + SOURCE + end + + it 'autocorrects offenses by just disabling the Cop' do + source = <<~SOURCE + class Foo + prepend EE::Foo + include Bar + end + SOURCE + + expect(autocorrect_source(source)).to eq(<<~SOURCE) + class Foo + prepend EE::Foo # rubocop: disable Cop/InjectEnterpriseEditionModule + include Bar + end + SOURCE + end +end diff --git a/spec/rubocop/cop/migration/add_timestamps_spec.rb b/spec/rubocop/cop/migration/add_timestamps_spec.rb index 3a41c91add2..fae0177d5f5 100644 --- a/spec/rubocop/cop/migration/add_timestamps_spec.rb +++ b/spec/rubocop/cop/migration/add_timestamps_spec.rb @@ -11,7 +11,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do subject(:cop) { described_class.new } let(:migration_with_add_timestamps) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -24,7 +24,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do let(:migration_without_add_timestamps) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -36,7 +36,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do let(:migration_with_add_timestamps_with_timezone) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change diff --git a/spec/rubocop/cop/migration/datetime_spec.rb b/spec/rubocop/cop/migration/datetime_spec.rb index 9e844325371..f2d9483d8d3 100644 --- a/spec/rubocop/cop/migration/datetime_spec.rb +++ b/spec/rubocop/cop/migration/datetime_spec.rb @@ -12,7 +12,7 @@ describe RuboCop::Cop::Migration::Datetime do let(:migration_with_datetime) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -25,7 +25,7 @@ describe RuboCop::Cop::Migration::Datetime do let(:migration_with_timestamp) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -38,7 +38,7 @@ describe RuboCop::Cop::Migration::Datetime do let(:migration_without_datetime) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -50,7 +50,7 @@ describe RuboCop::Cop::Migration::Datetime do let(:migration_with_datetime_with_timezone) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change diff --git a/spec/rubocop/cop/migration/timestamps_spec.rb b/spec/rubocop/cop/migration/timestamps_spec.rb index 685bdb21803..1812818692a 100644 --- a/spec/rubocop/cop/migration/timestamps_spec.rb +++ b/spec/rubocop/cop/migration/timestamps_spec.rb @@ -11,7 +11,7 @@ describe RuboCop::Cop::Migration::Timestamps do subject(:cop) { described_class.new } let(:migration_with_timestamps) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -27,7 +27,7 @@ describe RuboCop::Cop::Migration::Timestamps do let(:migration_without_timestamps) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change @@ -42,7 +42,7 @@ describe RuboCop::Cop::Migration::Timestamps do let(:migration_with_timestamps_with_timezone) do %q( - class Users < ActiveRecord::Migration + class Users < ActiveRecord::Migration[4.2] DOWNTIME = false def change diff --git a/spec/serializers/issue_board_entity_spec.rb b/spec/serializers/issue_board_entity_spec.rb index 06d9d3657e6..f6fa2a794f6 100644 --- a/spec/serializers/issue_board_entity_spec.rb +++ b/spec/serializers/issue_board_entity_spec.rb @@ -3,21 +3,40 @@ require 'spec_helper' describe IssueBoardEntity do - let(:project) { create(:project) } - let(:resource) { create(:issue, project: project) } - let(:user) { create(:user) } - - let(:request) { double('request', current_user: user) } + let(:project) { create(:project) } + let(:resource) { create(:issue, project: project) } + let(:user) { create(:user) } + let(:milestone) { create(:milestone, project: project) } + let(:label) { create(:label, project: project, title: 'Test Label') } + let(:request) { double('request', current_user: user) } subject { described_class.new(resource, request: request).as_json } it 'has basic attributes' do expect(subject).to include(:id, :iid, :title, :confidential, :due_date, :project_id, :relative_position, - :project, :labels) + :labels, :assignees, project: hash_including(:id, :path)) end it 'has path and endpoints' do expect(subject).to include(:reference_path, :real_path, :issue_sidebar_endpoint, :toggle_subscription_endpoint, :assignable_labels_endpoint) end + + it 'has milestone attributes' do + resource.milestone = milestone + + expect(subject).to include(milestone: hash_including(:id, :title)) + end + + it 'has assignee attributes' do + resource.assignees = [user] + + expect(subject).to include(assignees: array_including(hash_including(:id, :name, :username, :avatar_url))) + end + + it 'has label attributes' do + resource.labels = [label] + + expect(subject).to include(labels: array_including(hash_including(:id, :title, :color, :description, :text_color, :priority))) + end end diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb new file mode 100644 index 00000000000..047571f161c --- /dev/null +++ b/spec/serializers/suggestion_entity_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SuggestionEntity do + include RepoHelpers + + let(:user) { create(:user) } + let(:request) { double('request', current_user: user) } + let(:suggestion) { create(:suggestion) } + let(:entity) { described_class.new(suggestion, request: request) } + + subject { entity.as_json } + + it 'exposes correct attributes' do + expect(subject).to include(:id, :from_original_line, :to_original_line, :from_line, + :to_line, :appliable, :applied, :from_content, :to_content) + end + + it 'exposes current user abilities' do + expect(subject[:current_user]).to include(:can_apply) + end +end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 8b8021ecbc8..ffa47d527f7 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -840,6 +840,37 @@ describe Ci::CreatePipelineService do end end + context "when config uses variables for only keyword" do + let(:config) do + { + build: { + stage: 'build', + script: 'echo', + only: { + variables: %w($CI) + } + } + } + end + + context 'when merge request is specified' do + let(:merge_request) do + create(:merge_request, + source_project: project, + source_branch: ref_name, + target_project: project, + target_branch: 'master') + end + + it 'does not create a merge request pipeline' do + expect(pipeline).not_to be_persisted + + expect(pipeline.errors[:base]) + .to eq(['No stages / jobs for this pipeline.']) + end + end + end + context "when config has 'except: [tags]'" do let(:config) do { diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb index ac0a0458f56..1a2dd0b39ee 100644 --- a/spec/services/create_release_service_spec.rb +++ b/spec/services/create_release_service_spec.rb @@ -6,6 +6,8 @@ describe CreateReleaseService do let(:tag_name) { project.repository.tag_names.first } let(:description) { 'Awesome release!' } let(:service) { described_class.new(project, user) } + let(:tag) { project.repository.find_tag(tag_name) } + let(:sha) { tag.dereferenced_target.sha } it 'creates a new release' do result = service.execute(tag_name, description) @@ -13,6 +15,9 @@ describe CreateReleaseService do release = project.releases.find_by(tag: tag_name) expect(release).not_to be_nil expect(release.description).to eq(description) + expect(release.name).to eq(tag_name) + expect(release.sha).to eq(sha) + expect(release.author).to eq(user) end it 'raises an error if the tag does not exist' do diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb index 533dcdcd6cd..fd9bff46a06 100644 --- a/spec/services/notes/update_service_spec.rb +++ b/spec/services/notes/update_service_spec.rb @@ -20,6 +20,29 @@ describe Notes::UpdateService do @note.reload end + context 'suggestions' do + it 'refreshes note suggestions' do + markdown = <<-MARKDOWN.strip_heredoc + ```suggestion + foo + ``` + + ```suggestion + bar + ``` + MARKDOWN + + suggestion = create(:suggestion) + note = suggestion.note + + expect { described_class.new(project, user, note: markdown).execute(note) } + .to change { note.suggestions.count }.from(1).to(2) + + expect(note.suggestions.order(:relative_order).map(&:to_content)) + .to eq([" foo\n", " bar\n"]) + end + end + context 'todos' do let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 0f6c2604984..68ac3a00ab0 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2167,6 +2167,39 @@ describe NotificationService, :mailer do end end + context 'Remote mirror notifications' do + describe '#remote_mirror_update_failed' do + let(:project) { create(:project) } + let(:remote_mirror) { create(:remote_mirror, project: project) } + let(:u_blocked) { create(:user, :blocked) } + let(:u_silence) { create_user_with_notification(:disabled, 'silent-maintainer', project) } + let(:u_owner) { project.owner } + let(:u_maintainer1) { create(:user) } + let(:u_maintainer2) { create(:user) } + let(:u_developer) { create(:user) } + + before do + project.add_maintainer(u_blocked) + project.add_maintainer(u_silence) + project.add_maintainer(u_maintainer1) + project.add_maintainer(u_maintainer2) + project.add_developer(u_developer) + + # Mock remote update + allow(project.repository).to receive(:async_remove_remote) + allow(project.repository).to receive(:add_remote) + + reset_delivered_emails! + end + + it 'emails current watching maintainers' do + notification.remote_mirror_update_failed(remote_mirror) + + should_only_email(u_maintainer1, u_maintainer2, u_owner) + end + end + end + def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index b69977c812a..458cb8f1f31 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -19,6 +19,31 @@ describe PreviewMarkdownService do end end + describe 'suggestions' do + let(:params) { { text: "```suggestion\nfoo\n```", preview_suggestions: preview_suggestions } } + let(:service) { described_class.new(project, user, params) } + + context 'when preview markdown param is present' do + let(:preview_suggestions) { true } + + it 'returns users referenced in text' do + result = service.execute + + expect(result[:suggestions]).to eq(['foo']) + end + end + + context 'when preview markdown param is not present' do + let(:preview_suggestions) { false } + + it 'returns users referenced in text' do + result = service.execute + + expect(result[:suggestions]).to eq([]) + end + end + end + context 'new note with quick actions' do let(:issue) { create(:issue, project: project) } let(:params) do diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb index 6af5bfc7689..d7d7f1874eb 100644 --- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb +++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb @@ -54,6 +54,18 @@ describe Projects::LfsPointers::LfsDownloadService do end end + context 'when a bad URL is used' do + where(download_link: ['/etc/passwd', 'ftp://example.com', 'http://127.0.0.2']) + + with_them do + it 'does not download the file' do + expect(subject).not_to receive(:download_and_save_file) + + expect { subject.execute(oid, download_link) }.not_to change { LfsObject.count } + end + end + end + context 'when an lfs object with the same oid already exists' do before do create(:lfs_object, oid: 'oid') diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb new file mode 100644 index 00000000000..3a483717756 --- /dev/null +++ b/spec/services/suggestions/apply_service_spec.rb @@ -0,0 +1,229 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Suggestions::ApplyService do + include ProjectForksHelper + + let(:project) { create(:project, :repository) } + let(:user) { create(:user, :commit_email) } + + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs) + end + + let(:suggestion) do + create(:suggestion, note: diff_note, + from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n", + to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n") + end + + subject { described_class.new(user) } + + context 'patch is appliable' do + let(:expected_content) do + <<-CONTENT.strip_heredoc + require 'fileutils' + require 'open3' + + module Popen + extend self + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) + raise RuntimeError, 'Explosion' + # explosion? + end + + path ||= Dir.pwd + + vars = { + "PWD" => path + } + + options = { + chdir: path + } + + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + + @cmd_output = "" + @cmd_status = 0 + + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + @cmd_output << stderr.read + @cmd_status = wait_thr.value.exitstatus + end + + return @cmd_output, @cmd_status + end + end + CONTENT + end + + context 'non-fork project' do + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + let!(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + before do + project.add_maintainer(user) + end + + it 'updates the file with the new contents' do + subject.execute(suggestion) + + blob = project.repository.blob_at_branch(merge_request.source_branch, + position.new_path) + + expect(blob.data).to eq(expected_content) + end + + it 'returns success status' do + result = subject.execute(suggestion) + + expect(result[:status]).to eq(:success) + end + + it 'updates suggestion applied and commit_id columns' do + expect { subject.execute(suggestion) } + .to change(suggestion, :applied) + .from(false).to(true) + .and change(suggestion, :commit_id) + .from(nil) + end + + it 'created commit has users email and name' do + subject.execute(suggestion) + + commit = project.repository.commit + + expect(user.commit_email).not_to eq(user.email) + expect(commit.author_email).to eq(user.commit_email) + expect(commit.committer_email).to eq(user.commit_email) + expect(commit.author_name).to eq(user.name) + end + end + + context 'fork-project' do + let(:project) { create(:project, :public, :repository) } + + let(:forked_project) do + fork_project_with_submodules(project, user) + end + + let(:merge_request) do + create(:merge_request, + source_branch: 'conflict-resolvable-fork', source_project: forked_project, + target_branch: 'conflict-start', target_project: project) + end + + let!(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, position: position, project: project) + end + + before do + project.add_maintainer(user) + end + + it 'updates file in the source project' do + expect(Files::UpdateService).to receive(:new) + .with(merge_request.source_project, user, anything) + .and_call_original + + subject.execute(suggestion) + end + end + end + + context 'no permission' do + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + let(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + context 'user cannot write in project repo' do + before do + project.add_reporter(user) + end + + it 'returns error' do + result = subject.execute(suggestion) + + expect(result).to eq(message: "You are not allowed to push into this branch", + status: :error) + end + end + end + + context 'patch is not appliable' do + let(:merge_request) do + create(:merge_request, source_project: project, + target_project: project) + end + + let(:diff_note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + before do + project.add_maintainer(user) + end + + context 'suggestion was already applied' do + it 'returns success status' do + result = subject.execute(suggestion) + + expect(result[:status]).to eq(:success) + end + end + + context 'note is outdated' do + before do + allow(diff_note).to receive(:active?) { false } + end + + it 'returns error message' do + result = subject.execute(suggestion) + + expect(result).to eq(message: 'Suggestion is not appliable', + status: :error) + end + end + + context 'suggestion was already applied' do + before do + suggestion.update!(applied: true, commit_id: 'sha') + end + + it 'returns error message' do + result = subject.execute(suggestion) + + expect(result).to eq(message: 'Suggestion is not appliable', + status: :error) + end + end + end +end diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb new file mode 100644 index 00000000000..f1142c88a69 --- /dev/null +++ b/spec/services/suggestions/create_service_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Suggestions::CreateService do + let(:project_with_repo) { create(:project, :repository) } + let(:merge_request) do + create(:merge_request, source_project: project_with_repo, + target_project: project_with_repo) + end + + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: merge_request.diff_refs) + end + + let(:markdown) do + <<-MARKDOWN.strip_heredoc + ```suggestion + foo + bar + ``` + + ``` + nothing + ``` + + ```suggestion + xpto + baz + ``` + + ```thing + this is not a suggestion, it's a thing + ``` + MARKDOWN + end + + subject { described_class.new(note) } + + describe '#execute' do + context 'should not try to parse suggestions' do + context 'when not a diff note for merge requests' do + let(:note) do + create(:diff_note_on_commit, project: project_with_repo, + note: markdown) + end + + it 'does not try to parse suggestions' do + expect(Banzai::SuggestionsParser).not_to receive(:parse) + + subject.execute + end + end + + context 'when diff note is not for text' do + let(:note) do + create(:diff_note_on_merge_request, project: project_with_repo, + noteable: merge_request, + position: position, + note: markdown) + end + + it 'does not try to parse suggestions' do + allow(note).to receive(:on_text?) { false } + + expect(Banzai::SuggestionsParser).not_to receive(:parse) + + subject.execute + end + end + end + + context 'should create suggestions' do + let(:note) do + create(:diff_note_on_merge_request, project: project_with_repo, + noteable: merge_request, + position: position, + note: markdown) + end + + context 'single line suggestions' do + it 'persists suggestion records' do + expect { subject.execute } + .to change { note.suggestions.count } + .from(0) + .to(2) + end + + it 'persists original from_content lines and suggested lines' do + subject.execute + + suggestions = note.suggestions.order(:relative_order) + + suggestion_1 = suggestions.first + suggestion_2 = suggestions.last + + expect(suggestion_1).to have_attributes(from_content: " vars = {\n", + to_content: " foo\n bar\n") + + expect(suggestion_2).to have_attributes(from_content: " vars = {\n", + to_content: " xpto\n baz\n") + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3fedb9ed48c..fb3421b61d3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -115,7 +115,7 @@ RSpec.configure do |config| TestEnv.clean_test_path end - config.before(:example) do + config.before do # Enable all features by default for testing allow(Feature).to receive(:enabled?) { true } @@ -136,11 +136,11 @@ RSpec.configure do |config| RequestStore.clear! end - config.after(:example) do + config.after do Fog.unmock! if Fog.mock? end - config.after(:example) do + config.after do Gitlab::CurrentSettings.clear_in_memory_application_settings! end @@ -235,10 +235,6 @@ RSpec.configure do |config| example.run if Gitlab::Database.mysql? end - config.around(:each, :rails5) do |example| - example.run if Gitlab.rails5? - end - # This makes sure the `ApplicationController#can?` method is stubbed with the # original implementation for all view specs. config.before(:each, type: :view) do diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb index b4b016e408f..b376822d530 100644 --- a/spec/support/carrierwave.rb +++ b/spec/support/carrierwave.rb @@ -1,7 +1,7 @@ CarrierWave.root = File.expand_path('tmp/tests/public', Rails.root) RSpec.configure do |config| - config.after(:each) do + config.after do FileUtils.rm_rf(CarrierWave.root) end end diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb index 5edc5de2a09..34b9efaaecd 100644 --- a/spec/support/db_cleaner.rb +++ b/spec/support/db_cleaner.rb @@ -23,7 +23,7 @@ RSpec.configure do |config| DatabaseCleaner.clean_with(:deletion, cache_tables: false) end - config.before(:each) do + config.before do DatabaseCleaner.strategy = :transaction end @@ -39,11 +39,11 @@ RSpec.configure do |config| DatabaseCleaner.strategy = :deletion, { cache_tables: false } end - config.before(:each) do + config.before do DatabaseCleaner.start end - config.append_after(:each) do + config.append_after do DatabaseCleaner.clean end end diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index 922f3df144d..42a086d58d2 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -178,6 +178,16 @@ shared_examples 'discussion comments' do |resource_name| let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] } let(:reply_id) { find("#{comments_selector} .note:last-child", match: :first)['data-note-id'] } + it 'can be replied to after resolving' do + click_button "Resolve discussion" + wait_for_requests + + refresh + wait_for_requests + + submit_reply('to reply or not reply') + end + it 'shows resolved discussion when toggled' do submit_reply('a') diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb index 1fb8252459f..ad6e1064499 100644 --- a/spec/support/helpers/email_helpers.rb +++ b/spec/support/helpers/email_helpers.rb @@ -34,4 +34,13 @@ module EmailHelpers def find_email_for(user) ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) } end + + def have_referable_subject(referable, include_project: true, reply: false) + prefix = (include_project && referable.project ? "#{referable.project.name} | " : '').freeze + prefix = "Re: #{prefix}" if reply + + suffix = "#{referable.title} (#{referable.to_reference})" + + have_subject [prefix, suffix].compact.join + end end diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb index b0fc8422857..c7766df7a52 100644 --- a/spec/support/helpers/fake_migration_classes.rb +++ b/spec/support/helpers/fake_migration_classes.rb @@ -1,4 +1,4 @@ -class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration +class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration[4.2] include Gitlab::Database::RenameReservedPathsMigration::V1 def version diff --git a/spec/support/helpers/test_request_helpers.rb b/spec/support/helpers/test_request_helpers.rb index 187a0e07891..5a84d67bdfc 100644 --- a/spec/support/helpers/test_request_helpers.rb +++ b/spec/support/helpers/test_request_helpers.rb @@ -2,10 +2,6 @@ module TestRequestHelpers def test_request(remote_ip: '127.0.0.1') - if Gitlab.rails5? - ActionController::TestRequest.new({ remote_ip: remote_ip }, ActionController::TestSession.new) - else - ActionController::TestRequest.new(remote_ip: remote_ip) - end + ActionController::TestRequest.new({ remote_ip: remote_ip }, ActionController::TestSession.new) end end diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb index 1478c6b5a47..62ae95df8c0 100644 --- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb +++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb @@ -86,7 +86,7 @@ shared_examples "migrating a deleted user's associated records to the ghost user end it "blocks the user before #{record_class_name} migration begins" do - expect(service).to receive("migrate_#{record_class_name.parameterize('_').pluralize}".to_sym) do + expect(service).to receive("migrate_#{record_class_name.parameterize(separator: '_').pluralize}".to_sym) do expect(user.reload).to be_blocked end diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb index 2e7c88bfc09..1d2a4856724 100644 --- a/spec/support/setup_builds_storage.rb +++ b/spec/support/setup_builds_storage.rb @@ -11,7 +11,7 @@ RSpec.configure do |config| FileUtils.mkdir_p(builds_path) end - config.before(:each) do + config.before do FileUtils.rm_rf(builds_path) FileUtils.mkdir_p(builds_path) end diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb new file mode 100644 index 00000000000..b34948be670 --- /dev/null +++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb @@ -0,0 +1,32 @@ +shared_examples 'set sort order from user preference' do + describe '#set_sort_order_from_user_preference' do + # There is no issuable_sorting_field defined in any CE controllers yet, + # however any other field present in user_preferences table can be used for testing. + let(:sorting_field) { :issue_notes_filter } + let(:sorting_param) { 'any' } + + before do + allow(controller).to receive(:issuable_sorting_field).and_return(sorting_field) + end + + context 'when database is in read-only mode' do + it 'it does not update user preference' do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + + expect_any_instance_of(UserPreference).not_to receive(:update_attribute).with(sorting_field, sorting_param) + + get :index, namespace_id: project.namespace, project_id: project, sort: sorting_param + end + end + + context 'when database is not in read-only mode' do + it 'updates user preference' do + allow(Gitlab::Database).to receive(:read_only?).and_return(false) + + expect_any_instance_of(UserPreference).to receive(:update_attribute).with(sorting_field, sorting_param) + + get :index, namespace_id: project.namespace, project_id: project, sort: sorting_param + end + end + end +end diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index 66536e80db2..a38354060cf 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -1,5 +1,5 @@ shared_context 'gitlab email notification' do - set(:project) { create(:project, :repository) } + set(:project) { create(:project, :repository, name: 'a-known-name') } set(:recipient) { create(:user, email: 'recipient@example.com') } let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } @@ -62,9 +62,11 @@ end shared_examples 'an email with X-GitLab headers containing project details' do it 'has X-GitLab-Project headers' do aggregate_failures do + full_path_as_domain = "#{project.name}.#{project.namespace.path}" is_expected.to have_header('X-GitLab-Project', /#{project.name}/) is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/) is_expected.to have_header('X-GitLab-Project-Path', /#{project.full_path}/) + is_expected.to have_header('List-Id', "#{project.full_path} <#{project.id}.#{full_path_as_domain}.#{Gitlab.config.gitlab.host}>") end end end diff --git a/spec/support/shared_examples/only_except_policy_examples.rb b/spec/support/shared_examples/only_except_policy_examples.rb deleted file mode 100644 index 35240af1d74..00000000000 --- a/spec/support/shared_examples/only_except_policy_examples.rb +++ /dev/null @@ -1,167 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'correct only except policy' do - context 'when using simplified policy' do - describe 'validations' do - context 'when entry config value is valid' do - context 'when config is a branch or tag name' do - let(:config) { %w[master feature/branch] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - - describe '#value' do - it 'returns refs hash' do - expect(entry.value).to eq(refs: config) - end - end - end - - context 'when config is a regexp' do - let(:config) { ['/^issue-.*$/'] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when config is a special keyword' do - let(:config) { %w[tags triggers branches] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - end - - context 'when entry value is not valid' do - let(:config) { [1] } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include /policy config should be an array of strings or regexps/ - end - end - end - end - end - - context 'when using complex policy' do - context 'when specifying refs policy' do - let(:config) { { refs: ['master'] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(refs: %w[master]) - end - end - - context 'when specifying kubernetes policy' do - let(:config) { { kubernetes: 'active' } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(kubernetes: 'active') - end - end - - context 'when specifying invalid kubernetes policy' do - let(:config) { { kubernetes: 'something' } } - - it 'reports an error about invalid policy' do - expect(entry.errors).to include /unknown value: something/ - end - end - - context 'when specifying valid variables expressions policy' do - let(:config) { { variables: ['$VAR == null'] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(config) - end - end - - context 'when specifying variables expressions in invalid format' do - let(:config) { { variables: '$MY_VAR' } } - - it 'reports an error about invalid format' do - expect(entry.errors).to include /should be an array of strings/ - end - end - - context 'when specifying invalid variables expressions statement' do - let(:config) { { variables: ['$MY_VAR =='] } } - - it 'reports an error about invalid statement' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when specifying invalid variables expressions token' do - let(:config) { { variables: ['$MY_VAR == 123'] } } - - it 'reports an error about invalid expression' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when using invalid variables expressions regexp' do - let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } } - - it 'reports an error about invalid expression' do - expect(entry.errors).to include /invalid expression syntax/ - end - end - - context 'when specifying a valid changes policy' do - let(:config) { { changes: %w[some/* paths/**/*.rb] } } - - it 'is a correct configuraton' do - expect(entry).to be_valid - expect(entry.value).to eq(config) - end - end - - context 'when changes policy is invalid' do - let(:config) { { changes: [1, 2] } } - - it 'returns errors' do - expect(entry.errors).to include /changes should be an array of strings/ - end - end - - context 'when specifying unknown policy' do - let(:config) { { refs: ['master'], invalid: :something } } - - it 'returns error about invalid key' do - expect(entry.errors).to include /unknown keys: invalid/ - end - end - - context 'when policy is empty' do - let(:config) { {} } - - it 'is not a valid configuration' do - expect(entry.errors).to include /can't be blank/ - end - end - end - - context 'when policy strategy does not match' do - let(:config) { 'string strategy' } - - it 'returns information about errors' do - expect(entry.errors) - .to include /has to be either an array of conditions or a hash/ - end - end -end diff --git a/spec/support/shared_examples/serializers/diff_file_entity_examples.rb b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb index b8065886c42..1770308f789 100644 --- a/spec/support/shared_examples/serializers/diff_file_entity_examples.rb +++ b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb @@ -32,7 +32,7 @@ shared_examples 'diff file entity' do it 'exposes correct attributes' do expect(subject).to include(:too_large, :added_lines, :removed_lines, :context_lines_path, :highlighted_diff_lines, - :parallel_diff_lines) + :parallel_diff_lines, :empty) end it 'includes viewer' do diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 8c4360d4cf0..3b8f7f5fe7d 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -74,6 +74,7 @@ describe 'gitlab:app namespace rake task' do it 'invokes restoration on match' do allow(YAML).to receive(:load_file) .and_return({ gitlab_version: gitlab_version }) + expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) expect(Rake::Task['gitlab:backup:db:restore']).to receive(:invoke) expect(Rake::Task['gitlab:backup:repo:restore']).to receive(:invoke) diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb index 4f1ad2474f5..d73b0b53713 100644 --- a/spec/workers/repository_update_remote_mirror_worker_spec.rb +++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb @@ -25,12 +25,19 @@ describe RepositoryUpdateRemoteMirrorWorker do it 'sets status as failed when update remote mirror service executes with errors' do error_message = 'fail!' - expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :error, message: error_message) + expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service| + expect(service).to receive(:execute).with(remote_mirror).and_return(status: :error, message: error_message) + end + + # Mock the finder so that it returns an object we can set expectations on + expect_next_instance_of(RemoteMirrorFinder) do |finder| + expect(finder).to receive(:execute).and_return(remote_mirror) + end + expect(remote_mirror).to receive(:mark_as_failed).with(error_message) + expect do subject.perform(remote_mirror.id, Time.now) end.to raise_error(RepositoryUpdateRemoteMirrorWorker::UpdateError, error_message) - - expect(remote_mirror.reload.update_status).to eq('failed') end it 'does nothing if last_update_started_at is higher than the time the job was scheduled in' do |