Commit 647312f4 authored by Adam Hegyi's avatar Adam Hegyi

Merge branch 'pedropombeiro/334686/graphql-add-runners-to-groups' into 'master'

GraphQL: Add runners to groups query [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!65673
parents 5f9dcb15 60409ad0
...@@ -59,7 +59,7 @@ class Groups::RunnersController < Groups::ApplicationController ...@@ -59,7 +59,7 @@ class Groups::RunnersController < Groups::ApplicationController
private private
def runner def runner
@runner ||= Ci::RunnersFinder.new(current_user: current_user, group: @group, params: {}).execute @runner ||= Ci::RunnersFinder.new(current_user: current_user, params: { group: @group }).execute
.except(:limit, :offset) .except(:limit, :offset)
.find(params[:id]) .find(params[:id])
end end
......
...@@ -17,7 +17,7 @@ module Groups ...@@ -17,7 +17,7 @@ module Groups
NUMBER_OF_RUNNERS_PER_PAGE = 4 NUMBER_OF_RUNNERS_PER_PAGE = 4
def show def show
runners_finder = Ci::RunnersFinder.new(current_user: current_user, group: @group, params: params) runners_finder = Ci::RunnersFinder.new(current_user: current_user, params: params.merge({ group: @group }))
# We need all runners for count # We need all runners for count
@all_group_runners = runners_finder.execute.except(:limit, :offset) @all_group_runners = runners_finder.execute.except(:limit, :offset)
@group_runners = runners_finder.execute.page(params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE) @group_runners = runners_finder.execute.page(params[:page]).per(NUMBER_OF_RUNNERS_PER_PAGE)
......
...@@ -7,9 +7,9 @@ module Ci ...@@ -7,9 +7,9 @@ module Ci
ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze
DEFAULT_SORT = 'created_at_desc' DEFAULT_SORT = 'created_at_desc'
def initialize(current_user:, group: nil, params:) def initialize(current_user:, params:)
@params = params @params = params
@group = group @group = params.delete(:group)
@current_user = current_user @current_user = current_user
end end
...@@ -48,10 +48,16 @@ module Ci ...@@ -48,10 +48,16 @@ module Ci
def group_runners def group_runners
raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :admin_group, @group) raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :admin_group, @group)
# Getting all runners from the group itself and all its descendants @runners = case @params[:membership]
descendant_projects = Project.for_group_and_its_subgroups(@group) when :direct
Ci::Runner.belonging_to_group(@group.id)
@runners = Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects) when :descendants, nil
# Getting all runners from the group itself and all its descendant groups/projects
descendant_projects = Project.for_group_and_its_subgroups(@group)
Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects)
else
raise ArgumentError, 'Invalid membership filter'
end
end end
def filter_by_status! def filter_by_status!
......
# frozen_string_literal: true
module Resolvers
module Ci
class GroupRunnersResolver < RunnersResolver
type Types::Ci::RunnerType.connection_type, null: true
argument :membership, ::Types::Ci::RunnerMembershipFilterEnum,
required: false,
default_value: :descendants,
description: 'Control which runners to include in the results.'
protected
def runners_finder_params(params)
super(params).merge(membership: params[:membership])
end
def parent_param
raise 'Expected group missing' unless parent.is_a?(Group)
{ group: parent }
end
end
end
end
...@@ -34,7 +34,7 @@ module Resolvers ...@@ -34,7 +34,7 @@ module Resolvers
.execute) .execute)
end end
private protected
def runners_finder_params(params) def runners_finder_params(params)
{ {
...@@ -47,6 +47,19 @@ module Resolvers ...@@ -47,6 +47,19 @@ module Resolvers
tag_name: node_selection&.selects?(:tag_list) tag_name: node_selection&.selects?(:tag_list)
} }
}.compact }.compact
.merge(parent_param)
end
def parent_param
return {} unless parent
raise "Unexpected parent type: #{parent.class}"
end
private
def parent
object.respond_to?(:sync) ? object.sync : object
end end
end end
end end
......
# frozen_string_literal: true
module Types
module Ci
class RunnerMembershipFilterEnum < BaseEnum
graphql_name 'RunnerMembershipFilter'
description 'Values for filtering runners in namespaces.'
value 'DIRECT',
description: "Include runners that have a direct relationship.",
value: :direct
value 'DESCENDANTS',
description: "Include runners that have either a direct relationship or a relationship with descendants. These can be project runners or group runners (in the case where group is queried).",
value: :descendants
end
end
end
...@@ -155,6 +155,12 @@ module Types ...@@ -155,6 +155,12 @@ module Types
complexity: 5, complexity: 5,
resolver: Resolvers::GroupsResolver resolver: Resolvers::GroupsResolver
field :runners, Types::Ci::RunnerType.connection_type,
null: true,
resolver: Resolvers::Ci::GroupRunnersResolver,
description: "Find runners visible to the current user.",
feature_flag: :runner_graphql_query
def avatar_url def avatar_url
object.avatar_url(only_path: false) object.avatar_url(only_path: false)
end end
......
...@@ -10004,6 +10004,27 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -10004,6 +10004,27 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupprojectssearch"></a>`search` | [`String`](#string) | Search project with most similar names or paths. | | <a id="groupprojectssearch"></a>`search` | [`String`](#string) | Search project with most similar names or paths. |
| <a id="groupprojectssort"></a>`sort` | [`NamespaceProjectSort`](#namespaceprojectsort) | Sort projects by this criteria. | | <a id="groupprojectssort"></a>`sort` | [`NamespaceProjectSort`](#namespaceprojectsort) | Sort projects by this criteria. |
##### `Group.runners`
Find runners visible to the current user. Available only when feature flag `runner_graphql_query` is enabled. This flag is enabled by default.
Returns [`CiRunnerConnection`](#cirunnerconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="grouprunnersmembership"></a>`membership` | [`RunnerMembershipFilter`](#runnermembershipfilter) | Control which runners to include in the results. |
| <a id="grouprunnerssearch"></a>`search` | [`String`](#string) | Filter by full token or partial text in description field. |
| <a id="grouprunnerssort"></a>`sort` | [`CiRunnerSort`](#cirunnersort) | Sort order of results. |
| <a id="grouprunnersstatus"></a>`status` | [`CiRunnerStatus`](#cirunnerstatus) | Filter runners by status. |
| <a id="grouprunnerstaglist"></a>`tagList` | [`[String!]`](#string) | Filter by tags associated with the runner (comma-separated or array). |
| <a id="grouprunnerstype"></a>`type` | [`CiRunnerType`](#cirunnertype) | Filter runners by type. |
##### `Group.timelogs` ##### `Group.timelogs`
Time logged on issues and merge requests in the group and its subgroups. Time logged on issues and merge requests in the group and its subgroups.
...@@ -15626,6 +15647,15 @@ Status of a requirement based on last test report. ...@@ -15626,6 +15647,15 @@ Status of a requirement based on last test report.
| <a id="requirementstatusfiltermissing"></a>`MISSING` | Requirements without any test report. | | <a id="requirementstatusfiltermissing"></a>`MISSING` | Requirements without any test report. |
| <a id="requirementstatusfilterpassed"></a>`PASSED` | | | <a id="requirementstatusfilterpassed"></a>`PASSED` | |
### `RunnerMembershipFilter`
Values for filtering runners in namespaces.
| Value | Description |
| ----- | ----------- |
| <a id="runnermembershipfilterdescendants"></a>`DESCENDANTS` | Include runners that have either a direct relationship or a relationship with descendants. These can be project runners or group runners (in the case where group is queried). |
| <a id="runnermembershipfilterdirect"></a>`DIRECT` | Include runners that have a direct relationship. |
### `SastUiComponentSize` ### `SastUiComponentSize`
Size of UI component in SAST configuration page. Size of UI component in SAST configuration page.
......
...@@ -18,6 +18,13 @@ RSpec.describe Ci::RunnersFinder do ...@@ -18,6 +18,13 @@ RSpec.describe Ci::RunnersFinder do
end end
end end
context 'with nil group' do
it 'returns all runners' do
expect(Ci::Runner).to receive(:with_tags).and_call_original
expect(described_class.new(current_user: admin, params: { group: nil }).execute).to match_array [runner1, runner2]
end
end
context 'with preload param set to :tag_name true' do context 'with preload param set to :tag_name true' do
it 'requests tags' do it 'requests tags' do
expect(Ci::Runner).to receive(:with_tags).and_call_original expect(Ci::Runner).to receive(:with_tags).and_call_original
...@@ -158,6 +165,7 @@ RSpec.describe Ci::RunnersFinder do ...@@ -158,6 +165,7 @@ RSpec.describe Ci::RunnersFinder do
let_it_be(:project_4) { create(:project, group: sub_group_2) } let_it_be(:project_4) { create(:project, group: sub_group_2) }
let_it_be(:project_5) { create(:project, group: sub_group_3) } let_it_be(:project_5) { create(:project, group: sub_group_3) }
let_it_be(:project_6) { create(:project, group: sub_group_4) } let_it_be(:project_6) { create(:project, group: sub_group_4) }
let_it_be(:runner_instance) { create(:ci_runner, :instance, contacted_at: 13.minutes.ago) }
let_it_be(:runner_group) { create(:ci_runner, :group, contacted_at: 12.minutes.ago) } let_it_be(:runner_group) { create(:ci_runner, :group, contacted_at: 12.minutes.ago) }
let_it_be(:runner_sub_group_1) { create(:ci_runner, :group, active: false, contacted_at: 11.minutes.ago) } let_it_be(:runner_sub_group_1) { create(:ci_runner, :group, active: false, contacted_at: 11.minutes.ago) }
let_it_be(:runner_sub_group_2) { create(:ci_runner, :group, contacted_at: 10.minutes.ago) } let_it_be(:runner_sub_group_2) { create(:ci_runner, :group, contacted_at: 10.minutes.ago) }
...@@ -171,7 +179,10 @@ RSpec.describe Ci::RunnersFinder do ...@@ -171,7 +179,10 @@ RSpec.describe Ci::RunnersFinder do
let_it_be(:runner_project_6) { create(:ci_runner, :project, contacted_at: 2.minutes.ago, projects: [project_5])} let_it_be(:runner_project_6) { create(:ci_runner, :project, contacted_at: 2.minutes.ago, projects: [project_5])}
let_it_be(:runner_project_7) { create(:ci_runner, :project, contacted_at: 1.minute.ago, projects: [project_6])} let_it_be(:runner_project_7) { create(:ci_runner, :project, contacted_at: 1.minute.ago, projects: [project_6])}
let(:params) { {} } let(:target_group) { nil }
let(:membership) { nil }
let(:extra_params) { {} }
let(:params) { { group: target_group, membership: membership }.merge(extra_params).reject { |_, v| v.nil? } }
before do before do
group.runners << runner_group group.runners << runner_group
...@@ -182,65 +193,104 @@ RSpec.describe Ci::RunnersFinder do ...@@ -182,65 +193,104 @@ RSpec.describe Ci::RunnersFinder do
end end
describe '#execute' do describe '#execute' do
subject { described_class.new(current_user: user, group: group, params: params).execute } subject { described_class.new(current_user: user, params: params).execute }
shared_examples 'membership equal to :descendants' do
it 'returns all descendant runners' do
expect(subject).to eq([runner_project_7, runner_project_6, runner_project_5,
runner_project_4, runner_project_3, runner_project_2,
runner_project_1, runner_sub_group_4, runner_sub_group_3,
runner_sub_group_2, runner_sub_group_1, runner_group])
end
end
context 'with user as group owner' do context 'with user as group owner' do
before do before do
group.add_owner(user) group.add_owner(user)
end end
context 'passing no params' do context 'with :group as target group' do
it 'returns all descendant runners' do let(:target_group) { group }
expect(subject).to eq([runner_project_7, runner_project_6, runner_project_5,
runner_project_4, runner_project_3, runner_project_2, context 'passing no params' do
runner_project_1, runner_sub_group_4, runner_sub_group_3, it_behaves_like 'membership equal to :descendants'
runner_sub_group_2, runner_sub_group_1, runner_group])
end end
end
context 'with sort param' do context 'with :descendants membership' do
let(:params) { { sort: 'contacted_asc' } } let(:membership) { :descendants }
it 'sorts by specified attribute' do it_behaves_like 'membership equal to :descendants'
expect(subject).to eq([runner_group, runner_sub_group_1, runner_sub_group_2,
runner_sub_group_3, runner_sub_group_4, runner_project_1,
runner_project_2, runner_project_3, runner_project_4,
runner_project_5, runner_project_6, runner_project_7])
end end
end
context 'filtering' do context 'with :direct membership' do
context 'by search term' do let(:membership) { :direct }
let(:params) { { search: 'runner_project_search' } }
it 'returns runners belonging to group' do
expect(subject).to eq([runner_group])
end
end
context 'with unknown membership' do
let(:membership) { :unsupported }
it 'returns correct runner' do it 'raises an error' do
expect(subject).to eq([runner_project_3]) expect { subject }.to raise_error(ArgumentError, 'Invalid membership filter')
end end
end end
context 'by status' do context 'with nil group' do
let(:params) { { status_status: 'paused' } } let(:target_group) { nil }
it 'returns correct runner' do it 'returns no runners' do
expect(subject).to eq([runner_sub_group_1]) # Query should run against all runners, however since user is not admin, query returns no results
expect(subject).to eq([])
end end
end end
context 'by tag_name' do context 'with sort param' do
let(:params) { { tag_name: %w[runner_tag] } } let(:extra_params) { { sort: 'contacted_asc' } }
it 'returns correct runner' do it 'sorts by specified attribute' do
expect(subject).to eq([runner_project_5]) expect(subject).to eq([runner_group, runner_sub_group_1, runner_sub_group_2,
runner_sub_group_3, runner_sub_group_4, runner_project_1,
runner_project_2, runner_project_3, runner_project_4,
runner_project_5, runner_project_6, runner_project_7])
end end
end end
context 'by runner type' do context 'filtering' do
let(:params) { { type_type: 'project_type' } } context 'by search term' do
let(:extra_params) { { search: 'runner_project_search' } }
it 'returns correct runner' do
expect(subject).to eq([runner_project_3])
end
end
context 'by status' do
let(:extra_params) { { status_status: 'paused' } }
it 'returns correct runner' do
expect(subject).to eq([runner_sub_group_1])
end
end
context 'by tag_name' do
let(:extra_params) { { tag_name: %w[runner_tag] } }
it 'returns correct runner' do
expect(subject).to eq([runner_project_5])
end
end
context 'by runner type' do
let(:extra_params) { { type_type: 'project_type' } }
it 'returns correct runners' do it 'returns correct runners' do
expect(subject).to eq([runner_project_7, runner_project_6, expect(subject).to eq([runner_project_7, runner_project_6,
runner_project_5, runner_project_4, runner_project_5, runner_project_4,
runner_project_3, runner_project_2, runner_project_1]) runner_project_3, runner_project_2, runner_project_1])
end
end end
end end
end end
...@@ -278,7 +328,7 @@ RSpec.describe Ci::RunnersFinder do ...@@ -278,7 +328,7 @@ RSpec.describe Ci::RunnersFinder do
end end
describe '#sort_key' do describe '#sort_key' do
subject { described_class.new(current_user: user, group: group, params: params).sort_key } subject { described_class.new(current_user: user, params: params.merge(group: group)).sort_key }
context 'without params' do context 'without params' do
it 'returns created_at_desc' do it 'returns created_at_desc' do
...@@ -287,7 +337,7 @@ RSpec.describe Ci::RunnersFinder do ...@@ -287,7 +337,7 @@ RSpec.describe Ci::RunnersFinder do
end end
context 'with params' do context 'with params' do
let(:params) { { sort: 'contacted_asc' } } let(:extra_params) { { sort: 'contacted_asc' } }
it 'returns contacted_asc' do it 'returns contacted_asc' do
expect(subject).to eq('contacted_asc') expect(subject).to eq('contacted_asc')
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Ci::GroupRunnersResolver do
include GraphqlHelpers
describe '#resolve' do
subject { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) }
include_context 'runners resolver setup'
let(:obj) { group }
let(:args) { {} }
# First, we can do a couple of basic real tests to verify common cases. That ensures that the code works.
context 'when user cannot see runners' do
it 'returns no runners' do
expect(subject.items.to_a).to eq([])
end
end
context 'with user as group owner' do
before do
group.add_owner(user)
end
it 'returns all the runners' do
expect(subject.items.to_a).to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, subgroup_runner)
end
context 'with membership direct' do
let(:args) { { membership: :direct } }
it 'returns only direct runners' do
expect(subject.items.to_a).to contain_exactly(group_runner)
end
end
end
# Then, we can check specific edge cases for this resolver
context 'with obj set to nil' do
let(:obj) { nil }
it 'raises an error' do
expect { subject }.to raise_error('Expected group missing')
end
end
context 'with obj not set to group' do
let(:obj) { build(:project) }
it 'raises an error' do
expect { subject }.to raise_error('Expected group missing')
end
end
# Here we have a mocked part. We assume that all possible edge cases are covered in RunnersFinder spec. So we don't need to test them twice.
# Only thing we can do is to verify that args from the resolver is correctly transformed to params of the Finder and we return the Finder's result back.
describe 'Allowed query arguments' do
let(:finder) { instance_double(::Ci::RunnersFinder) }
let(:args) do
{
status: 'active',
type: :group_type,
tag_list: ['active_runner'],
search: 'abc',
sort: :contacted_asc,
membership: :descendants
}
end
let(:expected_params) do
{
status_status: 'active',
type_type: :group_type,
tag_name: ['active_runner'],
preload: { tag_name: nil },
search: 'abc',
sort: 'contacted_asc',
membership: :descendants,
group: group
}
end
it 'calls RunnersFinder with expected arguments' do
allow(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
allow(finder).to receive(:execute).once.and_return([:execute_return_value])
expect(subject.items.to_a).to eq([:execute_return_value])
end
end
end
end
...@@ -5,185 +5,70 @@ require 'spec_helper' ...@@ -5,185 +5,70 @@ require 'spec_helper'
RSpec.describe Resolvers::Ci::RunnersResolver do RSpec.describe Resolvers::Ci::RunnersResolver do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:user) { create_default(:user, :admin) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:inactive_project_runner) do
create(:ci_runner, :project, projects: [project], description: 'inactive project runner', token: 'abcdef', active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner))
end
let_it_be(:offline_project_runner) do
create(:ci_runner, :project, projects: [project], description: 'offline project runner', token: 'defghi', contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner))
end
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 1.second.ago) }
let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
describe '#resolve' do describe '#resolve' do
subject { resolve(described_class, ctx: { current_user: user }, args: args).items.to_a } let(:obj) { nil }
let(:args) { {} }
let(:args) do
{}
end
context 'when the user cannot see runners' do
let(:user) { create(:user) }
it 'returns no runners' do
is_expected.to be_empty
end
end
context 'without sort' do
it 'returns all the runners' do
is_expected.to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, instance_runner)
end
end
context 'with a sort argument' do
context "set to :contacted_asc" do
let(:args) do
{ sort: :contacted_asc }
end
it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner]) }
end
context "set to :contacted_desc" do
let(:args) do
{ sort: :contacted_desc }
end
it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner].reverse) }
end
context "set to :created_at_desc" do
let(:args) do
{ sort: :created_at_desc }
end
it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner]) } subject { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) }
end
context "set to :created_at_asc" do
let(:args) do
{ sort: :created_at_asc }
end
it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner].reverse) }
end
end
context 'when type is filtered' do include_context 'runners resolver setup'
let(:args) do
{ type: runner_type.to_s }
end
context 'to instance runners' do # First, we can do a couple of basic real tests to verify common cases. That ensures that the code works.
let(:runner_type) { :instance_type } context 'when user cannot see runners' do
let(:user) { build(:user) }
it 'returns the instance runner' do it 'returns no runners' do
is_expected.to contain_exactly(instance_runner) expect(subject.items.to_a).to eq([])
end
end
context 'to group runners' do
let(:runner_type) { :group_type }
it 'returns the group runner' do
is_expected.to contain_exactly(group_runner)
end
end
context 'to project runners' do
let(:runner_type) { :project_type }
it 'returns the project runner' do
is_expected.to contain_exactly(inactive_project_runner, offline_project_runner)
end
end end
end end
context 'when status is filtered' do context 'when user can see runners' do
let(:args) do let(:obj) { nil }
{ status: runner_status.to_s }
end
context 'to active runners' do
let(:runner_status) { :active }
it 'returns the instance and group runners' do
is_expected.to contain_exactly(offline_project_runner, group_runner, instance_runner)
end
end
context 'to offline runners' do
let(:runner_status) { :offline }
it 'returns the offline project runner' do it 'returns all the runners' do
is_expected.to contain_exactly(offline_project_runner) expect(subject.items.to_a).to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, subgroup_runner, instance_runner)
end
end end
end end
context 'when tag list is filtered' do # Then, we can check specific edge cases for this resolver
let(:args) do context 'with obj not set to nil' do
{ tag_list: tag_list } let(:obj) { build(:project) }
end
context 'with "project_runner" tag' do
let(:tag_list) { ['project_runner'] }
it 'returns the project_runner runners' do it 'raises an error' do
is_expected.to contain_exactly(offline_project_runner, inactive_project_runner) expect { subject }.to raise_error(a_string_including('Unexpected parent type'))
end
end
context 'with "project_runner" and "active_runner" tags as comma-separated string' do
let(:tag_list) { ['project_runner,active_runner'] }
it 'returns the offline_project_runner runner' do
is_expected.to contain_exactly(offline_project_runner)
end
end
context 'with "active_runner" and "instance_runner" tags as array' do
let(:tag_list) { %w[instance_runner active_runner] }
it 'returns the offline_project_runner runner' do
is_expected.to contain_exactly(instance_runner)
end
end end
end end
context 'when text is filtered' do # Here we have a mocked part. We assume that all possible edge cases are covered in RunnersFinder spec. So we don't need to test them twice.
# Only thing we can do is to verify that args from the resolver is correctly transformed to params of the Finder and we return the Finder's result back.
describe 'Allowed query arguments' do
let(:finder) { instance_double(::Ci::RunnersFinder) }
let(:args) do let(:args) do
{ search: search_term } {
end status: 'active',
type: :instance_type,
context 'to "project"' do tag_list: ['active_runner'],
let(:search_term) { 'project' } search: 'abc',
sort: :contacted_asc
it 'returns both project runners' do }
is_expected.to contain_exactly(inactive_project_runner, offline_project_runner) end
end
end let(:expected_params) do
{
context 'to "group"' do status_status: 'active',
let(:search_term) { 'group' } type_type: :instance_type,
tag_name: ['active_runner'],
it 'returns group runner' do preload: { tag_name: nil },
is_expected.to contain_exactly(group_runner) search: 'abc',
end sort: 'contacted_asc'
end }
end
context 'to "defghi"' do
let(:search_term) { 'defghi' } it 'calls RunnersFinder with expected arguments' do
allow(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
it 'returns runners containing term in token' do allow(finder).to receive(:execute).once.and_return([:execute_return_value])
is_expected.to contain_exactly(offline_project_runner)
end expect(subject.items.to_a).to eq([:execute_return_value])
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_context 'runners resolver setup' do
let_it_be(:user) { create_default(:user, :admin) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:subgroup) { create(:group, :public, parent: group) }
let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:inactive_project_runner) do
create(:ci_runner, :project, projects: [project], description: 'inactive project runner', token: 'abcdef', active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner))
end
let_it_be(:offline_project_runner) do
create(:ci_runner, :project, projects: [project], description: 'offline project runner', token: 'defghi', contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner))
end
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 2.seconds.ago) }
let_it_be(:subgroup_runner) { create(:ci_runner, :group, groups: [subgroup], token: 'mnopqr', description: 'subgroup runner', contacted_at: 1.second.ago) }
let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
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