diff options
author | James Edwards-Jones <jedwardsjones@gitlab.com> | 2017-04-06 21:35:01 +0100 |
---|---|---|
committer | James Edwards-Jones <jedwardsjones@gitlab.com> | 2017-04-06 21:35:01 +0100 |
commit | 55811ac9900af81fb980c3649ee1c9aadedb3a44 (patch) | |
tree | 437628918788ef02c879f59e64d109c1283fde27 /spec | |
parent | 902054db59e02cb14c28ecffd9dff95994dbb01f (diff) | |
parent | c3af43c3d263278bd39917c37a87022f8dc44e95 (diff) | |
download | gitlab-ce-55811ac9900af81fb980c3649ee1c9aadedb3a44.tar.gz |
Merge branch 'last-green-master' into 18471-restrict-tag-pushes-protected-tags
Diffstat (limited to 'spec')
23 files changed, 643 insertions, 72 deletions
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/groups_spec.rb b/spec/features/groups_spec.rb index c90cc06a8f5..8bfe6f4d54b 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -86,17 +86,40 @@ feature 'Group', feature: true do describe 'create a nested group' do let(:group) { create(:group, path: 'foo') } - before do - visit subgroups_group_path(group) - click_link 'New Subgroup' + context 'as admin' do + before do + visit subgroups_group_path(group) + click_link 'New Subgroup' + end + + it 'creates a nested group' do + fill_in 'Group path', with: 'bar' + click_button 'Create group' + + expect(current_path).to eq(group_path('foo/bar')) + expect(page).to have_content("Group 'bar' was successfully created.") + end end - it 'creates a nested group' do - fill_in 'Group path', with: 'bar' - click_button 'Create group' + context 'as group owner' do + let(:user) { create(:user) } - expect(current_path).to eq(group_path('foo/bar')) - expect(page).to have_content("Group 'bar' was successfully created.") + before do + group.add_owner(user) + logout + login_as(user) + + visit subgroups_group_path(group) + click_link 'New Subgroup' + end + + it 'creates a nested group' do + fill_in 'Group path', with: 'bar' + click_button 'Create group' + + expect(current_path).to eq(group_path('foo/bar')) + expect(page).to have_content("Group 'bar' was successfully created.") + end end end diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index 52196ce49bd..c66b9a34b86 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -71,6 +71,22 @@ feature "New project", feature: true do end end end + + context "with subgroup namespace" do + let(:group) { create(:group, :private, owner: user) } + let(:subgroup) { create(:group, parent: group) } + + before do + group.add_master(user) + visit new_project_path(namespace_id: subgroup.id) + end + + it "selects the group namespace" do + namespace = find("#project_namespace_id option[selected]") + + expect(namespace.text).to eq subgroup.full_path + end + end end context 'Import project options' do 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/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/fixtures/environments/metrics.html.haml b/spec/javascripts/fixtures/environments/metrics.html.haml index 483063fb889..e2dd9519898 100644 --- a/spec/javascripts/fixtures/environments/metrics.html.haml +++ b/spec/javascripts/fixtures/environments/metrics.html.haml @@ -1,12 +1,62 @@ -%div +.prometheus-container{ 'data-has-metrics': "false", 'data-doc-link': '/help/administration/monitoring/prometheus/index.md', 'data-prometheus-integration': '/root/hello-prometheus/services/prometheus/edit' } .top-area .row .col-sm-6 %h3.page-title Metrics for environment - .row - .col-sm-12 - %svg.prometheus-graph{ 'graph-type' => 'cpu_values' } - .row - .col-sm-12 - %svg.prometheus-graph{ 'graph-type' => 'memory_values' }
\ No newline at end of file + .prometheus-state + .js-getting-started.hidden + .row + .col-md-4.col-md-offset-4.state-svg + %svg + .row + .col-md-6.col-md-offset-3 + %h4.text-center.state-title + Get started with performance monitoring + .row + .col-md-6.col-md-offset-3 + .description-text.text-center.state-description + Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments. Learn more about performance monitoring + .row.state-button-section + .col-md-4.col-md-offset-4.text-center.state-button + %a.btn.btn-success + Configure Prometheus + .js-loading.hidden + .row + .col-md-4.col-md-offset-4.state-svg + %svg + .row + .col-md-6.col-md-offset-3 + %h4.text-center.state-title + Waiting for performance data + .row + .col-md-6.col-md-offset-3 + .description-text.text-center.state-description + Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available. + .row.state-button-section + .col-md-4.col-md-offset-4.text-center.state-button + %a.btn.btn-success + View documentation + .js-unable-to-connect.hidden + .row + .col-md-4.col-md-offset-4.state-svg + %svg + .row + .col-md-6.col-md-offset-3 + %h4.text-center.state-title + Unable to connect to Prometheus server + .row + .col-md-6.col-md-offset-3 + .description-text.text-center.state-description + Ensure connectivity is available from the GitLab server to the Prometheus server + .row.state-button-section + .col-md-4.col-md-offset-4.text-center.state-button + %a.btn.btn-success + View documentation + .prometheus-graphs + .row + .col-sm-12 + %svg.prometheus-graph{ 'graph-type' => 'cpu_values' } + .row + .col-sm-12 + %svg.prometheus-graph{ 'graph-type' => 'memory_values' } 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/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js index c2bcd9c0f7c..4b904fc2960 100644 --- a/spec/javascripts/monitoring/prometheus_graph_spec.js +++ b/spec/javascripts/monitoring/prometheus_graph_spec.js @@ -1,5 +1,4 @@ import 'jquery'; -import '~/lib/utils/common_utils'; import PrometheusGraph from '~/monitoring/prometheus_graph'; import { prometheusMockData } from './prometheus_mock_data'; @@ -12,6 +11,7 @@ describe('PrometheusGraph', () => { beforeEach(() => { loadFixtures(fixtureName); + $('.prometheus-container').data('has-metrics', 'true'); this.prometheusGraph = new PrometheusGraph(); const self = this; const fakeInit = (metricsResponse) => { @@ -75,3 +75,24 @@ describe('PrometheusGraph', () => { }); }); }); + +describe('PrometheusGraphs UX states', () => { + const fixtureName = 'static/environments/metrics.html.raw'; + preloadFixtures(fixtureName); + + beforeEach(() => { + loadFixtures(fixtureName); + this.prometheusGraph = new PrometheusGraph(); + }); + + it('shows a specified state', () => { + this.prometheusGraph.state = '.js-getting-started'; + this.prometheusGraph.updateState(); + const $state = $('.js-getting-started'); + expect($state).toBeDefined(); + expect($('.state-title', $state)).toBeDefined(); + expect($('.state-svg', $state)).toBeDefined(); + expect($('.state-description', $state)).toBeDefined(); + expect($('.state-button', $state)).toBeDefined(); + }); +}); 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/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb index 6ec4360adc2..c872d8232b0 100644 --- a/spec/lib/gitlab/etag_caching/middleware_spec.rb +++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb @@ -47,9 +47,9 @@ describe Gitlab::EtagCaching::Middleware do it 'tracks "etag_caching_key_not_found" event' do expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_middleware_used) + .with(:etag_caching_middleware_used, endpoint: 'issue_notes') expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_key_not_found) + .with(:etag_caching_key_not_found, endpoint: 'issue_notes') middleware.call(build_env(path, if_none_match)) end @@ -93,9 +93,9 @@ describe Gitlab::EtagCaching::Middleware do it 'tracks "etag_caching_cache_hit" event' do expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_middleware_used) + .with(:etag_caching_middleware_used, endpoint: 'issue_notes') expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_cache_hit) + .with(:etag_caching_cache_hit, endpoint: 'issue_notes') middleware.call(build_env(path, if_none_match)) end @@ -132,9 +132,9 @@ describe Gitlab::EtagCaching::Middleware do mock_app_response expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_middleware_used) + .with(:etag_caching_middleware_used, endpoint: 'issue_notes') expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_resource_changed) + .with(:etag_caching_resource_changed, endpoint: 'issue_notes') middleware.call(build_env(path, if_none_match)) end @@ -150,9 +150,9 @@ describe Gitlab::EtagCaching::Middleware do it 'tracks "etag_caching_header_missing" event' do expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_middleware_used) + .with(:etag_caching_middleware_used, endpoint: 'issue_notes') expect(Gitlab::Metrics).to receive(:add_event) - .with(:etag_caching_header_missing) + .with(:etag_caching_header_missing, endpoint: 'issue_notes') middleware.call(build_env(path, if_none_match)) 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/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/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/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/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/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 |