diff options
author | Mayra Cabrera <mcabrera@gitlab.com> | 2019-05-02 18:27:35 +0000 |
---|---|---|
committer | Douglas Barbosa Alexandre <dbalexandre@gmail.com> | 2019-05-02 18:27:35 +0000 |
commit | 5432f5480f334a0bd15ed06568e0c82f0dd54e45 (patch) | |
tree | 350ea2de8e0bdaf171f09c5b5886da026f3fc2e1 /spec | |
parent | 0c3d7830c5fdaef029457557f7b4ad867805b06a (diff) | |
download | gitlab-ce-5432f5480f334a0bd15ed06568e0c82f0dd54e45.tar.gz |
Adds a way to start multiple manual jobs in stage
- Adds an endpoint on PipelinesController
- Adds a service that iterates over every build in a stage and
plays it.
- Includes 'play_manual' details on EntitySerializer
- Builds a new Stage state: PlayManual. An stage can take this status if
it has manual builds or an skipped, scheduled or manual status
- Includes FE modifications and specs
Diffstat (limited to 'spec')
-rw-r--r-- | spec/controllers/projects/stages_controller_spec.rb | 72 | ||||
-rw-r--r-- | spec/features/projects/pipelines/pipeline_spec.rb | 14 | ||||
-rw-r--r-- | spec/javascripts/pipelines/graph/action_component_spec.js | 11 | ||||
-rw-r--r-- | spec/javascripts/pipelines/graph/stage_column_component_spec.js | 49 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/status/stage/factory_spec.rb | 18 | ||||
-rw-r--r-- | spec/lib/gitlab/ci/status/stage/play_manual_spec.rb | 74 | ||||
-rw-r--r-- | spec/models/ci/legacy_stage_spec.rb | 2 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 32 | ||||
-rw-r--r-- | spec/models/ci/stage_spec.rb | 2 | ||||
-rw-r--r-- | spec/serializers/pipeline_serializer_spec.rb | 2 | ||||
-rw-r--r-- | spec/serializers/stage_entity_spec.rb | 28 | ||||
-rw-r--r-- | spec/serializers/stage_serializer_spec.rb | 31 | ||||
-rw-r--r-- | spec/services/ci/play_manual_stage_service_spec.rb | 79 | ||||
-rw-r--r-- | spec/support/shared_examples/ci/stage_shared_examples.rb | 27 |
14 files changed, 439 insertions, 2 deletions
diff --git a/spec/controllers/projects/stages_controller_spec.rb b/spec/controllers/projects/stages_controller_spec.rb new file mode 100644 index 00000000000..a91e3523fd7 --- /dev/null +++ b/spec/controllers/projects/stages_controller_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::StagesController do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + + before do + sign_in(user) + end + + describe 'POST #play_manual.json' do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:stage_name) { 'test' } + + before do + create_manual_build(pipeline, 'test', 'rspec 1/2') + create_manual_build(pipeline, 'test', 'rspec 2/2') + + pipeline.reload + end + + context 'when user does not have access' do + it 'returns not authorized' do + play_manual_stage! + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when user has access' do + before do + project.add_maintainer(user) + end + + context 'when the stage does not exists' do + let(:stage_name) { 'deploy' } + + it 'fails to play all manual' do + play_manual_stage! + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when the stage exists' do + it 'starts all manual jobs' do + expect(pipeline.builds.manual.count).to eq(2) + + play_manual_stage! + + expect(response).to have_gitlab_http_status(:ok) + expect(pipeline.builds.manual.count).to eq(0) + end + end + end + + def play_manual_stage! + post :play_manual, params: { + namespace_id: project.namespace, + project_id: project, + id: pipeline.id, + stage_name: stage_name + }, format: :json + end + + def create_manual_build(pipeline, stage, name) + create(:ci_build, :manual, pipeline: pipeline, stage: stage, name: name) + end + end +end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 3825468cf71..a1115b514d3 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -236,6 +236,20 @@ describe 'Pipeline', :js do end end + context 'when the pipeline has manual stage' do + before do + create(:ci_build, :manual, pipeline: pipeline, stage: 'publish', name: 'CentOS') + create(:ci_build, :manual, pipeline: pipeline, stage: 'publish', name: 'Debian') + create(:ci_build, :manual, pipeline: pipeline, stage: 'publish', name: 'OpenSUDE') + + visit_pipeline + end + + it 'displays play all button' do + expect(page).to have_selector('.js-stage-action') + end + end + context 'page tabs' do before do visit_pipeline diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js index 95717d659b8..321497b35b5 100644 --- a/spec/javascripts/pipelines/graph/action_component_spec.js +++ b/spec/javascripts/pipelines/graph/action_component_spec.js @@ -66,5 +66,16 @@ describe('pipeline graph action component', () => { done(); }, 0); }); + + it('renders a loading icon while waiting for request', done => { + component.$el.click(); + + component.$nextTick(() => { + expect(component.$el.querySelector('.js-action-icon-loading')).not.toBeNull(); + setTimeout(() => { + done(); + }); + }); + }); }); }); diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js index 3240e8e4c1b..5183f8dd2d6 100644 --- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js +++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js @@ -70,4 +70,53 @@ describe('stage column component', () => { ); }); }); + + describe('with action', () => { + it('renders action button', () => { + component = mountComponent(StageColumnComponent, { + groups: [ + { + id: 4259, + name: '<img src=x onerror=alert(document.domain)>', + status: { + icon: 'status_success', + label: 'success', + tooltip: '<img src=x onerror=alert(document.domain)>', + }, + }, + ], + title: 'test', + hasTriggeredBy: false, + action: { + icon: 'play', + title: 'Play all', + path: 'action', + }, + }); + + expect(component.$el.querySelector('.js-stage-action')).not.toBeNull(); + }); + }); + + describe('without action', () => { + it('does not render action button', () => { + component = mountComponent(StageColumnComponent, { + groups: [ + { + id: 4259, + name: '<img src=x onerror=alert(document.domain)>', + status: { + icon: 'status_success', + label: 'success', + tooltip: '<img src=x onerror=alert(document.domain)>', + }, + }, + ], + title: 'test', + hasTriggeredBy: false, + }); + + expect(component.$el.querySelector('.js-stage-action')).toBeNull(); + }); + }); }); diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index dee4f4efd1b..4b299170c87 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::Stage::Factory do end context 'when stage has a core status' do - HasStatus::AVAILABLE_STATUSES.each do |core_status| + (HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status| context "when core status is #{core_status}" do before do create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status) @@ -64,4 +64,20 @@ describe Gitlab::Ci::Status::Stage::Factory do expect(status.details_path).to include "pipelines/#{pipeline.id}##{stage.name}" end end + + context 'when stage has manual builds' do + (HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status| + context "when status is #{core_status}" do + before do + create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status) + create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status) + create(:ci_build, pipeline: pipeline, stage: 'build', status: :manual) + end + + it 'fabricates a play manual status' do + expect(status).to be_a(Gitlab::Ci::Status::Stage::PlayManual) + end + end + end + end end diff --git a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb new file mode 100644 index 00000000000..b0113b00ef0 --- /dev/null +++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Status::Stage::PlayManual do + let(:stage) { double('stage') } + let(:play_manual) { described_class.new(stage) } + + describe '#action_icon' do + subject { play_manual.action_icon } + + it { is_expected.to eq('play') } + end + + describe '#action_button_title' do + subject { play_manual.action_button_title } + + it { is_expected.to eq('Play all manual') } + end + + describe '#action_title' do + subject { play_manual.action_title } + + it { is_expected.to eq('Play all manual') } + end + + describe '#action_path' do + let(:stage) { create(:ci_stage_entity, status: 'manual') } + let(:pipeline) { stage.pipeline } + let(:play_manual) { stage.detailed_status(create(:user)) } + + subject { play_manual.action_path } + + it { is_expected.to eq("/#{pipeline.project.full_path}/pipelines/#{pipeline.id}/stages/#{stage.name}/play_manual") } + end + + describe '#action_method' do + subject { play_manual.action_method } + + it { is_expected.to eq(:post) } + end + + describe '.matches?' do + let(:user) { double('user') } + + subject { described_class.matches?(stage, user) } + + context 'when stage is skipped' do + let(:stage) { create(:ci_stage_entity, status: :skipped) } + + it { is_expected.to be_truthy } + end + + context 'when stage is manual' do + let(:stage) { create(:ci_stage_entity, status: :manual) } + + it { is_expected.to be_truthy } + end + + context 'when stage is scheduled' do + let(:stage) { create(:ci_stage_entity, status: :scheduled) } + + it { is_expected.to be_truthy } + end + + context 'when stage is success' do + let(:stage) { create(:ci_stage_entity, status: :success) } + + context 'and does not have manual builds' do + it { is_expected.to be_falsy } + end + end + end +end diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb index be0307518eb..bb812cc0533 100644 --- a/spec/models/ci/legacy_stage_spec.rb +++ b/spec/models/ci/legacy_stage_spec.rb @@ -272,4 +272,6 @@ describe Ci::LegacyStage do def create_job(type, status: 'success', stage: stage_name, **opts) create(type, pipeline: pipeline, stage: stage, status: status, **opts) end + + it_behaves_like 'manual playable stage', :ci_stage end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 9d0cd654f13..af455a72f50 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2942,4 +2942,36 @@ describe Ci::Pipeline, :mailer do end end end + + describe '#find_stage_by_name' do + let(:pipeline) { create(:ci_pipeline) } + let(:stage_name) { 'test' } + + let(:stage) do + create(:ci_stage_entity, + pipeline: pipeline, + project: pipeline.project, + name: 'test') + end + + before do + create_list(:ci_build, 2, pipeline: pipeline, stage: stage.name) + end + + subject { pipeline.find_stage_by_name!(stage_name) } + + context 'when stage exists' do + it { is_expected.to eq(stage) } + end + + context 'when stage does not exist' do + let(:stage_name) { 'build' } + + it 'raises an ActiveRecord exception' do + expect do + subject + end.to raise_exception(ActiveRecord::RecordNotFound) + end + end + end end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 661958390e2..85cd32fb03a 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -285,4 +285,6 @@ describe Ci::Stage, :models do end end end + + it_behaves_like 'manual playable stage', :ci_stage_entity end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index d9023036534..54e6abc2d3a 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -156,8 +156,8 @@ describe PipelineSerializer do it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { subject } - expected_queries = Gitlab.ee? ? 38 : 31 + expect(recorded.count).to be_within(2).of(expected_queries) expect(recorded.cached_count).to eq(0) end diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb index 2034c7891ef..6b1185d1283 100644 --- a/spec/serializers/stage_entity_spec.rb +++ b/spec/serializers/stage_entity_spec.rb @@ -48,6 +48,10 @@ describe StageEntity do expect(subject[:title]).to eq 'test: passed' end + it 'does not contain play_details info' do + expect(subject[:status][:action]).not_to be_present + end + context 'when the jobs should be grouped' do let(:entity) { described_class.new(stage, request: request, grouped: true) } @@ -66,5 +70,29 @@ describe StageEntity do end end end + + context 'with a skipped stage ' do + let(:stage) { create(:ci_stage_entity, status: 'skipped') } + + it 'contains play_all_manual' do + expect(subject[:status][:action]).to be_present + end + end + + context 'with a scheduled stage ' do + let(:stage) { create(:ci_stage_entity, status: 'scheduled') } + + it 'contains play_all_manual' do + expect(subject[:status][:action]).to be_present + end + end + + context 'with a manual stage ' do + let(:stage) { create(:ci_stage_entity, status: 'manual') } + + it 'contains play_all_manual' do + expect(subject[:status][:action]).to be_present + end + end end end diff --git a/spec/serializers/stage_serializer_spec.rb b/spec/serializers/stage_serializer_spec.rb new file mode 100644 index 00000000000..aae17cfbcb9 --- /dev/null +++ b/spec/serializers/stage_serializer_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe StageSerializer do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:resource) { create(:ci_stage_entity) } + + let(:serializer) do + described_class.new(current_user: user, project: project) + end + + subject { serializer.represent(resource) } + + describe '#represent' do + context 'with a single entity' do + it 'serializes the stage object' do + expect(subject[:name]).to eq(resource.name) + end + end + + context 'with an array of entities' do + let(:resource) { create_list(:ci_stage_entity, 2) } + + it 'serializes the array of pipelines' do + expect(subject).not_to be_empty + end + end + end +end diff --git a/spec/services/ci/play_manual_stage_service_spec.rb b/spec/services/ci/play_manual_stage_service_spec.rb new file mode 100644 index 00000000000..5d812745c7f --- /dev/null +++ b/spec/services/ci/play_manual_stage_service_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::PlayManualStageService, '#execute' do + let(:current_user) { create(:user) } + let(:pipeline) { create(:ci_pipeline, user: current_user) } + let(:project) { pipeline.project } + let(:service) { described_class.new(project, current_user, pipeline: pipeline) } + let(:stage_status) { 'manual' } + + let(:stage) do + create(:ci_stage_entity, + pipeline: pipeline, + project: project, + name: 'test') + end + + before do + project.add_maintainer(current_user) + create_builds_for_stage(status: stage_status) + end + + context 'when pipeline has manual builds' do + before do + service.execute(stage) + end + + it 'starts manual builds from pipeline' do + expect(pipeline.builds.manual.count).to eq(0) + end + + it 'updates manual builds' do + pipeline.builds.each do |build| + expect(build.user).to eq(current_user) + end + end + end + + context 'when pipeline has no manual builds' do + let(:stage_status) { 'failed' } + + before do + service.execute(stage) + end + + it 'does not update the builds' do + expect(pipeline.builds.failed.count).to eq(3) + end + end + + context 'when user does not have permission on a specific build' do + before do + allow_any_instance_of(Ci::Build).to receive(:play) + .and_raise(Gitlab::Access::AccessDeniedError) + + service.execute(stage) + end + + it 'logs the error' do + expect(Gitlab::AppLogger).to receive(:error) + .exactly(stage.builds.manual.count) + + service.execute(stage) + end + end + + def create_builds_for_stage(options) + options.merge!({ + when: 'manual', + pipeline: pipeline, + stage: stage.name, + stage_id: stage.id, + user: pipeline.user + }) + + create_list(:ci_build, 3, options) + end +end diff --git a/spec/support/shared_examples/ci/stage_shared_examples.rb b/spec/support/shared_examples/ci/stage_shared_examples.rb new file mode 100644 index 00000000000..925974ed11e --- /dev/null +++ b/spec/support/shared_examples/ci/stage_shared_examples.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +shared_examples 'manual playable stage' do |stage_type| + let(:stage) { build(stage_type, status: status) } + + describe '#manual_playable?' do + subject { stage.manual_playable? } + + context 'when is manual' do + let(:status) { 'manual' } + + it { is_expected.to be_truthy } + end + + context 'when is scheduled' do + let(:status) { 'scheduled' } + + it { is_expected.to be_truthy } + end + + context 'when is skipped' do + let(:status) { 'skipped' } + + it { is_expected.to be_truthy } + end + end +end |