From b3647b2a67930e8aa3c1b1dd9bda29c368c862ba Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 23 Mar 2021 09:09:17 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/requests/api/graphql/ci/groups_spec.rb | 23 +-- spec/requests/api/graphql/ci/job_spec.rb | 100 +++++++++++ spec/requests/api/graphql/project/pipeline_spec.rb | 193 +++++++++++++++++++-- 3 files changed, 286 insertions(+), 30 deletions(-) create mode 100644 spec/requests/api/graphql/ci/job_spec.rb (limited to 'spec/requests') diff --git a/spec/requests/api/graphql/ci/groups_spec.rb b/spec/requests/api/graphql/ci/groups_spec.rb index 9e81358a152..4c063d359a5 100644 --- a/spec/requests/api/graphql/ci/groups_spec.rb +++ b/spec/requests/api/graphql/ci/groups_spec.rb @@ -4,10 +4,14 @@ require 'spec_helper' RSpec.describe 'Query.project.pipeline.stages.groups' do include GraphqlHelpers - let(:project) { create(:project, :repository, :public) } - let(:user) { create(:user) } - let(:pipeline) { create(:ci_pipeline, project: project, user: user) } - let(:group_graphql_data) { graphql_data.dig('project', 'pipeline', 'stages', 'nodes', 0, 'groups', 'nodes') } + let_it_be(:project) { create(:project, :repository, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) } + let(:group_graphql_data) { graphql_data_at(:project, :pipeline, :stages, :nodes, 0, :groups, :nodes) } + + let_it_be(:job_a) { create(:commit_status, pipeline: pipeline, name: 'rspec 0 2') } + let_it_be(:job_b) { create(:ci_build, pipeline: pipeline, name: 'rspec 0 1') } + let_it_be(:job_c) { create(:ci_bridge, pipeline: pipeline, name: 'spinach 0 1') } let(:params) { {} } @@ -38,18 +42,15 @@ RSpec.describe 'Query.project.pipeline.stages.groups' do end before do - create(:commit_status, pipeline: pipeline, name: 'rspec 0 2') - create(:commit_status, pipeline: pipeline, name: 'rspec 0 1') - create(:commit_status, pipeline: pipeline, name: 'spinach 0 1') post_graphql(query, current_user: user) end it_behaves_like 'a working graphql query' it 'returns a array of jobs belonging to a pipeline' do - expect(group_graphql_data.map { |g| g.slice('name', 'size') }).to eq([ - { 'name' => 'rspec', 'size' => 2 }, - { 'name' => 'spinach', 'size' => 1 } - ]) + expect(group_graphql_data).to contain_exactly( + a_hash_including('name' => 'rspec', 'size' => 2), + a_hash_including('name' => 'spinach', 'size' => 1) + ) end end diff --git a/spec/requests/api/graphql/ci/job_spec.rb b/spec/requests/api/graphql/ci/job_spec.rb new file mode 100644 index 00000000000..78f7d3e149b --- /dev/null +++ b/spec/requests/api/graphql/ci/job_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.project(fullPath).pipelines.job(id)' do + include GraphqlHelpers + + let_it_be(:user) { create_default(:user) } + let_it_be(:project) { create(:project, :repository, :public) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + + let_it_be(:prepare_stage) { create(:ci_stage_entity, pipeline: pipeline, project: project, name: 'prepare') } + let_it_be(:test_stage) { create(:ci_stage_entity, pipeline: pipeline, project: project, name: 'test') } + + let_it_be(:job_1) { create(:ci_build, pipeline: pipeline, stage: 'prepare', name: 'Job 1') } + let_it_be(:job_2) { create(:ci_build, pipeline: pipeline, stage: 'test', name: 'Job 2') } + let_it_be(:job_3) { create(:ci_build, pipeline: pipeline, stage: 'test', name: 'Job 3') } + + let(:path_to_job) do + [ + [:project, { full_path: project.full_path }], + [:pipelines, { first: 1 }], + [:nodes, nil], + [:job, { id: global_id_of(job_2) }] + ] + end + + let(:query) do + wrap_fields(query_graphql_path(query_path, all_graphql_fields_for(terminal_type))) + end + + describe 'scalar fields' do + let(:path) { [:project, :pipelines, :nodes, 0, :job] } + let(:query_path) { path_to_job } + let(:terminal_type) { 'CiJob' } + + it 'retrieves scalar fields' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(*path)).to match a_hash_including( + 'id' => global_id_of(job_2), + 'name' => job_2.name, + 'allowFailure' => job_2.allow_failure, + 'duration' => job_2.duration, + 'status' => job_2.status.upcase + ) + end + + context 'when fetching by name' do + before do + query_path.last[1] = { name: job_2.name } + end + + it 'retrieves scalar fields' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(*path)).to match a_hash_including( + 'id' => global_id_of(job_2), + 'name' => job_2.name + ) + end + end + end + + describe '.detailedStatus' do + let(:path) { [:project, :pipelines, :nodes, 0, :job, :detailed_status] } + let(:query_path) { path_to_job + [:detailed_status] } + let(:terminal_type) { 'DetailedStatus' } + + it 'retrieves detailed status' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(*path)).to match a_hash_including( + 'text' => 'pending', + 'label' => 'pending', + 'action' => a_hash_including('buttonTitle' => 'Cancel this job', 'icon' => 'cancel') + ) + end + end + + describe '.stage' do + let(:path) { [:project, :pipelines, :nodes, 0, :job, :stage] } + let(:query_path) { path_to_job + [:stage] } + let(:terminal_type) { 'CiStage' } + + it 'returns appropriate data' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(*path)).to match a_hash_including( + 'name' => test_stage.name, + 'jobs' => a_hash_including( + 'nodes' => contain_exactly( + a_hash_including('id' => global_id_of(job_2)), + a_hash_including('id' => global_id_of(job_3)) + ) + ) + ) + end + end +end diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb index cc028ff2ff9..6436fe1e9ef 100644 --- a/spec/requests/api/graphql/project/pipeline_spec.rb +++ b/spec/requests/api/graphql/project/pipeline_spec.rb @@ -5,24 +5,28 @@ require 'spec_helper' RSpec.describe 'getting pipeline information nested in a project' do include GraphqlHelpers - let!(:project) { create(:project, :repository, :public) } - let!(:pipeline) { create(:ci_pipeline, project: project) } - let!(:current_user) { create(:user) } - let(:pipeline_graphql_data) { graphql_data['project']['pipeline'] } - - let!(:query) do - %( - query { - project(fullPath: "#{project.full_path}") { - pipeline(iid: "#{pipeline.iid}") { - configSource - } - } - } + let_it_be(:project) { create(:project, :repository, :public) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:current_user) { create(:user) } + let_it_be(:build_job) { create(:ci_build, :trace_with_sections, name: 'build-a', pipeline: pipeline) } + let_it_be(:failed_build) { create(:ci_build, :failed, name: 'failed-build', pipeline: pipeline) } + let_it_be(:bridge) { create(:ci_bridge, name: 'ci-bridge-example', pipeline: pipeline) } + + let(:path) { %i[project pipeline] } + let(:pipeline_graphql_data) { graphql_data_at(*path) } + let(:depth) { 3 } + let(:excluded) { %w[job project] } # Project is very expensive, due to the number of fields + let(:fields) { all_graphql_fields_for('Pipeline', excluded: excluded, max_depth: depth) } + + let(:query) do + graphql_query_for( + :project, + { full_path: project.full_path }, + query_graphql_field(:pipeline, { iid: pipeline.iid.to_s }, fields) ) end - it_behaves_like 'a working graphql query' do + it_behaves_like 'a working graphql query', :use_clean_rails_memory_store_caching, :request_store do before do post_graphql(query, current_user: current_user) end @@ -37,14 +41,18 @@ RSpec.describe 'getting pipeline information nested in a project' do it 'contains configSource' do post_graphql(query, current_user: current_user) - expect(pipeline_graphql_data.dig('configSource')).to eq('UNKNOWN_SOURCE') + expect(pipeline_graphql_data['configSource']).to eq('UNKNOWN_SOURCE') end - context 'batching' do - let!(:pipeline2) { create(:ci_pipeline, project: project, user: current_user, builds: [create(:ci_build, :success)]) } - let!(:pipeline3) { create(:ci_pipeline, project: project, user: current_user, builds: [create(:ci_build, :success)]) } + context 'when batching' do + let!(:pipeline2) { successful_pipeline } + let!(:pipeline3) { successful_pipeline } let!(:query) { build_query_to_find_pipeline_shas(pipeline, pipeline2, pipeline3) } + def successful_pipeline + create(:ci_pipeline, project: project, user: current_user, builds: [create(:ci_build, :success)]) + end + it 'executes the finder once' do mock = double(Ci::PipelinesFinder) opts = { iids: [pipeline.iid, pipeline2.iid, pipeline3.iid].map(&:to_s) } @@ -80,4 +88,151 @@ RSpec.describe 'getting pipeline information nested in a project' do graphql_query_for('project', { 'fullPath' => project.full_path }, pipeline_fields) end + + context 'when enough data is requested' do + let(:fields) do + query_graphql_field(:jobs, nil, + query_graphql_field(:nodes, {}, all_graphql_fields_for('CiJob', max_depth: 3))) + end + + it 'contains jobs' do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(*path, :jobs, :nodes)).to contain_exactly( + a_hash_including( + 'name' => build_job.name, + 'status' => build_job.status.upcase, + 'duration' => build_job.duration + ), + a_hash_including( + 'id' => global_id_of(failed_build), + 'status' => failed_build.status.upcase + ), + a_hash_including( + 'id' => global_id_of(bridge), + 'status' => bridge.status.upcase + ) + ) + end + end + + context 'when requesting only builds with certain statuses' do + let(:variables) do + { + path: project.full_path, + pipelineIID: pipeline.iid.to_s, + status: :FAILED + } + end + + let(:query) do + <<~GQL + query($path: ID!, $pipelineIID: ID!, $status: CiJobStatus!) { + project(fullPath: $path) { + pipeline(iid: $pipelineIID) { + jobs(statuses: [$status]) { + nodes { + #{all_graphql_fields_for('CiJob', max_depth: 1)} + } + } + } + } + } + GQL + end + + it 'can filter build jobs by status' do + post_graphql(query, current_user: current_user, variables: variables) + + expect(graphql_data_at(*path, :jobs, :nodes)) + .to contain_exactly(a_hash_including('id' => global_id_of(failed_build))) + end + end + + context 'when requesting a specific job' do + let(:variables) do + { + path: project.full_path, + pipelineIID: pipeline.iid.to_s + } + end + + let(:build_fields) do + all_graphql_fields_for('CiJob', max_depth: 1) + end + + let(:query) do + <<~GQL + query($path: ID!, $pipelineIID: ID!, $jobName: String, $jobID: JobID) { + project(fullPath: $path) { + pipeline(iid: $pipelineIID) { + job(id: $jobID, name: $jobName) { + #{build_fields} + } + } + } + } + GQL + end + + let(:the_job) do + a_hash_including('name' => build_job.name, 'id' => global_id_of(build_job)) + end + + it 'can request a build by name' do + vars = variables.merge(jobName: build_job.name) + + post_graphql(query, current_user: current_user, variables: vars) + + expect(graphql_data_at(*path, :job)).to match(the_job) + end + + it 'can request a build by ID' do + vars = variables.merge(jobID: global_id_of(build_job)) + + post_graphql(query, current_user: current_user, variables: vars) + + expect(graphql_data_at(*path, :job)).to match(the_job) + end + + context 'when we request nested fields of the build' do + let_it_be(:needy) { create(:ci_build, :dependent, pipeline: pipeline) } + + let(:build_fields) { 'needs { nodes { name } }' } + let(:vars) { variables.merge(jobID: global_id_of(needy)) } + + it 'returns the nested data' do + post_graphql(query, current_user: current_user, variables: vars) + + expect(graphql_data_at(*path, :job, :needs, :nodes)).to contain_exactly( + a_hash_including('name' => needy.needs.first.name) + ) + end + + it 'requires a constant number of queries' do + fst_user = create(:user) + snd_user = create(:user) + path = %i[project pipeline job needs nodes name] + + baseline = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: fst_user, variables: vars) + end + + expect(baseline.count).to be > 0 + dep_names = graphql_dig_at(graphql_data(fresh_response_data), *path) + + deps = create_list(:ci_build, 3, :unique_name, pipeline: pipeline) + deps.each { |d| create(:ci_build_need, build: needy, name: d.name) } + + expect do + post_graphql(query, current_user: snd_user, variables: vars) + end.not_to exceed_query_limit(baseline) + + more_names = graphql_dig_at(graphql_data(fresh_response_data), *path) + + expect(more_names).to include(*dep_names) + expect(more_names.count).to be > dep_names.count + end + end + end end -- cgit v1.2.3