diff options
author | Luke "Jared" Bennett <lbennett@gitlab.com> | 2017-04-06 13:47:52 +0100 |
---|---|---|
committer | Luke "Jared" Bennett <lbennett@gitlab.com> | 2017-04-06 13:47:52 +0100 |
commit | 9cee280d7045d87a4c0854c0c281c98ba3a085e7 (patch) | |
tree | d65da90310054d83a81588a4886f11c88c59ca5c /spec | |
parent | 2a18425e97023b05aa52c29dbce6c5a7aa7400b7 (diff) | |
parent | 66d03aae98cc0429d2ed75eac68025f447868479 (diff) | |
download | gitlab-ce-9cee280d7045d87a4c0854c0c281c98ba3a085e7.tar.gz |
Merge branch 'update-droplab-to-webpack-version' into new-resolvable-discussion
Diffstat (limited to 'spec')
66 files changed, 1234 insertions, 390 deletions
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 71a4a2c43c7..6075259ea99 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -35,6 +35,13 @@ describe Dashboard::TodosController do expect(assigns(:todos).current_page).to eq(last_page) expect(response).to have_http_status(200) end + + it 'does not redirect to external sites when provided a host field' do + external_host = "www.example.com" + get :index, page: (last_page + 1).to_param, host: external_host + + expect(response).to redirect_to(dashboard_todos_path(page: last_page)) + end end end diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 7c75815f3c4..6724b474179 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -96,12 +96,19 @@ describe Projects::ImportsController do } end - it 'redirects to params[:to]' do + it 'redirects to internal params[:to]' do get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params expect(flash[:notice]).to eq params[:notice] expect(response).to redirect_to params[:to] end + + it 'does not redirect to external params[:to]' do + params[:to] = "//google.com" + + get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params + expect(response).not_to redirect_to params[:to] + end end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 731e511c270..79034b8d24d 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -83,6 +83,17 @@ describe Projects::IssuesController do expect(assigns(:issues).current_page).to eq(last_page) expect(response).to have_http_status(200) end + + it 'does not redirect to external sites when provided a host field' do + external_host = "www.example.com" + get :index, + namespace_id: project.namespace.to_param, + project_id: project, + page: (last_page + 1).to_param, + host: external_host + + expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope])) + end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 6cf4a97e76f..1739d40ab88 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -176,6 +176,18 @@ describe Projects::MergeRequestsController do expect(assigns(:merge_requests).current_page).to eq(last_page) expect(response).to have_http_status(200) end + + it 'does not redirect to external sites when provided a host field' do + external_host = "www.example.com" + get :index, + namespace_id: project.namespace.to_param, + project_id: project, + state: 'opened', + page: (last_page + 1).to_param, + host: external_host + + expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope])) + end end context 'when filtering by opened state' do diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index f78086211f7..87a0c95c4dc 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -192,5 +192,10 @@ FactoryGirl.define do trait :no_options do options { {} } end + + trait :non_playable do + status 'created' + self.when 'manual' + end end end diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb index 0852dda6b29..3fbf24b5c7d 100644 --- a/spec/factories/environments.rb +++ b/spec/factories/environments.rb @@ -32,5 +32,10 @@ FactoryGirl.define do environment.update_attribute(:deployments, [deployment]) end end + + trait :non_playable do + status 'created' + self.when 'manual' + end end end diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb index dd93b439b2b..4e140102492 100644 --- a/spec/factories/keys.rb +++ b/spec/factories/keys.rb @@ -23,5 +23,9 @@ FactoryGirl.define do factory :another_deploy_key, class: 'DeployKey' do end end + + factory :write_access_key, class: 'DeployKey' do + can_push true + end end end diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index 84d73d693bc..876f33dd03e 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -48,7 +48,9 @@ describe "GitLab Flavored Markdown", feature: true do end end - describe "for issues" do + describe "for issues", feature: true, js: true do + include WaitForVueResource + before do @other_issue = create(:issue, author: @user, @@ -79,6 +81,14 @@ describe "GitLab Flavored Markdown", feature: true do expect(page).to have_link(fred.to_reference) end + + it "renders updated subject once edited somewhere else in issues#show" do + visit namespace_project_issue_path(project.namespace, project, @issue) + @issue.update(title: "fix #{@other_issue.to_reference} and update") + + wait_for_vue_resource + expect(page).to have_text("fix #{@other_issue.to_reference} and update") + end end describe "for merge requests" do diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index 16e453bc328..8e67ab028d7 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' describe 'Awards Emoji', feature: true do include WaitForAjax + include WaitForVueResource let!(:project) { create(:project, :public) } let!(:user) { create(:user) } @@ -22,10 +23,11 @@ describe 'Awards Emoji', feature: true do # The `heart_tip` emoji is not valid anymore so we need to skip validation issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false) visit namespace_project_issue_path(project.namespace, project, issue) + wait_for_vue_resource end # Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529 - it 'does not shows a 500 page' do + it 'does not shows a 500 page', js: true do expect(page).to have_text(issue.title) end end @@ -35,6 +37,7 @@ describe 'Awards Emoji', feature: true do before do visit namespace_project_issue_path(project.namespace, project, issue) + wait_for_vue_resource end it 'increments the thumbsdown emoji', js: true do diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index ce96a420699..cf5259b878d 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -65,7 +65,7 @@ describe 'Dropdown milestone', :feature, :js do it 'should load all the milestones when opened' do filtered_search.set('milestone:') - expect(dropdown_milestone_size).to be > 0 + expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6) end end @@ -84,37 +84,37 @@ describe 'Dropdown milestone', :feature, :js do it 'filters by name' do filtered_search.send_keys('v1') - expect(dropdown_milestone_size).to eq(1) + expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1) end it 'filters by case insensitive name' do filtered_search.send_keys('V1') - expect(dropdown_milestone_size).to eq(1) + expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1) end it 'filters by name with symbol' do filtered_search.send_keys('%v1') - expect(dropdown_milestone_size).to eq(1) + expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1) end it 'filters by case insensitive name with symbol' do filtered_search.send_keys('%V1') - expect(dropdown_milestone_size).to eq(1) + expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1) end it 'filters by special characters' do filtered_search.send_keys('(+') - expect(dropdown_milestone_size).to eq(1) + expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1) end it 'filters by special characters with symbol' do filtered_search.send_keys('%(+') - expect(dropdown_milestone_size).to eq(1) + expect(filter_dropdown).to have_selector('.filter-dropdown .filter-dropdown-item', count: 1) end end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index f89b4db9e62..6c09903a2f6 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -37,8 +37,8 @@ feature 'issue move to another project' do edit_issue(issue) end - scenario 'moving issue to another project' do - first('#move_to_project_id', visible: false).set(new_project.id) + scenario 'moving issue to another project', js: true do + find('#move_to_project_id', visible: false).set(new_project.id) click_button('Save changes') expect(current_url).to include project_path(new_project) diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb index 4bc9b49f889..6001476d0ca 100644 --- a/spec/features/issues/spam_issues_spec.rb +++ b/spec/features/issues/spam_issues_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'New issue', feature: true do +describe 'New issue', feature: true, js: true do include StubENV let(:project) { create(:project, :public) } diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 7afceb88cf9..e3213d24f6a 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -695,4 +695,21 @@ describe 'Issues', feature: true do end end end + + describe 'title issue#show', js: true do + include WaitForVueResource + + it 'updates the title', js: true do + issue = create(:issue, author: @user, assignee: @user, project: project, title: 'new title') + + visit namespace_project_issue_path(project.namespace, project, issue) + + expect(page).to have_text("new title") + + issue.update(title: "updated title") + + wait_for_vue_resource + expect(page).to have_text("updated title") + end + end end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index f36781167fb..d4fe67c224f 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -70,6 +70,18 @@ feature 'Create New Merge Request', feature: true, js: true do visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id }) expect(page).not_to have_content private_project.path_with_namespace + expect(page).to have_content project.path_with_namespace + end + end + + context 'when source project cannot be viewed by the current user' do + it 'does not leak the private project name & namespace' do + private_project = create(:project, :private) + + visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { source_project_id: private_project.id }) + + expect(page).not_to have_content private_project.path_with_namespace + expect(page).to have_content project.path_with_namespace end end diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index ef97b061ca7..3c7c9bdcd08 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -3,8 +3,9 @@ require 'spec_helper' describe GroupProjectsFinder do let(:group) { create(:group) } let(:current_user) { create(:user) } + let(:options) { {} } - let(:finder) { described_class.new(source_user) } + let(:finder) { described_class.new(group: group, current_user: current_user, options: options) } let!(:public_project) { create(:empty_project, :public, group: group, path: '1') } let!(:private_project) { create(:empty_project, :private, group: group, path: '2') } @@ -18,22 +19,27 @@ describe GroupProjectsFinder do shared_project_3.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group) end + subject { finder.execute } + describe 'with a group member current user' do - before { group.add_user(current_user, Gitlab::Access::MASTER) } + before do + group.add_master(current_user) + end context "only shared" do - subject { described_class.new(group, only_shared: true).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) } + let(:options) { { only_shared: true } } + + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1]) } end context "only owned" do - subject { described_class.new(group, only_owned: true).execute(current_user) } - it { is_expected.to eq([private_project, public_project]) } + let(:options) { { only_owned: true } } + + it { is_expected.to match_array([private_project, public_project]) } end context "all" do - subject { described_class.new(group).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) } + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) } end end @@ -44,47 +50,57 @@ describe GroupProjectsFinder do end context "only shared" do + let(:options) { { only_shared: true } } + context "without external user" do - subject { described_class.new(group, only_shared: true).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) } + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1]) } end context "with external user" do - before { current_user.update_attributes(external: true) } - subject { described_class.new(group, only_shared: true).execute(current_user) } - it { is_expected.to eq([shared_project_2, shared_project_1]) } + before do + current_user.update_attributes(external: true) + end + + it { is_expected.to match_array([shared_project_2, shared_project_1]) } end end context "only owned" do + let(:options) { { only_owned: true } } + context "without external user" do - before { private_project.team << [current_user, Gitlab::Access::MASTER] } - subject { described_class.new(group, only_owned: true).execute(current_user) } - it { is_expected.to eq([private_project, public_project]) } + before do + private_project.team << [current_user, Gitlab::Access::MASTER] + end + + it { is_expected.to match_array([private_project, public_project]) } end context "with external user" do - before { current_user.update_attributes(external: true) } - subject { described_class.new(group, only_owned: true).execute(current_user) } - it { is_expected.to eq([public_project]) } - end + before do + current_user.update_attributes(external: true) + end - context "all" do - subject { described_class.new(group).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, public_project]) } + it { is_expected.to eq([public_project]) } end end + + context "all" do + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) } + end end describe "no user" do context "only shared" do - subject { described_class.new(group, only_shared: true).execute(current_user) } - it { is_expected.to eq([shared_project_3, shared_project_1]) } + let(:options) { { only_shared: true } } + + it { is_expected.to match_array([shared_project_3, shared_project_1]) } end context "only owned" do - subject { described_class.new(group, only_owned: true).execute(current_user) } - it { is_expected.to eq([public_project]) } + let(:options) { { only_owned: true } } + + it { is_expected.to eq([public_project]) } end end end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index e44e7434c80..148adcffe3b 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -21,38 +21,144 @@ describe ProjectsFinder do create(:empty_project, :private, name: 'D', path: 'D') end - let(:finder) { described_class.new } + let(:params) { {} } + let(:current_user) { user } + let(:project_ids_relation) { nil } + let(:finder) { described_class.new(params: params, current_user: current_user, project_ids_relation: project_ids_relation) } + + subject { finder.execute } describe 'without a user' do - subject { finder.execute } + let(:current_user) { nil } it { is_expected.to eq([public_project]) } end describe 'with a user' do - subject { finder.execute(user) } - describe 'without private projects' do - it { is_expected.to eq([public_project, internal_project]) } + it { is_expected.to match_array([public_project, internal_project]) } end describe 'with private projects' do before do - private_project.add_user(user, Gitlab::Access::MASTER) + private_project.add_master(user) end - it do - is_expected.to eq([public_project, internal_project, private_project]) - end + it { is_expected.to match_array([public_project, internal_project, private_project]) } end end describe 'with project_ids_relation' do let(:project_ids_relation) { Project.where(id: internal_project.id) } - subject { finder.execute(user, project_ids_relation) } - it { is_expected.to eq([internal_project]) } end + + describe 'filter by visibility_level' do + before do + private_project.add_master(user) + end + + context 'private' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::PRIVATE } } + + it { is_expected.to eq([private_project]) } + end + + context 'internal' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::INTERNAL } } + + it { is_expected.to eq([internal_project]) } + end + + context 'public' do + let(:params) { { visibility_level: Gitlab::VisibilityLevel::PUBLIC } } + + it { is_expected.to eq([public_project]) } + end + end + + describe 'filter by tags' do + before do + public_project.tag_list.add('foo') + public_project.save! + end + + let(:params) { { tag: 'foo' } } + + it { is_expected.to eq([public_project]) } + end + + describe 'filter by personal' do + let!(:personal_project) { create(:empty_project, namespace: user.namespace) } + let(:params) { { personal: true } } + + it { is_expected.to eq([personal_project]) } + end + + describe 'filter by search' do + let(:params) { { search: 'C' } } + + it { is_expected.to eq([public_project]) } + end + + describe 'filter by name for backward compatibility' do + let(:params) { { name: 'C' } } + + it { is_expected.to eq([public_project]) } + end + + describe 'filter by archived' do + let!(:archived_project) { create(:empty_project, :public, :archived, name: 'E', path: 'E') } + + context 'non_archived=true' do + let(:params) { { non_archived: true } } + + it { is_expected.to match_array([public_project, internal_project]) } + end + + context 'non_archived=false' do + let(:params) { { non_archived: false } } + + it { is_expected.to match_array([public_project, internal_project, archived_project]) } + end + + describe 'filter by archived for backward compatibility' do + let(:params) { { archived: false } } + + it { is_expected.to match_array([public_project, internal_project]) } + end + end + + describe 'filter by trending' do + let!(:trending_project) { create(:trending_project, project: public_project) } + let(:params) { { trending: true } } + + it { is_expected.to eq([public_project]) } + end + + describe 'filter by non_public' do + let(:params) { { non_public: true } } + before do + private_project.add_developer(current_user) + end + + it { is_expected.to eq([private_project]) } + end + + describe 'filter by viewable_starred_projects' do + let(:params) { { starred: true } } + before do + current_user.toggle_star(public_project) + end + + it { is_expected.to eq([public_project]) } + end + + describe 'sorting' do + let(:params) { { sort: 'name_asc' } } + + it { is_expected.to eq([internal_project, public_project]) } + end end end diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index 70443d27f33..a7c3c281083 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -2,8 +2,10 @@ require 'spec_helper' describe EventsHelper do describe '#event_note' do + let(:user) { build(:user) } + before do - allow(helper).to receive(:current_user).and_return(double) + allow(helper).to receive(:current_user).and_return(user) end it 'displays one line of plain text without alteration' do @@ -60,11 +62,26 @@ describe EventsHelper do expect(helper.event_note(input)).to eq(expected) end - it 'preserves style attribute within a tag' do - input = '<span class="" style="background-color: #44ad8e; color: #FFFFFF;"></span>' - expected = '<p><span style="background-color: #44ad8e; color: #FFFFFF;"></span></p>' + context 'labels formatting' do + let(:input) { 'this should be ~label_1' } - expect(helper.event_note(input)).to eq(expected) + def format_event_note(project) + create(:label, title: 'label_1', project: project) + + helper.event_note(input, { project: project }) + end + + it 'preserves style attribute for a label that can be accessed by current_user' do + project = create(:empty_project, :public) + + expect(format_event_note(project)).to match(/span class=.*style=.*/) + end + + it 'does not style a label that can not be accessed by current_user' do + project = create(:empty_project, :private) + + expect(format_event_note(project)).to eq("<p>#{input}</p>") + end end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index fc6ad6419ac..44312ada438 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -167,6 +167,7 @@ describe ProjectsHelper do before do allow(project).to receive(:repository_storage_path).and_return('/base/repo/path') + allow(Settings.shared).to receive(:[]).with('path').and_return('/base/repo/export/path') end it 'removes the repo path' do @@ -175,6 +176,13 @@ describe ProjectsHelper do expect(sanitize_repo_path(project, import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git') end + + it 'removes the temporary repo path used for uploads/exports' do + repo = '/base/repo/export/path/tmp/project_exports/uploads/test.tar.gz' + import_error = "Unable to decompress #{repo}\n" + + expect(sanitize_repo_path(project, import_error)).to eq('Unable to decompress [REPO EXPORT PATH]/uploads/test.tar.gz') + end end describe '#last_push_event' do diff --git a/spec/javascripts/blob/3d_viewer/mesh_object_spec.js b/spec/javascripts/blob/3d_viewer/mesh_object_spec.js new file mode 100644 index 00000000000..d1ebae33dab --- /dev/null +++ b/spec/javascripts/blob/3d_viewer/mesh_object_spec.js @@ -0,0 +1,42 @@ +import { + BoxGeometry, +} from 'three/build/three.module'; +import MeshObject from '~/blob/3d_viewer/mesh_object'; + +describe('Mesh object', () => { + it('defaults to non-wireframe material', () => { + const object = new MeshObject( + new BoxGeometry(10, 10, 10), + ); + + expect(object.material.wireframe).toBeFalsy(); + }); + + it('changes to wirefame material', () => { + const object = new MeshObject( + new BoxGeometry(10, 10, 10), + ); + + object.changeMaterial('wireframe'); + + expect(object.material.wireframe).toBeTruthy(); + }); + + it('scales object down', () => { + const object = new MeshObject( + new BoxGeometry(10, 10, 10), + ); + const radius = object.geometry.boundingSphere.radius; + + expect(radius).not.toBeGreaterThan(4); + }); + + it('does not scale object down', () => { + const object = new MeshObject( + new BoxGeometry(1, 1, 1), + ); + const radius = object.geometry.boundingSphere.radius; + + expect(radius).toBeLessThan(1); + }); +}); diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js index 19a4e55a9db..d3a4d04345b 100644 --- a/spec/javascripts/blob/pdf/index_spec.js +++ b/spec/javascripts/blob/pdf/index_spec.js @@ -3,6 +3,18 @@ import testPDF from './test.pdf'; describe('PDF renderer', () => { let viewer; + let app; + + const checkLoaded = (done) => { + if (app.loading) { + setTimeout(() => { + checkLoaded(done); + }, 100); + } else { + done(); + } + }; + preloadFixtures('static/pdf_viewer.html.raw'); beforeEach(() => { @@ -21,11 +33,9 @@ describe('PDF renderer', () => { describe('successful response', () => { beforeEach((done) => { - renderPDF(); + app = renderPDF(); - setTimeout(() => { - done(); - }, 500); + checkLoaded(done); }); it('does not show loading icon', () => { @@ -50,11 +60,9 @@ describe('PDF renderer', () => { describe('error getting file', () => { beforeEach((done) => { viewer.dataset.endpoint = 'invalid/endpoint'; - renderPDF(); + app = renderPDF(); - setTimeout(() => { - done(); - }, 500); + checkLoaded(done); }); it('does not show loading icon', () => { diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js index 549c7af8ea8..beee6cb2969 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/build_spec.js @@ -75,6 +75,7 @@ describe('Build', () => { expect(url).toBe(`${BUILD_URL}.json`); expect(dataType).toBe('json'); expect(success).toEqual(jasmine.any(Function)); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); success.call(context, { trace_html: '<span>Example</span>', status: 'running' }); @@ -83,6 +84,7 @@ describe('Build', () => { it('removes the spinner', () => { const [{ success, context }] = $.ajax.calls.argsFor(0); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); success.call(context, { trace_html: '<span>Example</span>', status: 'success' }); expect($('.js-build-refresh').length).toBe(0); diff --git a/spec/javascripts/environments/environment_actions_spec.js b/spec/javascripts/environments/environment_actions_spec.js index 13840b42bd6..6348d97b0a5 100644 --- a/spec/javascripts/environments/environment_actions_spec.js +++ b/spec/javascripts/environments/environment_actions_spec.js @@ -19,6 +19,11 @@ describe('Actions Component', () => { name: 'foo', play_path: '#', }, + { + name: 'foo bar', + play_path: 'url', + playable: false, + }, ]; spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve()); @@ -49,4 +54,14 @@ describe('Actions Component', () => { expect(spy).toHaveBeenCalledWith(actionsMock[0].play_path); }); + + it('should render a disabled action when it\'s not playable', () => { + expect( + component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'), + ).toEqual('disabled'); + + expect( + component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'), + ).toEqual(true); + }); }); diff --git a/spec/javascripts/issue_show/issue_title_spec.js b/spec/javascripts/issue_show/issue_title_spec.js new file mode 100644 index 00000000000..806d728a874 --- /dev/null +++ b/spec/javascripts/issue_show/issue_title_spec.js @@ -0,0 +1,22 @@ +import Vue from 'vue'; +import issueTitle from '~/issue_show/issue_title'; + +describe('Issue Title', () => { + let IssueTitleComponent; + + beforeEach(() => { + IssueTitleComponent = Vue.extend(issueTitle); + }); + + it('should render a title', () => { + const component = new IssueTitleComponent({ + propsData: { + initialTitle: 'wow', + endpoint: '/gitlab-org/gitlab-shell/issues/9/rendered_title', + }, + }).$mount(); + + expect(component.$el.classList).toContain('title'); + expect(component.$el.innerHTML).toContain('wow'); + }); +}); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 5a93d479c1f..03f3c206f44 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -310,5 +310,56 @@ require('~/lib/utils/common_utils'); }); }, 10000); }); + + describe('gl.utils.setFavicon', () => { + it('should set page favicon to provided favicon', () => { + const faviconName = 'custom_favicon'; + const fakeLink = { + setAttribute() {}, + }; + + spyOn(window.document, 'getElementById').and.callFake(() => fakeLink); + spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => { + expect(attr).toEqual('href'); + expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true); + }); + gl.utils.setFavicon(faviconName); + }); + }); + + describe('gl.utils.resetFavicon', () => { + it('should reset page favicon to tanuki', () => { + const fakeLink = { + setAttribute() {}, + }; + + spyOn(window.document, 'getElementById').and.callFake(() => fakeLink); + spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => { + expect(attr).toEqual('href'); + expect(val).toMatch(/favicon/); + }); + gl.utils.resetFavicon(); + }); + }); + + describe('gl.utils.setCiStatusFavicon', () => { + it('should set page favicon to CI status favicon based on provided status', () => { + const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`; + const FAVICON_PATH = 'ci_favicons/'; + const FAVICON = 'icon_status_success'; + const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub(); + const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub(); + spyOn($, 'ajax').and.callFake(function (options) { + options.success({ icon: FAVICON }); + expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON); + options.success(); + expect(spyResetFavicon).toHaveBeenCalled(); + options.error(); + expect(spyResetFavicon).toHaveBeenCalled(); + }); + + gl.utils.setCiStatusFavicon(BUILD_URL); + }); + }); }); })(); diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index d5193b41c33..88dae8c3e06 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -142,18 +142,21 @@ require('~/lib/utils/datetime_utility'); it('should call showCIStatus even if a notification should not be displayed', function() { var spy; spy = spyOn(this["class"], 'showCIStatus').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status); }); it('should call showCIStatus when a notification should be displayed', function() { var spy; spy = spyOn(this["class"], 'showCIStatus').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(true); return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status); }); it('should call showCICoverage when the coverage rate is set', function() { var spy; spy = spyOn(this["class"], 'showCICoverage').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); return expect(spy).toHaveBeenCalledWith(this.ciStatusData.coverage); }); @@ -161,12 +164,14 @@ require('~/lib/utils/datetime_utility'); var spy; this.ciStatusData.coverage = null; spy = spyOn(this["class"], 'showCICoverage').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); return expect(spy).not.toHaveBeenCalled(); }); it('should not display a notification on the first check after the widget has been created', function() { var spy; spy = spyOn(window, 'notify'); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"] = new window.gl.MergeRequestWidget(this.opts); this["class"].getCIStatus(true); return expect(spy).not.toHaveBeenCalled(); @@ -174,6 +179,7 @@ require('~/lib/utils/datetime_utility'); it('should update the pipeline URL when the pipeline changes', function() { var spy; spy = spyOn(this["class"], 'updatePipelineUrls').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); this.ciStatusData.pipeline += 1; this["class"].getCIStatus(false); @@ -182,6 +188,7 @@ require('~/lib/utils/datetime_utility'); it('should update the commit URL when the sha changes', function() { var spy; spy = spyOn(this["class"], 'updateCommitUrls').and.stub(); + spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {}); this["class"].getCIStatus(false); this.ciStatusData.sha = "9b50b99a"; this["class"].getCIStatus(false); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index b30c5da8822..07dc51a7815 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -64,6 +64,7 @@ if (process.env.BABEL_ENV === 'coverage') { './snippet/snippet_bundle.js', './terminal/terminal_bundle.js', './users/users_bundle.js', + './issue_show/index.js', ]; describe('Uncovered files', function () { diff --git a/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js b/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js index dba998c7688..0910df61915 100644 --- a/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js +++ b/spec/javascripts/vue_pipelines_index/pipelines_actions_spec.js @@ -15,6 +15,11 @@ describe('Pipelines Actions dropdown', () => { name: 'stop_review', path: '/root/review-app/builds/1893/play', }, + { + name: 'foo', + path: '#', + playable: false, + }, ]; spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve()); @@ -59,4 +64,14 @@ describe('Pipelines Actions dropdown', () => { expect(component.$el.querySelector('.fa-spinner')).toEqual(null); }); + + it('should render a disabled action when it\'s not playable', () => { + expect( + component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'), + ).toEqual('disabled'); + + expect( + component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'), + ).toEqual(true); + }); }); diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb new file mode 100644 index 00000000000..897288b8ad5 --- /dev/null +++ b/spec/lib/banzai/filter/markdown_filter_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Banzai::Filter::MarkdownFilter, lib: true do + include FilterSpecHelper + + context 'code block' do + it 'adds language to lang attribute when specified' do + result = filter("```html\nsome code\n```") + + expect(result).to start_with("\n<pre><code lang=\"html\">") + end + + it 'does not add language to lang attribute when not specified' do + result = filter("```\nsome code\n```") + + expect(result).to start_with("\n<pre><code>") + end + end +end diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb index b4cd5f63a15..fdbc65b5e00 100644 --- a/spec/lib/banzai/filter/sanitization_filter_spec.rb +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -49,11 +49,12 @@ describe Banzai::Filter::SanitizationFilter, lib: true do instance = described_class.new('Foo') 3.times { instance.whitelist } - expect(instance.whitelist[:transformers].size).to eq 5 + expect(instance.whitelist[:transformers].size).to eq 4 end - it 'allows syntax highlighting' do - exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>} + it 'sanitizes `class` attribute from all elements' do + act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>} + exp = %q{<pre><code><span class="k">def</span></code></pre>} expect(filter(act).to_html).to eq exp end diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index 63fb1bb25c4..f61fc8ceb9e 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -12,14 +12,14 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do context "when a valid language is specified" do it "highlights as that language" do - result = filter('<pre><code class="ruby">def fun end</code></pre>') + result = filter('<pre><code lang="ruby">def fun end</code></pre>') expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>') end end context "when an invalid language is specified" do it "highlights as plaintext" do - result = filter('<pre><code class="gnuplot">This is a test</code></pre>') + result = filter('<pre><code lang="gnuplot">This is a test</code></pre>') expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>') end end @@ -30,7 +30,7 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do end it "highlights as plaintext" do - result = filter('<pre><code class="ruby">This is a test</code></pre>') + result = filter('<pre><code lang="ruby">This is a test</code></pre>') expect(result.to_html).to eq('<pre class="code highlight" lang="" v-pre="true"><code>This is a test</code></pre>') end end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index e007044868c..4ac79454647 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -58,6 +58,48 @@ describe Gitlab::Database::MigrationHelpers, lib: true do end end + describe '#remove_concurrent_index' do + context 'outside a transaction' do + before do + allow(model).to receive(:transaction_open?).and_return(false) + end + + context 'using PostgreSQL' do + before do + allow(Gitlab::Database).to receive(:postgresql?).and_return(true) + allow(model).to receive(:disable_statement_timeout) + end + + it 'removes the index concurrently' do + expect(model).to receive(:remove_index). + with(:users, { algorithm: :concurrently, column: :foo }) + + model.remove_concurrent_index(:users, :foo) + end + end + + context 'using MySQL' do + it 'removes an index' do + expect(Gitlab::Database).to receive(:postgresql?).and_return(false) + + expect(model).to receive(:remove_index). + with(:users, { column: :foo }) + + model.remove_concurrent_index(:users, :foo) + end + end + end + + context 'inside a transaction' do + it 'raises RuntimeError' do + expect(model).to receive(:transaction_open?).and_return(true) + + expect { model.remove_concurrent_index(:users, :foo) }. + to raise_error(RuntimeError) + end + end + end + describe '#add_concurrent_foreign_key' do context 'inside a transaction' do it 'raises an error' do diff --git a/spec/lib/gitlab/git/attributes_spec.rb b/spec/lib/gitlab/git/attributes_spec.rb index 9c011e34c11..1cfd8db09a5 100644 --- a/spec/lib/gitlab/git/attributes_spec.rb +++ b/spec/lib/gitlab/git/attributes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Git::Attributes, seed_helper: true do let(:path) do - File.join(SEED_REPOSITORY_PATH, 'with-git-attributes.git') + File.join(SEED_STORAGE_PATH, 'with-git-attributes.git') end subject { described_class.new(path) } @@ -141,7 +141,7 @@ describe Gitlab::Git::Attributes, seed_helper: true do end it 'does not yield when the attributes file has an unsupported encoding' do - path = File.join(SEED_REPOSITORY_PATH, 'with-invalid-git-attributes.git') + path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git') attrs = described_class.new(path) expect { |b| attrs.each_line(&b) }.not_to yield_control diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index e169f5af6b6..8b041ac69b1 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe Gitlab::Git::Blame, seed_helper: true do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:blame) do Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md") end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index b883526151e..3f494257545 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe Gitlab::Git::Blob, seed_helper: true do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } describe 'initialize' do let(:blob) { Gitlab::Git::Blob.new(name: 'test') } diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index 78234b396c5..cdf1b8beee3 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Branch, seed_helper: true do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } subject { repository.branches } diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 5cf4631fbfc..3e44c577643 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Commit, seed_helper: true do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:commit) { Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID) } let(:rugged_commit) do repository.rugged.lookup(SeedRepo::Commit::ID) @@ -9,7 +9,7 @@ describe Gitlab::Git::Commit, seed_helper: true do describe "Commit info" do before do - repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged + repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged @committer = { email: 'mike@smith.com', @@ -59,7 +59,7 @@ describe Gitlab::Git::Commit, seed_helper: true do after do # Erase the new commit so other tests get the original repo - repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged + repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID) end end @@ -95,7 +95,7 @@ describe Gitlab::Git::Commit, seed_helper: true do end context 'with broken repo' do - let(:repository) { Gitlab::Git::Repository.new(TEST_BROKEN_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH) } it 'returns nil' do expect(Gitlab::Git::Commit.find(repository, SeedRepo::Commit::ID)).to be_nil diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb index e28debe1494..7c45071ec45 100644 --- a/spec/lib/gitlab/git/compare_spec.rb +++ b/spec/lib/gitlab/git/compare_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Compare, seed_helper: true do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, false) } let(:compare_straight) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, true) } diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 992126ef153..7253a2edeff 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Diff, seed_helper: true do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } before do @raw_diff_hash = { diff --git a/spec/lib/gitlab/git/encoding_helper_spec.rb b/spec/lib/gitlab/git/encoding_helper_spec.rb index 83311536893..27bcc241b82 100644 --- a/spec/lib/gitlab/git/encoding_helper_spec.rb +++ b/spec/lib/gitlab/git/encoding_helper_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe Gitlab::Git::EncodingHelper do let(:ext_class) { Class.new { extend Gitlab::Git::EncodingHelper } } - let(:binary_string) { File.join(SEED_REPOSITORY_PATH, 'gitlab_logo.png') } + let(:binary_string) { File.join(SEED_STORAGE_PATH, 'gitlab_logo.png') } describe '#encode!' do [ diff --git a/spec/lib/gitlab/git/index_spec.rb b/spec/lib/gitlab/git/index_spec.rb index d0c7ca60ddc..07d71f6777d 100644 --- a/spec/lib/gitlab/git/index_spec.rb +++ b/spec/lib/gitlab/git/index_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Git::Index, seed_helper: true do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:index) { described_class.new(repository) } before do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index d4b7684adfd..7e8bb796e03 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe Gitlab::Git::Repository, seed_helper: true do include Gitlab::Git::EncodingHelper - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } describe "Respond to" do subject { repository } @@ -14,6 +14,32 @@ describe Gitlab::Git::Repository, seed_helper: true do it { is_expected.to respond_to(:tags) } end + describe '#root_ref' do + context 'with gitaly disabled' do + before { allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) } + + it 'calls #discover_default_branch' do + expect(repository).to receive(:discover_default_branch) + repository.root_ref + end + end + + context 'with gitaly enabled' do + before { stub_gitaly } + + it 'gets the branch name from GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) + repository.root_ref + end + + it 'wraps GRPC exceptions' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name). + and_raise(GRPC::Unknown) + expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError) + end + end + end + describe "#discover_default_branch" do let(:master) { 'master' } let(:feature) { 'feature' } @@ -55,6 +81,21 @@ describe Gitlab::Git::Repository, seed_helper: true do end it { is_expected.to include("master") } it { is_expected.not_to include("branch-from-space") } + + context 'with gitaly enabled' do + before { stub_gitaly } + + it 'gets the branch names from GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) + subject + end + + it 'wraps GRPC exceptions' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names). + and_raise(GRPC::Unknown) + expect { subject }.to raise_error(Gitlab::Git::CommandError) + end + end end describe '#tag_names' do @@ -71,6 +112,21 @@ describe Gitlab::Git::Repository, seed_helper: true do end it { is_expected.to include("v1.0.0") } it { is_expected.not_to include("v5.0.0") } + + context 'with gitaly enabled' do + before { stub_gitaly } + + it 'gets the tag names from GitalyClient' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) + subject + end + + it 'wraps GRPC exceptions' do + expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names). + and_raise(GRPC::Unknown) + expect { subject }.to raise_error(Gitlab::Git::CommandError) + end + end end shared_examples 'archive check' do |extenstion| @@ -221,7 +277,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end context '#submodules' do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } context 'where repo has submodules' do let(:submodules) { repository.submodules('master') } @@ -290,9 +346,9 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe "#reset" do - change_path = File.join(TEST_NORMAL_REPO_PATH, "CHANGELOG") - untracked_path = File.join(TEST_NORMAL_REPO_PATH, "UNTRACKED") - tracked_path = File.join(TEST_NORMAL_REPO_PATH, "files", "ruby", "popen.rb") + change_path = File.join(SEED_STORAGE_PATH, TEST_NORMAL_REPO_PATH, "CHANGELOG") + untracked_path = File.join(SEED_STORAGE_PATH, TEST_NORMAL_REPO_PATH, "UNTRACKED") + tracked_path = File.join(SEED_STORAGE_PATH, TEST_NORMAL_REPO_PATH, "files", "ruby", "popen.rb") change_text = "New changelog text" untracked_text = "This file is untracked" @@ -311,7 +367,7 @@ describe Gitlab::Git::Repository, seed_helper: true do f.write(untracked_text) end - @normal_repo = Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) + @normal_repo = Gitlab::Git::Repository.new('default', TEST_NORMAL_REPO_PATH) @normal_repo.reset("HEAD", :hard) end @@ -354,7 +410,7 @@ describe Gitlab::Git::Repository, seed_helper: true do context "-b" do before(:all) do - @normal_repo = Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) + @normal_repo = Gitlab::Git::Repository.new('default', TEST_NORMAL_REPO_PATH) @normal_repo.checkout(new_branch, { b: true }, "origin/feature") end @@ -382,7 +438,7 @@ describe Gitlab::Git::Repository, seed_helper: true do context "without -b" do context "and specifying a nonexistent branch" do it "should not do anything" do - normal_repo = Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) + normal_repo = Gitlab::Git::Repository.new('default', TEST_NORMAL_REPO_PATH) expect { normal_repo.checkout(new_branch) }.to raise_error(Rugged::ReferenceError) expect(normal_repo.rugged.branches[new_branch]).to be_nil @@ -402,7 +458,7 @@ describe Gitlab::Git::Repository, seed_helper: true do context "and with a valid branch" do before(:all) do - @normal_repo = Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) + @normal_repo = Gitlab::Git::Repository.new('default', TEST_NORMAL_REPO_PATH) @normal_repo.rugged.branches.create("feature", "origin/feature") @normal_repo.checkout("feature") end @@ -414,13 +470,13 @@ describe Gitlab::Git::Repository, seed_helper: true do end it "should update the working directory" do - File.open(File.join(TEST_NORMAL_REPO_PATH, ".gitignore"), "r") do |f| + File.open(File.join(SEED_STORAGE_PATH, TEST_NORMAL_REPO_PATH, ".gitignore"), "r") do |f| expect(f.read.each_line.to_a).not_to include(".DS_Store\n") end end after(:all) do - FileUtils.rm_rf(TEST_NORMAL_REPO_PATH) + FileUtils.rm_rf(SEED_STORAGE_PATH, TEST_NORMAL_REPO_PATH) ensure_seeds end end @@ -429,7 +485,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe "#delete_branch" do before(:all) do - @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH) @repo.delete_branch("feature") end @@ -449,7 +505,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe "#create_branch" do before(:all) do - @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH) end it "should create a new branch" do @@ -496,7 +552,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe "#remote_delete" do before(:all) do - @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH) @repo.remote_delete("expendable") end @@ -512,7 +568,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe "#remote_add" do before(:all) do - @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH) @repo.remote_add("new_remote", SeedHelper::GITLAB_GIT_TEST_REPO_URL) end @@ -528,7 +584,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe "#remote_update" do before(:all) do - @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH) @repo.remote_update("expendable", url: TEST_NORMAL_REPO_PATH) end @@ -551,7 +607,7 @@ describe Gitlab::Git::Repository, seed_helper: true do before(:context) do # Add new commits so that there's a renamed file in the commit history - repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged + repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged commit_with_old_name = new_commit_edit_old_file(repo) rename_commit = new_commit_move_file(repo) @@ -560,7 +616,7 @@ describe Gitlab::Git::Repository, seed_helper: true do after(:context) do # Erase our commits so other tests get the original repo - repo = Gitlab::Git::Repository.new(TEST_REPO_PATH).rugged + repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID) end @@ -885,7 +941,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe '#autocrlf' do before(:all) do - @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH) @repo.rugged.config['core.autocrlf'] = true end @@ -900,14 +956,14 @@ describe Gitlab::Git::Repository, seed_helper: true do describe '#autocrlf=' do before(:all) do - @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH) @repo.rugged.config['core.autocrlf'] = false end it 'should set the autocrlf option to the provided option' do @repo.autocrlf = :input - File.open(File.join(TEST_MUTABLE_REPO_PATH, '.git', 'config')) do |config_file| + File.open(File.join(SEED_STORAGE_PATH, TEST_MUTABLE_REPO_PATH, '.git', 'config')) do |config_file| expect(config_file.read).to match('autocrlf = input') end end @@ -999,7 +1055,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe "#copy_gitattributes" do - let(:attributes_path) { File.join(TEST_REPO_PATH, 'info/attributes') } + let(:attributes_path) { File.join(SEED_STORAGE_PATH, TEST_REPO_PATH, 'info/attributes') } it "raises an error with invalid ref" do expect { repository.copy_gitattributes("invalid") }.to raise_error(Gitlab::Git::Repository::InvalidRef) @@ -1075,7 +1131,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#diffable' do - info_dir_path = attributes_path = File.join(TEST_REPO_PATH, 'info') + info_dir_path = attributes_path = File.join(SEED_STORAGE_PATH, TEST_REPO_PATH, 'info') attributes_path = File.join(info_dir_path, 'attributes') before(:all) do @@ -1143,7 +1199,7 @@ describe Gitlab::Git::Repository, seed_helper: true do describe '#local_branches' do before(:all) do - @repo = Gitlab::Git::Repository.new(TEST_MUTABLE_REPO_PATH) + @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH) end after(:all) do @@ -1235,4 +1291,11 @@ describe Gitlab::Git::Repository, seed_helper: true do sha = Rugged::Commit.create(repo, options) repo.lookup(sha) end + + def stub_gitaly + allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true) + + stub = double(:stub) + allow(Gitaly::Ref::Stub).to receive(:new).and_return(stub) + end end diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb index ad469e94735..67a9c974298 100644 --- a/spec/lib/gitlab/git/tag_spec.rb +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Tag, seed_helper: true do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } describe 'first tag' do let(:tag) { repository.tags.first } diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 82685712b5b..4b76a43e6b5 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe Gitlab::Git::Tree, seed_helper: true do context :repo do - let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) } it { expect(tree).to be_kind_of Array } diff --git a/spec/lib/gitlab/gitaly_client/notifications_spec.rb b/spec/lib/gitlab/gitaly_client/notifications_spec.rb index bb5d93994ad..39c2048fef8 100644 --- a/spec/lib/gitlab/gitaly_client/notifications_spec.rb +++ b/spec/lib/gitlab/gitaly_client/notifications_spec.rb @@ -2,12 +2,15 @@ require 'spec_helper' describe Gitlab::GitalyClient::Notifications do describe '#post_receive' do + let(:project) { create(:empty_project) } + let(:repo_path) { project.repository.path_to_repo } + subject { described_class.new(project.repository_storage, project.full_path + '.git') } + it 'sends a post_receive message' do - repo_path = create(:empty_project).repository.path_to_repo expect_any_instance_of(Gitaly::Notifications::Stub). - to receive(:post_receive).with(post_receive_request_with_repo_path(repo_path)) + to receive(:post_receive).with(gitaly_request_with_repo_path(repo_path)) - described_class.new(repo_path).post_receive + subject.post_receive end end end diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_spec.rb new file mode 100644 index 00000000000..79c9ca993e4 --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/ref_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::Ref do + let(:project) { create(:empty_project) } + let(:repo_path) { project.repository.path_to_repo } + let(:client) { Gitlab::GitalyClient::Ref.new(project.repository_storage, project.full_path + '.git') } + + before do + allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true) + end + + describe '#branch_names' do + it 'sends a find_all_branch_names message' do + expect_any_instance_of(Gitaly::Ref::Stub). + to receive(:find_all_branch_names).with(gitaly_request_with_repo_path(repo_path)). + and_return([]) + + client.branch_names + end + end + + describe '#tag_names' do + it 'sends a find_all_tag_names message' do + expect_any_instance_of(Gitaly::Ref::Stub). + to receive(:find_all_tag_names).with(gitaly_request_with_repo_path(repo_path)). + and_return([]) + + client.tag_names + end + end + + describe '#default_branch_name' do + it 'sends a find_default_branch_name message' do + expect_any_instance_of(Gitaly::Ref::Stub). + to receive(:find_default_branch_name).with(gitaly_request_with_repo_path(repo_path)). + and_return(double(name: 'foo')) + + client.default_branch_name + end + end +end diff --git a/spec/lib/gitlab/polling_interval_spec.rb b/spec/lib/gitlab/polling_interval_spec.rb index 56c2847e26a..5ea8ecb1c30 100644 --- a/spec/lib/gitlab/polling_interval_spec.rb +++ b/spec/lib/gitlab/polling_interval_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::PollingInterval, lib: true do it 'sets value to -1' do polling_interval.set_header(response, interval: 10_000) - expect(headers['Poll-Interval']).to eq(-1) + expect(headers['Poll-Interval']).to eq('-1') end end @@ -27,7 +27,7 @@ describe Gitlab::PollingInterval, lib: true do it 'applies modifier to base interval' do polling_interval.set_header(response, interval: 10_000) - expect(headers['Poll-Interval']).to eq(3333) + expect(headers['Poll-Interval']).to eq('3333') end end end diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb index 287bf62d9bd..6307f8c16a3 100644 --- a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb +++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::SidekiqStatus::ClientMiddleware do describe '#call' do it 'tracks the job in Redis' do - expect(Gitlab::SidekiqStatus).to receive(:set).with('123') + expect(Gitlab::SidekiqStatus).to receive(:set).with('123', Gitlab::SidekiqStatus::DEFAULT_EXPIRATION) described_class.new. call('Foo', { 'jid' => '123' }, double(:queue), double(:pool)) { nil } diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb index 56f06b61afb..496e50fbae4 100644 --- a/spec/lib/gitlab/sidekiq_status_spec.rb +++ b/spec/lib/gitlab/sidekiq_status_spec.rb @@ -73,4 +73,17 @@ describe Gitlab::SidekiqStatus do expect(key).to include('123') end end + + describe 'completed', :redis do + it 'returns the completed job' do + expect(described_class.completed_jids(%w(123))).to eq(['123']) + end + + it 'returns only the jobs completed' do + described_class.set('123') + described_class.set('456') + + expect(described_class.completed_jids(%w(123 456 789))).to eq(['789']) + end + end end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 9fbcb1fee69..b703e9808a8 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -188,10 +188,8 @@ describe Gitlab::Workhorse, lib: true do context 'when Gitaly is enabled' do let(:gitaly_params) do - address = Gitlab::GitalyClient.get_address('default') { - GitalySocketPath: URI(address).path, - GitalyAddress: address, + GitalyAddress: Gitlab::GitalyClient.get_address('default'), } end diff --git a/spec/mailers/previews/notify_preview.rb b/spec/mailers/previews/notify_preview.rb index 7bdd88a468b..580f0d56a92 100644 --- a/spec/mailers/previews/notify_preview.rb +++ b/spec/mailers/previews/notify_preview.rb @@ -94,4 +94,14 @@ class NotifyPreview < ActionMailer::Preview email end + + def pipeline_success_email + pipeline = Ci::Pipeline.last + Notify.pipeline_success_email(pipeline, pipeline.user.try(:email)) + end + + def pipeline_failed_email + pipeline = Ci::Pipeline.last + Notify.pipeline_failed_email(pipeline, pipeline.user.try(:email)) + end end diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index 09b1fda3796..0f29766db41 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -111,6 +111,20 @@ describe Blob do end end + describe '#stl?' do + it 'is falsey with image extension' do + git_blob = Gitlab::Git::Blob.new(name: 'file.png') + + expect(described_class.decorate(git_blob)).not_to be_stl + end + + it 'is truthy with STL extension' do + git_blob = Gitlab::Git::Blob.new(name: 'file.stl') + + expect(described_class.decorate(git_blob)).to be_stl + end + end + describe '#to_partial_path' do let(:project) { double(lfs_enabled?: true) } @@ -122,7 +136,8 @@ describe Blob do lfs_pointer?: false, svg?: false, text?: false, - binary?: false + binary?: false, + stl?: false ) described_class.decorate(double).tap do |blob| @@ -175,6 +190,11 @@ describe Blob do blob = stubbed_blob(text?: true, sketch?: true, binary?: true) expect(blob.to_partial_path(project)).to eq 'sketch' end + + it 'handles STLs' do + blob = stubbed_blob(text?: true, stl?: true) + expect(blob.to_partial_path(project)).to eq 'stl' + end end describe '#size_within_svg_limits?' do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index ccaf0d7abc7..d9216112259 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -162,28 +162,44 @@ describe Namespace, models: true do it { expect { @namespace.move_dir }.to raise_error('Namespace cannot be moved, because at least one project has tags in container registry') } end - context 'renaming a sub-group' do + context 'with subgroups' do let(:parent) { create(:group, name: 'parent', path: 'parent') } let(:child) { create(:group, name: 'child', path: 'child', parent: parent) } let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child) } - let(:uploads_dir) { File.join(CarrierWave.root, 'uploads', 'parent') } - let(:pages_dir) { File.join(TestEnv.pages_path, 'parent') } + let(:uploads_dir) { File.join(CarrierWave.root, 'uploads') } + let(:pages_dir) { TestEnv.pages_path } before do - FileUtils.mkdir_p(File.join(uploads_dir, 'child', 'the-project')) - FileUtils.mkdir_p(File.join(pages_dir, 'child', 'the-project')) + FileUtils.mkdir_p(File.join(uploads_dir, 'parent', 'child', 'the-project')) + FileUtils.mkdir_p(File.join(pages_dir, 'parent', 'child', 'the-project')) end - it 'correctly moves the repository, uploads and pages' do - expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git') - expected_upload_path = File.join(uploads_dir, 'renamed', 'the-project') - expected_pages_path = File.join(pages_dir, 'renamed', 'the-project') + context 'renaming child' do + it 'correctly moves the repository, uploads and pages' do + expected_repository_path = File.join(TestEnv.repos_path, 'parent', 'renamed', 'the-project.git') + expected_upload_path = File.join(uploads_dir, 'parent', 'renamed', 'the-project') + expected_pages_path = File.join(pages_dir, 'parent', 'renamed', 'the-project') - child.update_attributes!(path: 'renamed') + child.update_attributes!(path: 'renamed') - expect(File.directory?(expected_repository_path)).to be(true) - expect(File.directory?(expected_upload_path)).to be(true) - expect(File.directory?(expected_pages_path)).to be(true) + expect(File.directory?(expected_repository_path)).to be(true) + expect(File.directory?(expected_upload_path)).to be(true) + expect(File.directory?(expected_pages_path)).to be(true) + end + end + + context 'renaming parent' do + it 'correctly moves the repository, uploads and pages' do + expected_repository_path = File.join(TestEnv.repos_path, 'renamed', 'child', 'the-project.git') + expected_upload_path = File.join(uploads_dir, 'renamed', 'child', 'the-project') + expected_pages_path = File.join(pages_dir, 'renamed', 'child', 'the-project') + + parent.update_attributes!(path: 'renamed') + + expect(File.directory?(expected_repository_path)).to be(true) + expect(File.directory?(expected_upload_path)).to be(true) + expect(File.directory?(expected_pages_path)).to be(true) + end end end end @@ -295,4 +311,13 @@ describe Namespace, models: true do to eq([namespace.owner_id]) end end + + describe '#all_projects' do + let(:group) { create(:group) } + let(:child) { create(:group, parent: group) } + let!(:project1) { create(:project_empty_repo, namespace: group) } + let!(:project2) { create(:project_empty_repo, namespace: child) } + + it { expect(group.all_projects.to_a).to eq([project2, project1]) } + end end diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 4f4b18cf0e0..e1beac28dab 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -108,6 +108,15 @@ describe API::DeployKeys, api: true do expect(response).to have_http_status(201) end + + it 'accepts can_push parameter' do + key_attrs = attributes_for :write_access_key + + post api("/projects/#{project.id}/deploy_keys", admin), key_attrs + + expect(response).to have_http_status(201) + expect(json_response['can_push']).to eq(true) + end end describe 'DELETE /projects/:id/deploy_keys/:key_id' do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 504ff19a17f..551aae7d701 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -12,6 +12,8 @@ describe API::Issues, api: true do let(:assignee) { create(:assignee) } let(:admin) { create(:user, :admin) } let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) } + let(:issue_title) { 'foo' } + let(:issue_description) { 'closed' } let!(:closed_issue) do create :closed_issue, author: user, @@ -38,7 +40,9 @@ describe API::Issues, api: true do project: project, milestone: milestone, created_at: generate(:past_time), - updated_at: 1.hour.ago + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description end let!(:label) do create(:label, title: 'label', color: '#FFAABB', project: project) @@ -61,6 +65,7 @@ describe API::Issues, api: true do context "when unauthenticated" do it "returns authentication error" do get api("/issues") + expect(response).to have_http_status(401) end end @@ -69,9 +74,7 @@ describe API::Issues, api: true do it "returns an array of issues" do get api("/issues", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + expect_paginated_array_response(size: 2) expect(json_response.first['title']).to eq(issue.title) expect(json_response.last).to have_key('web_url') end @@ -79,41 +82,43 @@ describe API::Issues, api: true do it 'returns an array of closed issues' do get api('/issues?state=closed', user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(closed_issue.id) end it 'returns an array of opened issues' do get api('/issues?state=opened', user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(issue.id) end it 'returns an array of all issues' do get api('/issues?state=all', user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response(size: 2) expect(json_response.first['id']).to eq(issue.id) expect(json_response.second['id']).to eq(closed_issue.id) end + it 'returns issues matching given search string for title' do + get api("/issues?search=#{issue.title}", user) + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(issue.id) + end + + it 'returns issues matching given search string for description' do + get api("/issues?search=#{issue.description}", user) + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(issue.id) + end + it 'returns an array of labeled issues' do get api("/issues?labels=#{label.title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label.title]) end @@ -126,29 +131,20 @@ describe API::Issues, api: true do get api("/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}" - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) end it 'returns an empty array if no issue matches labels' do get api('/issues?labels=foo,bar', user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an array of labeled issues matching given state' do get api("/issues?labels=#{label.title}&state=opened", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label.title]) expect(json_response.first['state']).to eq('opened') end @@ -156,47 +152,32 @@ describe API::Issues, api: true do it 'returns unlabeled issues for "No Label" label' do get api("/issues", user), labels: 'No Label' - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to be_empty end it 'returns an empty array if no issue matches labels and state filters' do get api("/issues?labels=#{label.title}&state=closed", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an empty array if no issue matches milestone' do get api("/issues?milestone=#{empty_milestone.title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an empty array if milestone does not exist' do get api("/issues?milestone=foo", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an array of issues in given milestone' do get api("/issues?milestone=#{milestone.title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response(size: 2) expect(json_response.first['id']).to eq(issue.id) expect(json_response.second['id']).to eq(closed_issue.id) end @@ -205,49 +186,36 @@ describe API::Issues, api: true do get api("/issues?milestone=#{milestone.title}"\ '&state=closed', user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(closed_issue.id) end it 'returns an array of issues with no milestone' do get api("/issues?milestone=#{no_milestone_title}", author) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(confidential_issue.id) end it 'returns an array of issues found by iids' do get api('/issues', user), iids: [closed_issue.iid] - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(closed_issue.id) end it 'returns an empty array if iid does not exist' do get api("/issues", user), iids: [99999] - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'sorts by created_at descending by default' do get api('/issues', user) response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 2) expect(response_dates).to eq(response_dates.sort.reverse) end @@ -255,9 +223,8 @@ describe API::Issues, api: true do get api('/issues?sort=asc', user) response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 2) expect(response_dates).to eq(response_dates.sort) end @@ -265,9 +232,8 @@ describe API::Issues, api: true do get api('/issues?order_by=updated_at', user) response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 2) expect(response_dates).to eq(response_dates.sort.reverse) end @@ -275,9 +241,8 @@ describe API::Issues, api: true do get api('/issues?order_by=updated_at&sort=asc', user) response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 2) expect(response_dates).to eq(response_dates.sort) end @@ -316,7 +281,9 @@ describe API::Issues, api: true do assignee: user, project: group_project, milestone: group_milestone, - updated_at: 1.hour.ago + updated_at: 1.hour.ago, + title: issue_title, + description: issue_description end let!(:group_label) do create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project) @@ -336,74 +303,65 @@ describe API::Issues, api: true do it 'returns all group issues (including opened and closed)' do get api(base_url, admin) - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect_paginated_array_response(size: 3) end it 'returns group issues without confidential issues for non project members' do get api("#{base_url}?state=opened", non_member) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['title']).to eq(group_issue.title) end it 'returns group confidential issues for author' do get api("#{base_url}?state=opened", author) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response(size: 2) end it 'returns group confidential issues for assignee' do get api("#{base_url}?state=opened", assignee) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response(size: 2) end it 'returns group issues with confidential issues for project members' do get api("#{base_url}?state=opened", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response(size: 2) end it 'returns group confidential issues for admin' do get api("#{base_url}?state=opened", admin) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response(size: 2) end it 'returns an array of labeled group issues' do get api("#{base_url}?labels=#{group_label.title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([group_label.title]) end it 'returns an array of labeled group issues where all labels match' do get api("#{base_url}?labels=#{group_label.title},foo,bar", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) + end + + it 'returns issues matching given search string for title' do + get api("#{base_url}?search=#{group_issue.title}", user) + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(group_issue.id) + end + + it 'returns issues matching given search string for description' do + get api("#{base_url}?search=#{group_issue.description}", user) + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(group_issue.id) end it 'returns an array of labeled issues when all labels matches' do @@ -415,65 +373,45 @@ describe API::Issues, api: true do get api("#{base_url}", user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}" - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) end it 'returns an array of issues found by iids' do get api(base_url, user), iids: [group_issue.iid] - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) end it 'returns an empty array if iid does not exist' do get api(base_url, user), iids: [99999] - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an empty array if no group issue matches labels' do get api("#{base_url}?labels=foo,bar", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an empty array if no issue matches milestone' do get api("#{base_url}?milestone=#{group_empty_milestone.title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an empty array if milestone does not exist' do get api("#{base_url}?milestone=foo", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an array of issues in given milestone' do get api("#{base_url}?state=opened&milestone=#{group_milestone.title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) end @@ -481,10 +419,7 @@ describe API::Issues, api: true do get api("#{base_url}?milestone=#{group_milestone.title}"\ '&state=closed', user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_closed_issue.id) end @@ -492,9 +427,8 @@ describe API::Issues, api: true do get api("#{base_url}?milestone=#{no_milestone_title}", user) expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_confidential_issue.id) end @@ -502,9 +436,8 @@ describe API::Issues, api: true do get api(base_url, user) response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 3) expect(response_dates).to eq(response_dates.sort.reverse) end @@ -512,9 +445,8 @@ describe API::Issues, api: true do get api("#{base_url}?sort=asc", user) response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 3) expect(response_dates).to eq(response_dates.sort) end @@ -522,9 +454,8 @@ describe API::Issues, api: true do get api("#{base_url}?order_by=updated_at", user) response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 3) expect(response_dates).to eq(response_dates.sort.reverse) end @@ -532,9 +463,8 @@ describe API::Issues, api: true do get api("#{base_url}?order_by=updated_at&sort=asc", user) response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 3) expect(response_dates).to eq(response_dates.sort) end end @@ -563,79 +493,55 @@ describe API::Issues, api: true do get api("/projects/#{restricted_project.id}/issues", non_member) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response).to eq([]) + expect_paginated_array_response(size: 0) end it 'returns project issues without confidential issues for non project members' do get api("#{base_url}/issues", non_member) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response(size: 2) expect(json_response.first['title']).to eq(issue.title) end it 'returns project issues without confidential issues for project members with guest role' do get api("#{base_url}/issues", guest) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response(size: 2) expect(json_response.first['title']).to eq(issue.title) end it 'returns project confidential issues for author' do get api("#{base_url}/issues", author) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect_paginated_array_response(size: 3) expect(json_response.first['title']).to eq(issue.title) end it 'returns project confidential issues for assignee' do get api("#{base_url}/issues", assignee) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect_paginated_array_response(size: 3) expect(json_response.first['title']).to eq(issue.title) end it 'returns project issues with confidential issues for project members' do get api("#{base_url}/issues", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect_paginated_array_response(size: 3) expect(json_response.first['title']).to eq(issue.title) end it 'returns project confidential issues for admin' do get api("#{base_url}/issues", admin) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(3) + expect_paginated_array_response(size: 3) expect(json_response.first['title']).to eq(issue.title) end it 'returns an array of labeled project issues' do get api("#{base_url}/issues?labels=#{label.title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label.title]) end @@ -648,74 +554,65 @@ describe API::Issues, api: true do get api("#{base_url}/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}" - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title]) end + it 'returns issues matching given search string for title' do + get api("#{base_url}/issues?search=#{issue.title}", user) + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(issue.id) + end + + it 'returns issues matching given search string for description' do + get api("#{base_url}/issues?search=#{issue.description}", user) + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(issue.id) + end + it 'returns an array of issues found by iids' do get api("#{base_url}/issues", user), iids: [issue.iid] - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(issue.id) end it 'returns an empty array if iid does not exist' do get api("#{base_url}/issues", user), iids: [99999] - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an empty array if not all labels matches' do get api("#{base_url}/issues?labels=#{label.title},foo", user) - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an empty array if no project issue matches labels' do get api("#{base_url}/issues?labels=foo,bar", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an empty array if no issue matches milestone' do get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an empty array if milestone does not exist' do get api("#{base_url}/issues?milestone=foo", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(0) + expect_paginated_array_response(size: 0) end it 'returns an array of issues in given milestone' do get api("#{base_url}/issues?milestone=#{milestone.title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) + expect_paginated_array_response(size: 2) expect(json_response.first['id']).to eq(issue.id) expect(json_response.second['id']).to eq(closed_issue.id) end @@ -723,20 +620,14 @@ describe API::Issues, api: true do it 'returns an array of issues matching state in milestone' do get api("#{base_url}/issues?milestone=#{milestone.title}&state=closed", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(closed_issue.id) end it 'returns an array of issues with no milestone' do get api("#{base_url}/issues?milestone=#{no_milestone_title}", user) - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(confidential_issue.id) end @@ -744,9 +635,8 @@ describe API::Issues, api: true do get api("#{base_url}/issues", user) response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 3) expect(response_dates).to eq(response_dates.sort.reverse) end @@ -754,9 +644,8 @@ describe API::Issues, api: true do get api("#{base_url}/issues?sort=asc", user) response_dates = json_response.map { |issue| issue['created_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 3) expect(response_dates).to eq(response_dates.sort) end @@ -764,9 +653,8 @@ describe API::Issues, api: true do get api("#{base_url}/issues?order_by=updated_at", user) response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 3) expect(response_dates).to eq(response_dates.sort.reverse) end @@ -774,9 +662,8 @@ describe API::Issues, api: true do get api("#{base_url}/issues?order_by=updated_at&sort=asc", user) response_dates = json_response.map { |issue| issue['updated_at'] } - expect(response).to have_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array + + expect_paginated_array_response(size: 3) expect(response_dates).to eq(response_dates.sort) end end @@ -1457,4 +1344,11 @@ describe API::Issues, api: true do include_examples 'time tracking endpoints', 'issue' end + + def expect_paginated_array_response(size: nil) + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(size) if size + end end diff --git a/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb new file mode 100644 index 00000000000..a714bf4e5d5 --- /dev/null +++ b/spec/rubocop/cop/migration/remove_concurrent_index_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/remove_concurrent_index' + +describe RuboCop::Cop::Migration::RemoveConcurrentIndex do + include CopHelper + + subject(:cop) { described_class.new } + + context 'in migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when remove_concurrent_index is used inside a change method' do + inspect_source(cop, 'def change; remove_concurrent_index :table, :column; end') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + + it 'registers no offense when remove_concurrent_index is used inside an up method' do + inspect_source(cop, 'def up; remove_concurrent_index :table, :column; end') + + expect(cop.offenses.size).to eq(0) + end + end + + context 'outside of migration' do + it 'registers no offense' do + inspect_source(cop, 'def change; remove_concurrent_index :table, :column; end') + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/rubocop/cop/migration/remove_index_spec.rb b/spec/rubocop/cop/migration/remove_index_spec.rb new file mode 100644 index 00000000000..31923cb7429 --- /dev/null +++ b/spec/rubocop/cop/migration/remove_index_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +require 'rubocop' +require 'rubocop/rspec/support' + +require_relative '../../../../rubocop/cop/migration/remove_index' + +describe RuboCop::Cop::Migration::RemoveIndex do + include CopHelper + + subject(:cop) { described_class.new } + + context 'in migration' do + before do + allow(cop).to receive(:in_migration?).and_return(true) + end + + it 'registers an offense when remove_index is used' do + inspect_source(cop, 'def change; remove_index :table, :column; end') + + aggregate_failures do + expect(cop.offenses.size).to eq(1) + expect(cop.offenses.map(&:line)).to eq([1]) + end + end + end + + context 'outside of migration' do + it 'registers no offense' do + inspect_source(cop, 'def change; remove_index :table, :column; end') + + expect(cop.offenses.size).to eq(0) + end + end +end diff --git a/spec/serializers/build_action_entity_spec.rb b/spec/serializers/build_action_entity_spec.rb index 0f7be8b2c39..54ac17447b1 100644 --- a/spec/serializers/build_action_entity_spec.rb +++ b/spec/serializers/build_action_entity_spec.rb @@ -17,5 +17,9 @@ describe BuildActionEntity do it 'contains path to the action play' do expect(subject[:path]).to include "builds/#{build.id}/play" end + + it 'contains whether it is playable' do + expect(subject[:playable]).to eq build.playable? + end end end diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb index 7dcdf54fd93..f76a5cf72d1 100644 --- a/spec/serializers/build_entity_spec.rb +++ b/spec/serializers/build_entity_spec.rb @@ -24,6 +24,10 @@ describe BuildEntity do expect(subject).not_to include(/variables/) end + it 'contains whether it is playable' do + expect(subject[:playable]).to eq build.playable? + end + it 'contains timestamps' do expect(subject).to include(:created_at, :updated_at) end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index c8bd4d4601a..be9f9ea2dec 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -4,6 +4,8 @@ describe MergeRequests::BuildService, services: true do include RepoHelpers let(:project) { create(:project, :repository) } + let(:source_project) { nil } + let(:target_project) { nil } let(:user) { create(:user) } let(:issue_confidential) { false } let(:issue) { create(:issue, project: project, title: 'A bug', confidential: issue_confidential) } @@ -20,7 +22,9 @@ describe MergeRequests::BuildService, services: true do MergeRequests::BuildService.new(project, user, description: description, source_branch: source_branch, - target_branch: target_branch) + target_branch: target_branch, + source_project: source_project, + target_project: target_project) end before do @@ -256,5 +260,41 @@ describe MergeRequests::BuildService, services: true do ) end end + + context 'target_project is set and accessible by current_user' do + let(:target_project) { create(:project, :public, :repository)} + let(:commits) { Commit.decorate([commit_1], project) } + + it 'sets target project correctly' do + expect(merge_request.target_project).to eq(target_project) + end + end + + context 'target_project is set but not accessible by current_user' do + let(:target_project) { create(:project, :private, :repository)} + let(:commits) { Commit.decorate([commit_1], project) } + + it 'sets target project correctly' do + expect(merge_request.target_project).to eq(project) + end + end + + context 'source_project is set and accessible by current_user' do + let(:source_project) { create(:project, :public, :repository)} + let(:commits) { Commit.decorate([commit_1], project) } + + it 'sets target project correctly' do + expect(merge_request.source_project).to eq(source_project) + end + end + + context 'source_project is set but not accessible by current_user' do + let(:source_project) { create(:project, :private, :repository)} + let(:commits) { Commit.decorate([commit_1], project) } + + it 'sets target project correctly' do + expect(merge_request.source_project).to eq(project) + end + end end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index c22d145ca5d..03215a4624a 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -49,6 +49,7 @@ describe MergeRequests::RefreshService, services: true do context 'push to origin repo source branch' do let(:refresh_service) { service.new(@project, @user) } + before do allow(refresh_service).to receive(:execute_hooks) refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') @@ -70,6 +71,32 @@ describe MergeRequests::RefreshService, services: true do end end + context 'push to origin repo source branch when an MR was reopened' do + let(:refresh_service) { service.new(@project, @user) } + + before do + @merge_request.update(state: :reopened) + + allow(refresh_service).to receive(:execute_hooks) + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it 'executes hooks with update action' do + expect(refresh_service).to have_received(:execute_hooks). + with(@merge_request, 'update', @oldrev) + + expect(@merge_request.notes).not_to be_empty + expect(@merge_request).to be_open + expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey + expect(@merge_request.diff_head_sha).to eq(@newrev) + expect(@fork_merge_request).to be_open + expect(@fork_merge_request.notes).to be_empty + expect(@build_failed_todo).to be_done + expect(@fork_build_failed_todo).to be_done + end + end + context 'push to origin repo target branch' do before do service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb index d7a53820684..65dbc01f6e4 100644 --- a/spec/support/matchers/gitaly_matchers.rb +++ b/spec/support/matchers/gitaly_matchers.rb @@ -1,3 +1,3 @@ -RSpec::Matchers.define :post_receive_request_with_repo_path do |path| +RSpec::Matchers.define :gitaly_request_with_repo_path do |path| match { |actual| actual.repository.path == path } end diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb index f55fee28ff9..47b5f556e66 100644 --- a/spec/support/seed_helper.rb +++ b/spec/support/seed_helper.rb @@ -1,20 +1,22 @@ +require_relative 'test_env' + # This file is specific to specs in spec/lib/gitlab/git/ -SEED_REPOSITORY_PATH = File.expand_path('../../tmp/repositories', __dir__) -TEST_REPO_PATH = File.join(SEED_REPOSITORY_PATH, 'gitlab-git-test.git') -TEST_NORMAL_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "not-bare-repo.git") -TEST_MUTABLE_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "mutable-repo.git") -TEST_BROKEN_REPO_PATH = File.join(SEED_REPOSITORY_PATH, "broken-repo.git") +SEED_STORAGE_PATH = TestEnv.repos_path +TEST_REPO_PATH = 'gitlab-git-test.git'.freeze +TEST_NORMAL_REPO_PATH = 'not-bare-repo.git'.freeze +TEST_MUTABLE_REPO_PATH = 'mutable-repo.git'.freeze +TEST_BROKEN_REPO_PATH = 'broken-repo.git'.freeze module SeedHelper GITLAB_GIT_TEST_REPO_URL = ENV.fetch('GITLAB_GIT_TEST_REPO_URL', 'https://gitlab.com/gitlab-org/gitlab-git-test.git').freeze def ensure_seeds - if File.exist?(SEED_REPOSITORY_PATH) - FileUtils.rm_r(SEED_REPOSITORY_PATH) + if File.exist?(SEED_STORAGE_PATH) + FileUtils.rm_r(SEED_STORAGE_PATH) end - FileUtils.mkdir_p(SEED_REPOSITORY_PATH) + FileUtils.mkdir_p(SEED_STORAGE_PATH) create_bare_seeds create_normal_seeds @@ -26,41 +28,45 @@ module SeedHelper def create_bare_seeds system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{GITLAB_GIT_TEST_REPO_URL}), - chdir: SEED_REPOSITORY_PATH, + chdir: SEED_STORAGE_PATH, out: '/dev/null', err: '/dev/null') end def create_normal_seeds system(git_env, *%W(#{Gitlab.config.git.bin_path} clone #{TEST_REPO_PATH} #{TEST_NORMAL_REPO_PATH}), + chdir: SEED_STORAGE_PATH, out: '/dev/null', err: '/dev/null') end def create_mutable_seeds system(git_env, *%W(#{Gitlab.config.git.bin_path} clone #{TEST_REPO_PATH} #{TEST_MUTABLE_REPO_PATH}), + chdir: SEED_STORAGE_PATH, out: '/dev/null', err: '/dev/null') - system(git_env, *%w(git branch -t feature origin/feature), - chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null') + mutable_repo_full_path = File.join(SEED_STORAGE_PATH, TEST_MUTABLE_REPO_PATH) + system(git_env, *%W(#{Gitlab.config.git.bin_path} branch -t feature origin/feature), + chdir: mutable_repo_full_path, out: '/dev/null', err: '/dev/null') system(git_env, *%W(#{Gitlab.config.git.bin_path} remote add expendable #{GITLAB_GIT_TEST_REPO_URL}), - chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null') + chdir: mutable_repo_full_path, out: '/dev/null', err: '/dev/null') end def create_broken_seeds system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} #{TEST_BROKEN_REPO_PATH}), + chdir: SEED_STORAGE_PATH, out: '/dev/null', err: '/dev/null') - refs_path = File.join(TEST_BROKEN_REPO_PATH, 'refs') + refs_path = File.join(SEED_STORAGE_PATH, TEST_BROKEN_REPO_PATH, 'refs') FileUtils.rm_r(refs_path) end def create_git_attributes - dir = File.join(SEED_REPOSITORY_PATH, 'with-git-attributes.git', 'info') + dir = File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info') FileUtils.mkdir_p(dir) @@ -85,7 +91,7 @@ bla/bla.txt end def create_invalid_git_attributes - dir = File.join(SEED_REPOSITORY_PATH, 'with-invalid-git-attributes.git', 'info') + dir = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git', 'info') FileUtils.mkdir_p(dir) diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb new file mode 100644 index 00000000000..f627f9165fb --- /dev/null +++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe 'notify/pipeline_failed_email.html.haml' do + include Devise::Test::ControllerHelpers + + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project) } + + let(:pipeline) do + create(:ci_pipeline, + project: project, + user: user, + ref: project.default_branch, + sha: project.commit.sha, + status: :success) + end + + before do + assign(:project, project) + assign(:pipeline, pipeline) + assign(:merge_request, merge_request) + end + + context 'pipeline with user' do + it 'renders the email correctly' do + render + + expect(rendered).to have_content "Your pipeline has failed" + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50) + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content pipeline.user.name + end + end + + context 'pipeline without user' do + before do + pipeline.update_attribute(:user, nil) + end + + it 'renders the email correctly' do + render + + expect(rendered).to have_content "Your pipeline has failed" + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50) + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content "by API" + end + end +end diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb new file mode 100644 index 00000000000..ecd096ee579 --- /dev/null +++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe 'notify/pipeline_success_email.html.haml' do + include Devise::Test::ControllerHelpers + + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project) } + + let(:pipeline) do + create(:ci_pipeline, + project: project, + user: user, + ref: project.default_branch, + sha: project.commit.sha, + status: :success) + end + + before do + assign(:project, project) + assign(:pipeline, pipeline) + assign(:merge_request, merge_request) + end + + context 'pipeline with user' do + it 'renders the email correctly' do + render + + expect(rendered).to have_content "Your pipeline has passed" + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50) + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content pipeline.user.name + end + end + + context 'pipeline without user' do + before do + pipeline.update_attribute(:user, nil) + end + + it 'renders the email correctly' do + render + + expect(rendered).to have_content "Your pipeline has passed" + expect(rendered).to have_content pipeline.project.name + expect(rendered).to have_content pipeline.git_commit_message.truncate(50) + expect(rendered).to have_content pipeline.commit.author_name + expect(rendered).to have_content "##{pipeline.id}" + expect(rendered).to have_content "by API" + end + end +end diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index fbb22439f33..5a2c0671dac 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -23,10 +23,12 @@ describe RepositoryImportWorker do error = %q{remote: Not Found fatal: repository 'https://user:pass@test.com/root/repoC.git/' not found } expect_any_instance_of(Projects::ImportService).to receive(:execute). and_return({ status: :error, message: error }) + allow(subject).to receive(:jid).and_return('123') subject.perform(project.id) expect(project.reload.import_error).to include("https://*****:*****@test.com/root/repoC.git/") + expect(project.reload.import_jid).not_to be_nil end end end diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb new file mode 100644 index 00000000000..466277a5e5e --- /dev/null +++ b/spec/workers/stuck_import_jobs_worker_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe StuckImportJobsWorker do + let(:worker) { described_class.new } + let(:exclusive_lease_uuid) { SecureRandom.uuid } + + before do + allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(exclusive_lease_uuid) + end + + describe 'long running import' do + let(:project) { create(:empty_project, import_jid: '123', import_status: 'started') } + + before do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return(['123']) + end + + it 'marks the project as failed' do + expect { worker.perform }.to change { project.reload.import_status }.to('failed') + end + end + + describe 'running import' do + let(:project) { create(:empty_project, import_jid: '123', import_status: 'started') } + + before do + allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([]) + end + + it 'does not mark the project as failed' do + worker.perform + + expect(project.reload.import_status).to eq('started') + end + end +end |