Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests/api/graphql/ci/runner_spec.rb')
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb264
1 files changed, 192 insertions, 72 deletions
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 94c0a3c41bd..ca08e780758 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'Query.runner(id)' do
+RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
include GraphqlHelpers
let_it_be(:user) { create(:user, :admin) }
@@ -74,34 +74,39 @@ RSpec.describe 'Query.runner(id)' do
runner_data = graphql_data_at(:runner)
expect(runner_data).not_to be_nil
- expect(runner_data).to match a_hash_including(
- 'id' => runner.to_global_id.to_s,
- 'description' => runner.description,
- 'createdAt' => runner.created_at&.iso8601,
- 'contactedAt' => runner.contacted_at&.iso8601,
- 'version' => runner.version,
- 'shortSha' => runner.short_sha,
- 'revision' => runner.revision,
- 'locked' => false,
- 'active' => runner.active,
- 'paused' => !runner.active,
- 'status' => runner.status('14.5').to_s.upcase,
- 'maximumTimeout' => runner.maximum_timeout,
- 'accessLevel' => runner.access_level.to_s.upcase,
- 'runUntagged' => runner.run_untagged,
- 'ipAddress' => runner.ip_address,
- 'runnerType' => runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE',
- 'executorName' => runner.executor_type&.dasherize,
- 'architectureName' => runner.architecture,
- 'platformName' => runner.platform,
- 'maintenanceNote' => runner.maintenance_note,
- 'maintenanceNoteHtml' =>
+ expect(runner_data).to match a_graphql_entity_for(
+ runner,
+ description: runner.description,
+ created_at: runner.created_at&.iso8601,
+ contacted_at: runner.contacted_at&.iso8601,
+ version: runner.version,
+ short_sha: runner.short_sha,
+ revision: runner.revision,
+ locked: false,
+ active: runner.active,
+ paused: !runner.active,
+ status: runner.status('14.5').to_s.upcase,
+ job_execution_status: runner.builds.running.any? ? 'RUNNING' : 'IDLE',
+ maximum_timeout: runner.maximum_timeout,
+ access_level: runner.access_level.to_s.upcase,
+ run_untagged: runner.run_untagged,
+ ip_address: runner.ip_address,
+ runner_type: runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE',
+ executor_name: runner.executor_type&.dasherize,
+ architecture_name: runner.architecture,
+ platform_name: runner.platform,
+ maintenance_note: runner.maintenance_note,
+ maintenance_note_html:
runner.maintainer_note.present? ? a_string_including('<strong>Test maintenance note</strong>') : '',
- 'jobCount' => 0,
- 'jobs' => a_hash_including("count" => 0, "nodes" => [], "pageInfo" => anything),
- 'projectCount' => nil,
- 'adminUrl' => "http://localhost/admin/runners/#{runner.id}",
- 'userPermissions' => {
+ job_count: runner.builds.count,
+ jobs: a_hash_including(
+ "count" => runner.builds.count,
+ "nodes" => an_instance_of(Array),
+ "pageInfo" => anything
+ ),
+ project_count: nil,
+ admin_url: "http://localhost/admin/runners/#{runner.id}",
+ user_permissions: {
'readRunner' => true,
'updateRunner' => true,
'deleteRunner' => true,
@@ -129,10 +134,7 @@ RSpec.describe 'Query.runner(id)' do
runner_data = graphql_data_at(:runner)
expect(runner_data).not_to be_nil
- expect(runner_data).to match a_hash_including(
- 'id' => runner.to_global_id.to_s,
- 'adminUrl' => nil
- )
+ expect(runner_data).to match a_graphql_entity_for(runner, admin_url: nil)
expect(runner_data['tagList']).to match_array runner.tag_list
end
end
@@ -179,6 +181,16 @@ RSpec.describe 'Query.runner(id)' do
expect(runner_data).not_to include('tagList')
end
end
+
+ context 'with build running' do
+ before do
+ project = create(:project, :repository)
+ pipeline = create(:ci_pipeline, project: project)
+ create(:ci_build, :running, runner: runner, pipeline: pipeline)
+ end
+
+ it_behaves_like 'runner details fetch'
+ end
end
describe 'for project runner' do
@@ -216,9 +228,47 @@ RSpec.describe 'Query.runner(id)' do
runner_data = graphql_data_at(:runner)
- expect(runner_data).to match a_hash_including(
- 'id' => project_runner.to_global_id.to_s,
- 'locked' => is_locked
+ expect(runner_data).to match a_graphql_entity_for(project_runner, locked: is_locked)
+ end
+ end
+ end
+
+ describe 'jobCount' do
+ let_it_be(:pipeline1) { create(:ci_pipeline, project: project1) }
+ let_it_be(:pipeline2) { create(:ci_pipeline, project: project1) }
+ let_it_be(:build1) { create(:ci_build, :running, runner: active_project_runner, pipeline: pipeline1) }
+ let_it_be(:build2) { create(:ci_build, :running, runner: active_project_runner, pipeline: pipeline2) }
+
+ let(:runner_query_fragment) { 'id jobCount' }
+ let(:query) do
+ %(
+ query {
+ runner1: runner(id: "#{active_project_runner.to_global_id}") { #{runner_query_fragment} }
+ runner2: runner(id: "#{inactive_instance_runner.to_global_id}") { #{runner_query_fragment} }
+ }
+ )
+ end
+
+ it 'retrieves correct jobCount values' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data).to match a_hash_including(
+ 'runner1' => a_graphql_entity_for(active_project_runner, job_count: 2),
+ 'runner2' => a_graphql_entity_for(inactive_instance_runner, job_count: 0)
+ )
+ end
+
+ context 'when JOB_COUNT_LIMIT is in effect' do
+ before do
+ stub_const('Types::Ci::RunnerType::JOB_COUNT_LIMIT', 0)
+ end
+
+ it 'retrieves correct capped jobCount values' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data).to match a_hash_including(
+ 'runner1' => a_graphql_entity_for(active_project_runner, job_count: 1),
+ 'runner2' => a_graphql_entity_for(inactive_instance_runner, job_count: 0)
)
end
end
@@ -243,18 +293,8 @@ RSpec.describe 'Query.runner(id)' do
post_graphql(query, current_user: user)
expect(graphql_data).to match a_hash_including(
- 'runner1' => {
- 'id' => runner1.to_global_id.to_s,
- 'ownerProject' => {
- 'id' => project2.to_global_id.to_s
- }
- },
- 'runner2' => {
- 'id' => runner2.to_global_id.to_s,
- 'ownerProject' => {
- 'id' => project1.to_global_id.to_s
- }
- }
+ 'runner1' => a_graphql_entity_for(runner1, owner_project: a_graphql_entity_for(project2)),
+ 'runner2' => a_graphql_entity_for(runner2, owner_project: a_graphql_entity_for(project1))
)
end
end
@@ -284,8 +324,8 @@ RSpec.describe 'Query.runner(id)' do
it 'retrieves groups field with expected value' do
post_graphql(query, current_user: user)
- runner_data = graphql_data_at(:runner, :groups)
- expect(runner_data).to eq 'nodes' => [{ 'id' => group.to_global_id.to_s }]
+ runner_data = graphql_data_at(:runner, :groups, :nodes)
+ expect(runner_data).to contain_exactly(a_graphql_entity_for(group))
end
end
@@ -409,13 +449,13 @@ RSpec.describe 'Query.runner(id)' do
'jobCount' => 1,
'jobs' => a_hash_including(
"count" => 1,
- "nodes" => [{ "id" => job.to_global_id.to_s, "status" => job.status.upcase }]
+ "nodes" => [a_graphql_entity_for(job, status: job.status.upcase)]
),
'projectCount' => 2,
'projects' => {
'nodes' => [
- { 'id' => project1.to_global_id.to_s },
- { 'id' => project2.to_global_id.to_s }
+ a_graphql_entity_for(project1),
+ a_graphql_entity_for(project2)
]
})
expect(runner2_data).to match a_hash_including(
@@ -486,15 +526,24 @@ RSpec.describe 'Query.runner(id)' do
groups {
nodes {
id
+ path
+ fullPath
+ webUrl
}
}
projects {
nodes {
id
+ path
+ fullPath
+ webUrl
}
}
ownerProject {
id
+ path
+ fullPath
+ webUrl
}
}
SINGLE
@@ -503,8 +552,8 @@ RSpec.describe 'Query.runner(id)' do
let(:active_project_runner2) { create(:ci_runner, :project) }
let(:active_group_runner2) { create(:ci_runner, :group) }
- # Currently excluding known N+1 issues, see https://gitlab.com/gitlab-org/gitlab/-/issues/334759
- let(:excluded_fields) { %w[jobCount groups projects ownerProject] }
+ # Exclude fields that are already hardcoded above
+ let(:excluded_fields) { %w[jobs groups projects ownerProject] }
let(:single_query) do
<<~QUERY
@@ -542,27 +591,98 @@ RSpec.describe 'Query.runner(id)' do
expect(graphql_data.count).to eq 6
expect(graphql_data).to match(
a_hash_including(
- 'instance_runner1' => a_hash_including('id' => active_instance_runner.to_global_id.to_s),
- 'instance_runner2' => a_hash_including('id' => inactive_instance_runner.to_global_id.to_s),
- 'group_runner1' => a_hash_including(
- 'id' => active_group_runner.to_global_id.to_s,
- 'groups' => { 'nodes' => [a_hash_including('id' => group.to_global_id.to_s)] }
+ 'instance_runner1' => a_graphql_entity_for(active_instance_runner),
+ 'instance_runner2' => a_graphql_entity_for(inactive_instance_runner),
+ 'group_runner1' => a_graphql_entity_for(
+ active_group_runner,
+ groups: { 'nodes' => contain_exactly(a_graphql_entity_for(group)) }
),
- 'group_runner2' => a_hash_including(
- 'id' => active_group_runner2.to_global_id.to_s,
- 'groups' => { 'nodes' => [a_hash_including('id' => active_group_runner2.groups[0].to_global_id.to_s)] }
+ 'group_runner2' => a_graphql_entity_for(
+ active_group_runner2,
+ groups: { 'nodes' => active_group_runner2.groups.map { |g| a_graphql_entity_for(g) } }
),
- 'project_runner1' => a_hash_including(
- 'id' => active_project_runner.to_global_id.to_s,
- 'projects' => { 'nodes' => [a_hash_including('id' => active_project_runner.projects[0].to_global_id.to_s)] },
- 'ownerProject' => a_hash_including('id' => active_project_runner.projects[0].to_global_id.to_s)
+ 'project_runner1' => a_graphql_entity_for(
+ active_project_runner,
+ projects: { 'nodes' => active_project_runner.projects.map { |p| a_graphql_entity_for(p) } },
+ owner_project: a_graphql_entity_for(active_project_runner.projects[0])
),
- 'project_runner2' => a_hash_including(
- 'id' => active_project_runner2.to_global_id.to_s,
- 'projects' => {
- 'nodes' => [a_hash_including('id' => active_project_runner2.projects[0].to_global_id.to_s)]
- },
- 'ownerProject' => a_hash_including('id' => active_project_runner2.projects[0].to_global_id.to_s)
+ 'project_runner2' => a_graphql_entity_for(
+ active_project_runner2,
+ projects: { 'nodes' => active_project_runner2.projects.map { |p| a_graphql_entity_for(p) } },
+ owner_project: a_graphql_entity_for(active_project_runner2.projects[0])
+ )
+ ))
+ end
+ end
+
+ describe 'Query limits with jobs' do
+ let!(:group1) { create(:group) }
+ let!(:group2) { create(:group) }
+ let!(:project1) { create(:project, :repository, group: group1) }
+ let!(:project2) { create(:project, :repository, group: group1) }
+ let!(:project3) { create(:project, :repository, group: group2) }
+
+ let!(:merge_request1) { create(:merge_request, source_project: project1) }
+ let!(:merge_request2) { create(:merge_request, source_project: project3) }
+
+ let(:project_runner2) { create(:ci_runner, :project, projects: [project1, project2]) }
+ let!(:build1) { create(:ci_build, :success, name: 'Build One', runner: project_runner2, pipeline: pipeline1) }
+ let!(:pipeline1) do
+ create(:ci_pipeline, project: project1, source: :merge_request_event, merge_request: merge_request1, ref: 'main',
+ target_sha: 'xxx')
+ end
+
+ let(:query) do
+ <<~QUERY
+ {
+ runner(id: "#{project_runner2.to_global_id}") {
+ id
+ jobs {
+ nodes {
+ id
+ detailedStatus {
+ id
+ detailsPath
+ group
+ icon
+ text
+ }
+ shortSha
+ commitPath
+ finishedAt
+ duration
+ queuedDuration
+ tags
+ }
+ }
+ }
+ }
+ QUERY
+ end
+
+ it 'does not execute more queries per job', :aggregate_failures do
+ # warm-up license cache and so on:
+ personal_access_token = create(:personal_access_token, user: user)
+ args = { current_user: user, token: { personal_access_token: personal_access_token } }
+ post_graphql(query, **args)
+
+ control = ActiveRecord::QueryRecorder.new(query_recorder_debug: true) { post_graphql(query, **args) }
+
+ # Add a new build to project_runner2
+ project_runner2.runner_projects << build(:ci_runner_project, runner: project_runner2, project: project3)
+ pipeline2 = create(:ci_pipeline, project: project3, source: :merge_request_event, merge_request: merge_request2,
+ ref: 'main', target_sha: 'xxx')
+ build2 = create(:ci_build, :success, name: 'Build Two', runner: project_runner2, pipeline: pipeline2)
+
+ args[:current_user] = create(:user, :admin) # do not reuse same user
+ expect { post_graphql(query, **args) }.not_to exceed_all_query_limit(control)
+
+ expect(graphql_data.count).to eq 1
+ expect(graphql_data).to match(
+ a_hash_including(
+ 'runner' => a_graphql_entity_for(
+ project_runner2,
+ jobs: { 'nodes' => containing_exactly(a_graphql_entity_for(build1), a_graphql_entity_for(build2)) }
)
))
end