diff options
Diffstat (limited to 'spec/requests/api/ci/pipelines_spec.rb')
-rw-r--r-- | spec/requests/api/ci/pipelines_spec.rb | 616 |
1 files changed, 609 insertions, 7 deletions
diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb index 111bc933ea4..577b43e6e42 100644 --- a/spec/requests/api/ci/pipelines_spec.rb +++ b/spec/requests/api/ci/pipelines_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe API::Ci::Pipelines do let_it_be(:user) { create(:user) } let_it_be(:non_member) { create(:user) } + let_it_be(:project2) { create(:project, creator: user) } # We need to reload as the shared example 'pipelines visibility table' is changing project let_it_be(:project, reload: true) do @@ -307,6 +308,606 @@ RSpec.describe API::Ci::Pipelines do end end + describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do + let(:query) { {} } + let(:api_user) { user } + let_it_be(:job) do + create(:ci_build, :success, pipeline: pipeline, + artifacts_expire_at: 1.day.since) + end + + let(:guest) { create(:project_member, :guest, project: project).user } + + before do |example| + unless example.metadata[:skip_before_request] + project.update!(public_builds: false) + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query + end + end + + context 'with ci_jobs_finder_refactor ff enabled' do + before do + stub_feature_flags(ci_jobs_finder_refactor: true) + end + + context 'authorized user' do + it 'returns pipeline jobs' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + end + + it 'returns correct values' do + expect(json_response).not_to be_empty + expect(json_response.first['commit']['id']).to eq project.commit.id + expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) + expect(json_response.first['artifacts_file']).to be_nil + expect(json_response.first['artifacts']).to be_an Array + expect(json_response.first['artifacts']).to be_empty + end + + it_behaves_like 'a job with artifacts and trace' do + let(:api_endpoint) { "/projects/#{project.id}/pipelines/#{pipeline.id}/jobs" } + end + + it 'returns pipeline data' do + json_job = json_response.first + + expect(json_job['pipeline']).not_to be_empty + expect(json_job['pipeline']['id']).to eq job.pipeline.id + expect(json_job['pipeline']['ref']).to eq job.pipeline.ref + expect(json_job['pipeline']['sha']).to eq job.pipeline.sha + expect(json_job['pipeline']['status']).to eq job.pipeline.status + end + + context 'filter jobs with one scope element' do + let(:query) { { 'scope' => 'pending' } } + + it do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + end + end + + context 'filter jobs with hash' do + let(:query) { { scope: { hello: 'pending', world: 'running' } } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + + context 'filter jobs with array of scope elements' do + let(:query) { { scope: %w(pending running) } } + + it do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + end + end + + context 'respond 400 when scope contains invalid state' do + let(:query) { { scope: %w(unknown running) } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + + context 'jobs in different pipelines' do + let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } + let!(:job2) { create(:ci_build, pipeline: pipeline2) } + + it 'excludes jobs from other pipelines' do + json_response.each { |job| expect(job['pipeline']['id']).to eq(pipeline.id) } + end + end + + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query + end.count + + create_list(:ci_build, 3, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) + + expect do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query + end.not_to exceed_all_query_limit(control_count) + end + end + + context 'no pipeline is found' do + it 'does not return jobs' do + get api("/projects/#{project2.id}/pipelines/#{pipeline.id}/jobs", user) + + expect(json_response['message']).to eq '404 Project Not Found' + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'unauthorized user' do + context 'when user is not logged in' do + let(:api_user) { nil } + + it 'does not return jobs' do + expect(json_response['message']).to eq '404 Project Not Found' + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user is guest' do + let(:guest) { create(:project_member, :guest, project: project).user } + let(:api_user) { guest } + + it 'does not return jobs' do + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + end + + context 'with ci_jobs_finder ff disabled' do + before do + stub_feature_flags(ci_jobs_finder_refactor: false) + end + + context 'authorized user' do + it 'returns pipeline jobs' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + end + + it 'returns correct values' do + expect(json_response).not_to be_empty + expect(json_response.first['commit']['id']).to eq project.commit.id + expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) + expect(json_response.first['artifacts_file']).to be_nil + expect(json_response.first['artifacts']).to be_an Array + expect(json_response.first['artifacts']).to be_empty + end + + it_behaves_like 'a job with artifacts and trace' do + let(:api_endpoint) { "/projects/#{project.id}/pipelines/#{pipeline.id}/jobs" } + end + + it 'returns pipeline data' do + json_job = json_response.first + + expect(json_job['pipeline']).not_to be_empty + expect(json_job['pipeline']['id']).to eq job.pipeline.id + expect(json_job['pipeline']['ref']).to eq job.pipeline.ref + expect(json_job['pipeline']['sha']).to eq job.pipeline.sha + expect(json_job['pipeline']['status']).to eq job.pipeline.status + end + + context 'filter jobs with one scope element' do + let(:query) { { 'scope' => 'pending' } } + + it do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + end + end + + context 'filter jobs with hash' do + let(:query) { { scope: { hello: 'pending', world: 'running' } } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + + context 'filter jobs with array of scope elements' do + let(:query) { { scope: %w(pending running) } } + + it do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + end + end + + context 'respond 400 when scope contains invalid state' do + let(:query) { { scope: %w(unknown running) } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + + context 'jobs in different pipelines' do + let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } + let!(:job2) { create(:ci_build, pipeline: pipeline2) } + + it 'excludes jobs from other pipelines' do + json_response.each { |job| expect(job['pipeline']['id']).to eq(pipeline.id) } + end + end + + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query + end.count + + create_list(:ci_build, 3, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) + + expect do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query + end.not_to exceed_all_query_limit(control_count) + end + end + + context 'no pipeline is found' do + it 'does not return jobs' do + get api("/projects/#{project2.id}/pipelines/#{pipeline.id}/jobs", user) + + expect(json_response['message']).to eq '404 Project Not Found' + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'unauthorized user' do + context 'when user is not logged in' do + let(:api_user) { nil } + + it 'does not return jobs' do + expect(json_response['message']).to eq '404 Project Not Found' + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user is guest' do + let(:guest) { create(:project_member, :guest, project: project).user } + let(:api_user) { guest } + + it 'does not return jobs' do + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + end + end + + describe 'GET /projects/:id/pipelines/:pipeline_id/bridges' do + let_it_be(:bridge) { create(:ci_bridge, pipeline: pipeline) } + let(:downstream_pipeline) { create(:ci_pipeline) } + + let!(:pipeline_source) do + create(:ci_sources_pipeline, + source_pipeline: pipeline, + source_project: project, + source_job: bridge, + pipeline: downstream_pipeline, + project: downstream_pipeline.project) + end + + let(:query) { {} } + let(:api_user) { user } + + before do |example| + unless example.metadata[:skip_before_request] + project.update!(public_builds: false) + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + end + end + + context 'with ci_jobs_finder_refactor ff enabled' do + before do + stub_feature_flags(ci_jobs_finder_refactor: true) + end + + context 'authorized user' do + it 'returns pipeline bridges' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + end + + it 'returns correct values' do + expect(json_response).not_to be_empty + expect(json_response.first['commit']['id']).to eq project.commit.id + expect(json_response.first['id']).to eq bridge.id + expect(json_response.first['name']).to eq bridge.name + expect(json_response.first['stage']).to eq bridge.stage + end + + it 'returns pipeline data' do + json_bridge = json_response.first + + expect(json_bridge['pipeline']).not_to be_empty + expect(json_bridge['pipeline']['id']).to eq bridge.pipeline.id + expect(json_bridge['pipeline']['ref']).to eq bridge.pipeline.ref + expect(json_bridge['pipeline']['sha']).to eq bridge.pipeline.sha + expect(json_bridge['pipeline']['status']).to eq bridge.pipeline.status + end + + it 'returns downstream pipeline data' do + json_bridge = json_response.first + + expect(json_bridge['downstream_pipeline']).not_to be_empty + expect(json_bridge['downstream_pipeline']['id']).to eq downstream_pipeline.id + expect(json_bridge['downstream_pipeline']['ref']).to eq downstream_pipeline.ref + expect(json_bridge['downstream_pipeline']['sha']).to eq downstream_pipeline.sha + expect(json_bridge['downstream_pipeline']['status']).to eq downstream_pipeline.status + end + + context 'filter bridges' do + before_all do + create_bridge(pipeline, :pending) + create_bridge(pipeline, :running) + end + + context 'with one scope element' do + let(:query) { { 'scope' => 'pending' } } + + it :skip_before_request do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(json_response.count).to eq 1 + expect(json_response.first["status"]).to eq "pending" + end + end + + context 'with array of scope elements' do + let(:query) { { scope: %w(pending running) } } + + it :skip_before_request do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(json_response.count).to eq 2 + json_response.each { |r| expect(%w(pending running).include?(r['status'])).to be true } + end + end + end + + context 'respond 400 when scope contains invalid state' do + context 'in an array' do + let(:query) { { scope: %w(unknown running) } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + + context 'in a hash' do + let(:query) { { scope: { unknown: true } } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + + context 'in a string' do + let(:query) { { scope: "unknown" } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + end + + context 'bridges in different pipelines' do + let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } + let!(:bridge2) { create(:ci_bridge, pipeline: pipeline2) } + + it 'excludes bridges from other pipelines' do + json_response.each { |bridge| expect(bridge['pipeline']['id']).to eq(pipeline.id) } + end + end + + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + end.count + + 3.times { create_bridge(pipeline) } + + expect do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + end.not_to exceed_all_query_limit(control_count) + end + end + + context 'no pipeline is found' do + it 'does not return bridges' do + get api("/projects/#{project2.id}/pipelines/#{pipeline.id}/bridges", user) + + expect(json_response['message']).to eq '404 Project Not Found' + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'unauthorized user' do + context 'when user is not logged in' do + let(:api_user) { nil } + + it 'does not return bridges' do + expect(json_response['message']).to eq '404 Project Not Found' + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user is guest' do + let(:api_user) { guest } + let(:guest) { create(:project_member, :guest, project: project).user } + + it 'does not return bridges' do + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user has no read_build access for project' do + before do + project.add_guest(api_user) + end + + it 'does not return bridges' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user) + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + end + + context 'with ci_jobs_finder_refactor ff disabled' do + before do + stub_feature_flags(ci_jobs_finder_refactor: false) + end + + context 'authorized user' do + it 'returns pipeline bridges' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + end + + it 'returns correct values' do + expect(json_response).not_to be_empty + expect(json_response.first['commit']['id']).to eq project.commit.id + expect(json_response.first['id']).to eq bridge.id + expect(json_response.first['name']).to eq bridge.name + expect(json_response.first['stage']).to eq bridge.stage + end + + it 'returns pipeline data' do + json_bridge = json_response.first + + expect(json_bridge['pipeline']).not_to be_empty + expect(json_bridge['pipeline']['id']).to eq bridge.pipeline.id + expect(json_bridge['pipeline']['ref']).to eq bridge.pipeline.ref + expect(json_bridge['pipeline']['sha']).to eq bridge.pipeline.sha + expect(json_bridge['pipeline']['status']).to eq bridge.pipeline.status + end + + it 'returns downstream pipeline data' do + json_bridge = json_response.first + + expect(json_bridge['downstream_pipeline']).not_to be_empty + expect(json_bridge['downstream_pipeline']['id']).to eq downstream_pipeline.id + expect(json_bridge['downstream_pipeline']['ref']).to eq downstream_pipeline.ref + expect(json_bridge['downstream_pipeline']['sha']).to eq downstream_pipeline.sha + expect(json_bridge['downstream_pipeline']['status']).to eq downstream_pipeline.status + end + + context 'filter bridges' do + before_all do + create_bridge(pipeline, :pending) + create_bridge(pipeline, :running) + end + + context 'with one scope element' do + let(:query) { { 'scope' => 'pending' } } + + it :skip_before_request do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(json_response.count).to eq 1 + expect(json_response.first["status"]).to eq "pending" + end + end + + context 'with array of scope elements' do + let(:query) { { scope: %w(pending running) } } + + it :skip_before_request do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_an Array + expect(json_response.count).to eq 2 + json_response.each { |r| expect(%w(pending running).include?(r['status'])).to be true } + end + end + end + + context 'respond 400 when scope contains invalid state' do + context 'in an array' do + let(:query) { { scope: %w(unknown running) } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + + context 'in a hash' do + let(:query) { { scope: { unknown: true } } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + + context 'in a string' do + let(:query) { { scope: "unknown" } } + + it { expect(response).to have_gitlab_http_status(:bad_request) } + end + end + + context 'bridges in different pipelines' do + let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } + let!(:bridge2) { create(:ci_bridge, pipeline: pipeline2) } + + it 'excludes bridges from other pipelines' do + json_response.each { |bridge| expect(bridge['pipeline']['id']).to eq(pipeline.id) } + end + end + + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + end.count + + 3.times { create_bridge(pipeline) } + + expect do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query + end.not_to exceed_all_query_limit(control_count) + end + end + + context 'no pipeline is found' do + it 'does not return bridges' do + get api("/projects/#{project2.id}/pipelines/#{pipeline.id}/bridges", user) + + expect(json_response['message']).to eq '404 Project Not Found' + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'unauthorized user' do + context 'when user is not logged in' do + let(:api_user) { nil } + + it 'does not return bridges' do + expect(json_response['message']).to eq '404 Project Not Found' + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user is guest' do + let(:api_user) { guest } + let(:guest) { create(:project_member, :guest, project: project).user } + + it 'does not return bridges' do + expect(response).to have_gitlab_http_status(:forbidden) + end + end + + context 'when user has no read_build access for project' do + before do + project.add_guest(api_user) + end + + it 'does not return bridges' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user) + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + end + + def create_bridge(pipeline, status = :created) + create(:ci_bridge, status: status, pipeline: pipeline).tap do |bridge| + downstream_pipeline = create(:ci_pipeline) + create(:ci_sources_pipeline, + source_pipeline: pipeline, + source_project: pipeline.project, + source_job: bridge, + pipeline: downstream_pipeline, + project: downstream_pipeline.project) + end + end + end + describe 'POST /projects/:id/pipeline ' do def expect_variables(variables, expected_variables) variables.each_with_index do |variable, index| @@ -476,17 +1077,18 @@ RSpec.describe API::Ci::Pipelines do end end - context 'when config source is not ci' do - let(:non_ci_config_source) { ::Ci::PipelineEnums.non_ci_config_source_values.first } - let(:pipeline_not_ci) do - create(:ci_pipeline, config_source: non_ci_config_source, project: project) + context 'when pipeline is a dangling pipeline' do + let(:dangling_source) { Enums::Ci::Pipeline.dangling_sources.each_value.first } + + let(:dangling_pipeline) do + create(:ci_pipeline, source: dangling_source, project: project) end it 'returns the specified pipeline' do - get api("/projects/#{project.id}/pipelines/#{pipeline_not_ci.id}", user) + get api("/projects/#{project.id}/pipelines/#{dangling_pipeline.id}", user) expect(response).to have_gitlab_http_status(:ok) - expect(json_response['sha']).to eq(pipeline_not_ci.sha) + expect(json_response['sha']).to eq(dangling_pipeline.sha) end end end @@ -624,7 +1226,7 @@ RSpec.describe API::Ci::Pipelines do end it 'does not log an audit event' do - expect { delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner) }.not_to change { SecurityEvent.count } + expect { delete api("/projects/#{project.id}/pipelines/#{pipeline.id}", owner) }.not_to change { AuditEvent.count } end context 'when the pipeline has jobs' do |