Commit ee1f9b7f authored by lauraMon's avatar lauraMon

Adds feature flag and more

* Adds ci_jobs_finder_refactor ff
* Updates specs to work with and without the ff
* Puts back old code for when FF is disabled
parent f19280fd
...@@ -25,7 +25,11 @@ module Ci ...@@ -25,7 +25,11 @@ module Ci
attr_reader :current_user, :pipeline, :project, :params, :type attr_reader :current_user, :pipeline, :project, :params, :type
def init_collection def init_collection
pipeline_jobs || project_jobs || all_jobs if Feature.enabled?(:ci_jobs_finder_refactor)
pipeline_jobs || project_jobs || all_jobs
else
project ? project_builds : all_jobs
end
end end
def all_jobs def all_jobs
...@@ -34,6 +38,12 @@ module Ci ...@@ -34,6 +38,12 @@ module Ci
type.all type.all
end end
def project_builds
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :read_build, project)
project.builds.relevant
end
def project_jobs def project_jobs
return unless project return unless project
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :read_build, project) raise Gitlab::Access::AccessDeniedError unless can?(current_user, :read_build, project)
...@@ -49,7 +59,9 @@ module Ci ...@@ -49,7 +59,9 @@ module Ci
end end
def filter_by_scope(builds) def filter_by_scope(builds)
return filter_by_statuses!(params[:scope], builds) if params[:scope].is_a?(Array) if Feature.enabled?(:ci_jobs_finder_refactor)
return filter_by_statuses!(params[:scope], builds) if params[:scope].is_a?(Array)
end
case params[:scope] case params[:scope]
when 'pending' when 'pending'
......
---
name: ci_jobs_finder_refactor
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36622
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/245183
group: group::continuous integration
type: development
default_enabled: false
...@@ -127,9 +127,15 @@ module API ...@@ -127,9 +127,15 @@ module API
authorize!(:read_pipeline, user_project) authorize!(:read_pipeline, user_project)
pipeline = user_project.all_pipelines.find(params[:pipeline_id]) pipeline = user_project.all_pipelines.find(params[:pipeline_id])
builds = ::Ci::JobsFinder
.new(current_user: current_user, pipeline: pipeline, params: params) if Feature.enabled?(:ci_jobs_finder_refactor)
.execute builds = ::Ci::JobsFinder
.new(current_user: current_user, pipeline: pipeline, params: params)
.execute
else
builds = pipeline.builds
builds = filter_builds(builds, params[:scope])
end
builds = builds.with_preloads builds = builds.with_preloads
...@@ -150,9 +156,14 @@ module API ...@@ -150,9 +156,14 @@ module API
pipeline = user_project.all_pipelines.find(params[:pipeline_id]) pipeline = user_project.all_pipelines.find(params[:pipeline_id])
bridges = ::Ci::JobsFinder if Feature.enabled?(:ci_jobs_finder_refactor)
.new(current_user: current_user, pipeline: pipeline, params: params, type: ::Ci::Bridge) bridges = ::Ci::JobsFinder
.execute .new(current_user: current_user, pipeline: pipeline, params: params, type: ::Ci::Bridge)
.execute
else
bridges = pipeline.bridges
bridges = filter_builds(bridges, params[:scope])
end
bridges = bridges.with_preloads bridges = bridges.with_preloads
...@@ -233,6 +244,21 @@ module API ...@@ -233,6 +244,21 @@ module API
end end
helpers do helpers do
if Feature.disabled?(:ci_jobs_finder_refactor)
# rubocop: disable CodeReuse/ActiveRecord
def filter_builds(builds, scope)
return builds if scope.nil? || scope.empty?
available_statuses = ::CommitStatus::AVAILABLE_STATUSES
unknown = scope - available_statuses
render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty?
builds.where(status: scope)
end
# rubocop: enable CodeReuse/ActiveRecord
end
def pipeline def pipeline
strong_memoize(:pipeline) do strong_memoize(:pipeline) do
user_project.all_pipelines.find(params[:pipeline_id]) user_project.all_pipelines.find(params[:pipeline_id])
......
...@@ -78,6 +78,10 @@ module Gitlab ...@@ -78,6 +78,10 @@ module Gitlab
def self.child_of_child_pipeline_enabled?(project) def self.child_of_child_pipeline_enabled?(project)
::Feature.enabled?(:ci_child_of_child_pipeline, project, default_enabled: false) ::Feature.enabled?(:ci_child_of_child_pipeline, project, default_enabled: false)
end end
def self.ci_jobs_finder_refactor?
::Feature.enabled?(:ci_jobs_finder_refactor, default_enabled: false)
end
end end
end end
end end
......
...@@ -36,62 +36,135 @@ RSpec.describe Ci::JobsFinder, '#execute' do ...@@ -36,62 +36,135 @@ RSpec.describe Ci::JobsFinder, '#execute' do
end end
end end
context 'scope is present' do context 'with ci_jobs_finder_refactor ff enabled' do
let(:jobs) { [job_1, job_2, job_3] } before do
stub_feature_flags(ci_jobs_finder_refactor: true)
where(:scope, :index) do end
[
['pending', 0], context 'scope is present' do
['running', 1], let(:jobs) { [job_1, job_2, job_3] }
['finished', 2]
] where(:scope, :index) do
[
['pending', 0],
['running', 1],
['finished', 2]
]
end
with_them do
let(:params) { { scope: scope } }
it { expect(subject).to match_array([jobs[index]]) }
end
end end
with_them do context 'scope is an array' do
let(:params) { { scope: scope } } let(:jobs) { [job_1, job_2, job_3] }
let(:params) {{ scope: ['running'] }}
it { expect(subject).to match_array([jobs[index]]) } it 'filters by the job statuses in the scope' do
expect(subject).to match_array([job_2])
end
end end
end end
context 'scope is an array' do context 'with ci_jobs_finder_refactor ff disabled' do
let(:jobs) { [job_1, job_2, job_3] } before do
let(:params) {{ scope: ['running'] }} stub_feature_flags(ci_jobs_finder_refactor: false)
end
context 'scope is present' do
let(:jobs) { [job_1, job_2, job_3] }
where(:scope, :index) do
[
['pending', 0],
['running', 1],
['finished', 2]
]
end
it 'filters by the job statuses in the scope' do with_them do
expect(subject).to match_array([job_2]) let(:params) { { scope: scope } }
it { expect(subject).to match_array([jobs[index]]) }
end
end end
end end
end end
context 'a project is present' do context 'with ci_jobs_finder_refactor ff enabled' do
subject { described_class.new(current_user: user, project: project, params: params).execute } before do
stub_feature_flags(ci_jobs_finder_refactor: true)
end
context 'a project is present' do
subject { described_class.new(current_user: user, project: project, params: params).execute }
context 'user has access to the project' do context 'user has access to the project' do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
end
it 'returns jobs for the specified project' do
expect(subject).to match_array([job_3])
end
end end
it 'returns jobs for the specified project' do context 'user has no access to project builds' do
expect(subject).to match_array([job_3]) before do
project.add_guest(user)
end
it 'returns no jobs' do
expect(subject).to be_empty
end
end end
context 'without user' do
let(:user) { nil }
it 'returns no jobs' do
expect(subject).to be_empty
end
end
end
end
context 'with ci_jobs_finder_refactor ff disabled' do
before do
stub_feature_flags(ci_jobs_finder_refactor: false)
end end
context 'a project is present' do
subject { described_class.new(current_user: user, project: project, params: params).execute }
context 'user has no access to project builds' do context 'user has access to the project' do
before do before do
project.add_guest(user) project.add_maintainer(user)
end
it 'returns jobs for the specified project' do
expect(subject).to match_array([job_3])
end
end end
it 'returns no jobs' do context 'user has no access to project builds' do
expect(subject).to be_empty before do
project.add_guest(user)
end
it 'returns no jobs' do
expect(subject).to be_empty
end
end end
end
context 'without user' do context 'without user' do
let(:user) { nil } let(:user) { nil }
it 'returns no jobs' do it 'returns no jobs' do
expect(subject).to be_empty expect(subject).to be_empty
end
end end
end end
end end
......
...@@ -5,6 +5,7 @@ require 'spec_helper' ...@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe API::Ci::Pipelines do RSpec.describe API::Ci::Pipelines do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:non_member) { 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 # We need to reload as the shared example 'pipelines visibility table' is changing project
let_it_be(:project, reload: true) do let_it_be(:project, reload: true) do
...@@ -324,104 +325,236 @@ RSpec.describe API::Ci::Pipelines do ...@@ -324,104 +325,236 @@ RSpec.describe API::Ci::Pipelines do
end end
end end
context 'authorized user' do context 'with ci_jobs_finder_refactor ff enabled' do
it 'returns pipeline jobs' do before do
expect(response).to have_gitlab_http_status(:ok) stub_feature_flags(ci_jobs_finder_refactor: true)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
end end
it 'returns correct values' do context 'authorized user' do
expect(json_response).not_to be_empty it 'returns pipeline jobs' do
expect(json_response.first['commit']['id']).to eq project.commit.id expect(response).to have_gitlab_http_status(:ok)
expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) expect(response).to include_pagination_headers
expect(json_response.first['artifacts_file']).to be_nil expect(json_response).to be_an Array
expect(json_response.first['artifacts']).to be_an Array end
expect(json_response.first['artifacts']).to be_empty
end
it_behaves_like 'a job with artifacts and trace' do it 'returns correct values' do
let(:api_endpoint) { "/projects/#{project.id}/pipelines/#{pipeline.id}/jobs" } expect(json_response).not_to be_empty
end 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 'returns pipeline data' do it do
json_job = json_response.first 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(json_job['pipeline']).not_to be_empty expect do
expect(json_job['pipeline']['id']).to eq job.pipeline.id get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query
expect(json_job['pipeline']['ref']).to eq job.pipeline.ref end.not_to exceed_all_query_limit(control_count)
expect(json_job['pipeline']['sha']).to eq job.pipeline.sha end
expect(json_job['pipeline']['status']).to eq job.pipeline.status
end end
context 'filter jobs with one scope element' do context 'no pipeline is found' do
let(:query) { { 'scope' => 'pending' } } it 'does not return jobs' do
get api("/projects/#{project2.id}/pipelines/#{pipeline.id}/jobs", user)
it do expect(json_response['message']).to eq '404 Project Not Found'
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:not_found)
expect(json_response).to be_an Array
end end
end end
context 'filter jobs with hash' do context 'unauthorized user' do
let(:query) { { scope: { hello: 'pending', world: 'running' } } } context 'when user is not logged in' do
let(:api_user) { nil }
it { expect(response).to have_gitlab_http_status(:bad_request) } 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
context 'filter jobs with array of scope elements' do context 'with ci_jobs_finder ff disabled' do
let(:query) { { scope: %w(pending running) } } before do
stub_feature_flags(ci_jobs_finder_refactor: false)
end
it do context 'authorized user' do
it 'returns pipeline jobs' do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to be_an Array
end end
end
context 'respond 400 when scope contains invalid state' do it 'returns correct values' do
let(:query) { { scope: %w(unknown running) } } 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 { expect(response).to have_gitlab_http_status(:bad_request) } it_behaves_like 'a job with artifacts and trace' do
end let(:api_endpoint) { "/projects/#{project.id}/pipelines/#{pipeline.id}/jobs" }
end
context 'jobs in different pipelines' do it 'returns pipeline data' do
let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } json_job = json_response.first
let!(:job2) { create(:ci_build, pipeline: pipeline2) }
it 'excludes jobs from other pipelines' do expect(json_job['pipeline']).not_to be_empty
json_response.each { |job| expect(job['pipeline']['id']).to eq(pipeline.id) } 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 end
end
it 'avoids N+1 queries' do context 'filter jobs with one scope element' do
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do let(:query) { { 'scope' => 'pending' } }
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) it do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
end
end
expect do context 'filter jobs with hash' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query let(:query) { { scope: { hello: 'pending', world: 'running' } } }
end.not_to exceed_all_query_limit(control_count)
end
end
context 'unauthorized user' do it { expect(response).to have_gitlab_http_status(:bad_request) }
context 'when user is not logged in' do end
let(:api_user) { nil }
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 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(json_response['message']).to eq '404 Project Not Found'
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
end end
context 'when user is guest' do context 'unauthorized user' do
let(:guest) { create(:project_member, :guest, project: project).user } context 'when user is not logged in' do
let(:api_user) { guest } let(:api_user) { nil }
it 'does not return jobs' do it 'does not return jobs' do
expect(response).to have_gitlab_http_status(:forbidden) 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
end end
...@@ -450,143 +583,314 @@ RSpec.describe API::Ci::Pipelines do ...@@ -450,143 +583,314 @@ RSpec.describe API::Ci::Pipelines do
end end
end end
context 'authorized user' do context 'with ci_jobs_finder_refactor ff enabled' do
it 'returns pipeline bridges' do before do
expect(response).to have_gitlab_http_status(:ok) stub_feature_flags(ci_jobs_finder_refactor: true)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
end end
it 'returns correct values' do context 'authorized user' do
expect(json_response).not_to be_empty it 'returns pipeline bridges' do
expect(json_response.first['commit']['id']).to eq project.commit.id expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['id']).to eq bridge.id expect(response).to include_pagination_headers
expect(json_response.first['name']).to eq bridge.name expect(json_response).to be_an Array
expect(json_response.first['stage']).to eq bridge.stage end
end
it 'returns pipeline data' do it 'returns correct values' do
json_bridge = json_response.first 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
expect(json_bridge['pipeline']).not_to be_empty it 'returns pipeline data' do
expect(json_bridge['pipeline']['id']).to eq bridge.pipeline.id json_bridge = json_response.first
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 expect(json_bridge['pipeline']).not_to be_empty
json_bridge = json_response.first 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
expect(json_bridge['downstream_pipeline']).not_to be_empty it 'returns downstream pipeline data' do
expect(json_bridge['downstream_pipeline']['id']).to eq downstream_pipeline.id json_bridge = json_response.first
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 expect(json_bridge['downstream_pipeline']).not_to be_empty
before_all do expect(json_bridge['downstream_pipeline']['id']).to eq downstream_pipeline.id
create_bridge(pipeline, :pending) expect(json_bridge['downstream_pipeline']['ref']).to eq downstream_pipeline.ref
create_bridge(pipeline, :running) expect(json_bridge['downstream_pipeline']['sha']).to eq downstream_pipeline.sha
expect(json_bridge['downstream_pipeline']['status']).to eq downstream_pipeline.status
end end
context 'with one scope element' do context 'filter bridges' do
let(:query) { { 'scope' => 'pending' } } before_all do
create_bridge(pipeline, :pending)
create_bridge(pipeline, :running)
end
it :skip_before_request do context 'with one scope element' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query let(:query) { { 'scope' => 'pending' } }
expect(response).to have_gitlab_http_status(:ok) it :skip_before_request do
expect(json_response).to be_an Array get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query
expect(json_response.count).to eq 1
expect(json_response.first["status"]).to eq "pending" 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 end
end
context 'with array of scope elements' do context 'with array of scope elements' do
let(:query) { { scope: %w(pending running) } } let(:query) { { scope: %w(pending running) } }
it :skip_before_request do it :skip_before_request do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.count).to eq 2 expect(json_response.count).to eq 2
json_response.each { |r| expect(%w(pending running).include?(r['status'])).to be true } json_response.each { |r| expect(%w(pending running).include?(r['status'])).to be true }
end
end end
end end
end
context 'respond 400 when scope contains invalid state' do context 'respond 400 when scope contains invalid state' do
context 'in an array' do context 'in an array' do
let(:query) { { scope: %w(unknown running) } } let(:query) { { scope: %w(unknown running) } }
it { expect(response).to have_gitlab_http_status(:bad_request) } 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 end
context 'in a hash' do context 'bridges in different pipelines' do
let(:query) { { scope: { unknown: true } } } let!(:pipeline2) { create(:ci_empty_pipeline, project: project) }
let!(:bridge2) { create(:ci_bridge, pipeline: pipeline2) }
it { expect(response).to have_gitlab_http_status(:bad_request) } it 'excludes bridges from other pipelines' do
json_response.each { |bridge| expect(bridge['pipeline']['id']).to eq(pipeline.id) }
end
end end
context 'in a string' do it 'avoids N+1 queries' do
let(:query) { { scope: "unknown" } } 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) }
it { expect(response).to have_gitlab_http_status(:bad_request) } 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
end end
context 'bridges in different pipelines' do context 'no pipeline is found' do
let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } it 'does not return bridges' do
let!(:bridge2) { create(:ci_bridge, pipeline: pipeline2) } get api("/projects/#{project2.id}/pipelines/#{pipeline.id}/bridges", user)
it 'excludes bridges from other pipelines' do expect(json_response['message']).to eq '404 Project Not Found'
json_response.each { |bridge| expect(bridge['pipeline']['id']).to eq(pipeline.id) } expect(response).to have_gitlab_http_status(:not_found)
end end
end end
it 'avoids N+1 queries' do context 'unauthorized user' do
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do context 'when user is not logged in' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query let(:api_user) { nil }
end.count
3.times { create_bridge(pipeline) } 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
expect do context 'when user is guest' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query let(:api_user) { guest }
end.not_to exceed_all_query_limit(control_count) 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
end end
context 'unauthorized user' do context 'with ci_jobs_finder_refactor ff disabled' do
context 'when user is not logged in' do before do
let(:api_user) { nil } 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 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(json_response['message']).to eq '404 Project Not Found'
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
end end
context 'when user is guest' do context 'unauthorized user' do
let(:api_user) { guest } context 'when user is not logged in' do
let(:guest) { create(:project_member, :guest, project: project).user } let(:api_user) { nil }
it 'does not return bridges' do it 'does not return bridges' do
expect(response).to have_gitlab_http_status(:forbidden) expect(json_response['message']).to eq '404 Project Not Found'
expect(response).to have_gitlab_http_status(:not_found)
end
end end
end
context 'when user has no read_build access for project' do context 'when user is guest' do
before do let(:api_user) { guest }
project.add_guest(api_user) 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 end
it 'does not return bridges' do context 'when user has no read_build access for project' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user) before do
expect(response).to have_gitlab_http_status(:forbidden) 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 end
end end
...@@ -595,11 +899,11 @@ RSpec.describe API::Ci::Pipelines do ...@@ -595,11 +899,11 @@ RSpec.describe API::Ci::Pipelines do
create(:ci_bridge, status: status, pipeline: pipeline).tap do |bridge| create(:ci_bridge, status: status, pipeline: pipeline).tap do |bridge|
downstream_pipeline = create(:ci_pipeline) downstream_pipeline = create(:ci_pipeline)
create(:ci_sources_pipeline, create(:ci_sources_pipeline,
source_pipeline: pipeline, source_pipeline: pipeline,
source_project: pipeline.project, source_project: pipeline.project,
source_job: bridge, source_job: bridge,
pipeline: downstream_pipeline, pipeline: downstream_pipeline,
project: downstream_pipeline.project) project: downstream_pipeline.project)
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment