diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 16:49:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-20 16:49:51 +0300 |
commit | 71786ddc8e28fbd3cb3fcc4b3ff15e5962a1c82e (patch) | |
tree | 6a2d93ef3fb2d353bb7739e4b57e6541f51cdd71 /spec/graphql | |
parent | a7253423e3403b8c08f8a161e5937e1488f5f407 (diff) |
Add latest changes from gitlab-org/gitlab@15-9-stable-eev15.9.0-rc42
Diffstat (limited to 'spec/graphql')
45 files changed, 783 insertions, 276 deletions
diff --git a/spec/graphql/mutations/achievements/create_spec.rb b/spec/graphql/mutations/achievements/create_spec.rb index 4bad6164314..12b8ff67549 100644 --- a/spec/graphql/mutations/achievements/create_spec.rb +++ b/spec/graphql/mutations/achievements/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::Achievements::Create, feature_category: :users do +RSpec.describe Mutations::Achievements::Create, feature_category: :user_profile do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/graphql/mutations/ci/job_token_scope/add_project_spec.rb b/spec/graphql/mutations/ci/job_token_scope/add_project_spec.rb index 727db7e2361..44147987ebb 100644 --- a/spec/graphql/mutations/ci/job_token_scope/add_project_spec.rb +++ b/spec/graphql/mutations/ci/job_token_scope/add_project_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Mutations::Ci::JobTokenScope::AddProject do +RSpec.describe Mutations::Ci::JobTokenScope::AddProject, feature_category: :continuous_integration do let(:mutation) do described_class.new(object: nil, context: { current_user: current_user }, field: nil) end @@ -14,9 +14,10 @@ RSpec.describe Mutations::Ci::JobTokenScope::AddProject do let_it_be(:target_project) { create(:project) } let(:target_project_path) { target_project.full_path } + let(:mutation_args) { { project_path: project.full_path, target_project_path: target_project_path } } subject do - mutation.resolve(project_path: project.full_path, target_project_path: target_project_path) + mutation.resolve(**mutation_args) end context 'when user is not logged in' do @@ -42,18 +43,45 @@ RSpec.describe Mutations::Ci::JobTokenScope::AddProject do target_project.add_guest(current_user) end - it 'adds target project to the job token scope' do + it 'adds target project to the outbound job token scope by default' do expect do expect(subject).to include(ci_job_token_scope: be_present, errors: be_empty) - end.to change { Ci::JobToken::ProjectScopeLink.count }.by(1) + end.to change { Ci::JobToken::ProjectScopeLink.outbound.count }.by(1) + end + + context 'when mutation uses the direction argument' do + let(:mutation_args) { super().merge!(direction: direction) } + + context 'when targeting the outbound allowlist' do + let(:direction) { :outbound } + + it 'adds the target project' do + expect do + expect(subject).to include(ci_job_token_scope: be_present, errors: be_empty) + end.to change { Ci::JobToken::ProjectScopeLink.outbound.count }.by(1) + end + end + + context 'when targeting the inbound allowlist' do + let(:direction) { :inbound } + + it 'adds the target project' do + expect do + expect(subject).to include(ci_job_token_scope: be_present, errors: be_empty) + end.to change { Ci::JobToken::ProjectScopeLink.inbound.count }.by(1) + end + end end context 'when the service returns an error' do let(:service) { double(:service) } it 'returns an error response' do - expect(::Ci::JobTokenScope::AddProjectService).to receive(:new).with(project, current_user).and_return(service) - expect(service).to receive(:execute).with(target_project).and_return(ServiceResponse.error(message: 'The error message')) + expect(::Ci::JobTokenScope::AddProjectService).to receive(:new).with( + project, + current_user + ).and_return(service) + expect(service).to receive(:execute).with(target_project, direction: :outbound).and_return(ServiceResponse.error(message: 'The error message')) expect(subject.fetch(:ci_job_token_scope)).to be_nil expect(subject.fetch(:errors)).to include("The error message") diff --git a/spec/graphql/mutations/ci/job_token_scope/remove_project_spec.rb b/spec/graphql/mutations/ci/job_token_scope/remove_project_spec.rb index d399e73f394..5385b6ca1cf 100644 --- a/spec/graphql/mutations/ci/job_token_scope/remove_project_spec.rb +++ b/spec/graphql/mutations/ci/job_token_scope/remove_project_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe Mutations::Ci::JobTokenScope::RemoveProject do +RSpec.describe Mutations::Ci::JobTokenScope::RemoveProject, feature_category: :continuous_integration do let(:mutation) do described_class.new(object: nil, context: { current_user: current_user }, field: nil) end @@ -17,6 +17,7 @@ RSpec.describe Mutations::Ci::JobTokenScope::RemoveProject do end let(:target_project_path) { target_project.full_path } + let(:links_relation) { Ci::JobToken::ProjectScopeLink.with_source(project).with_target(target_project) } subject do mutation.resolve(project_path: project.full_path, target_project_path: target_project_path) @@ -45,18 +46,40 @@ RSpec.describe Mutations::Ci::JobTokenScope::RemoveProject do target_project.add_guest(current_user) end - it 'removes target project from the job token scope' do - expect do - expect(subject).to include(ci_job_token_scope: be_present, errors: be_empty) - end.to change { Ci::JobToken::ProjectScopeLink.count }.by(-1) + let(:service) { instance_double('Ci::JobTokenScope::RemoveProjectService') } + + context 'with no direction specified' do + it 'defaults to asking the RemoveProjectService to remove the outbound link' do + expect(::Ci::JobTokenScope::RemoveProjectService) + .to receive(:new).with(project, current_user).and_return(service) + expect(service).to receive(:execute).with(target_project, :outbound) + .and_return(instance_double('ServiceResponse', "success?": true)) + + subject + end + end + + context 'with direction specified' do + subject do + mutation.resolve(project_path: project.full_path, target_project_path: target_project_path, direction: 'inbound') + end + + it 'executes project removal for the correct direction' do + expect(::Ci::JobTokenScope::RemoveProjectService) + .to receive(:new).with(project, current_user).and_return(service) + expect(service).to receive(:execute).with(target_project, 'inbound') + .and_return(instance_double('ServiceResponse', "success?": true)) + + subject + end end context 'when the service returns an error' do - let(:service) { double(:service) } + let(:service) { instance_double('Ci::JobTokenScope::RemoveProjectService') } it 'returns an error response' do expect(::Ci::JobTokenScope::RemoveProjectService).to receive(:new).with(project, current_user).and_return(service) - expect(service).to receive(:execute).with(target_project).and_return(ServiceResponse.error(message: 'The error message')) + expect(service).to receive(:execute).with(target_project, :outbound).and_return(ServiceResponse.error(message: 'The error message')) expect(subject.fetch(:ci_job_token_scope)).to be_nil expect(subject.fetch(:errors)).to include("The error message") diff --git a/spec/graphql/mutations/ci/pipeline_schedule/variable_input_type_spec.rb b/spec/graphql/mutations/ci/pipeline_schedule/variable_input_type_spec.rb new file mode 100644 index 00000000000..564bc95b352 --- /dev/null +++ b/spec/graphql/mutations/ci/pipeline_schedule/variable_input_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Ci::PipelineSchedule::VariableInputType, feature_category: :continuous_integration do + specify { expect(described_class.graphql_name).to eq('PipelineScheduleVariableInput') } + + it { expect(described_class.arguments.keys).to match_array(%w[key value variableType]) } +end diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb index bb57ad4c404..324f225f209 100644 --- a/spec/graphql/mutations/issues/update_spec.rb +++ b/spec/graphql/mutations/issues/update_spec.rb @@ -46,10 +46,12 @@ RSpec.describe Mutations::Issues::Update do project.add_developer(user) end - it 'updates issue with correct values' do - subject + context 'when all attributes except timeEstimate are provided' do + it 'updates issue with correct values' do + subject - expect(issue.reload).to have_attributes(expected_attributes) + expect(issue.reload).to have_attributes(expected_attributes) + end end context 'when iid does not exist' do @@ -162,6 +164,39 @@ RSpec.describe Mutations::Issues::Update do expect { subject }.to change { issue.reload.issue_type }.from('issue').to('incident') end end + + context 'when timeEstimate attribute is provided' do + let_it_be_with_refind(:issue) { create(:issue, project: project, time_estimate: 3600) } + + let(:time_estimate) { '0' } + let(:expected_attributes) { { time_estimate: time_estimate } } + + context 'when timeEstimate is invalid' do + let(:time_estimate) { '1e' } + + it 'raises an argument error and changes are not applied' do + expect { mutation.ready?(time_estimate: time_estimate) } + .to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'timeEstimate must be formatted correctly, for example `1h 30m`') + expect { subject }.not_to change { issue.reload.time_estimate } + end + end + + context 'when timeEstimate is 0' do + let(:time_estimate) { '0' } + + it 'resets the time estimate' do + expect { subject }.to change { issue.reload.time_estimate }.from(3600).to(0) + end + end + + context 'when timeEstimate is a valid human readable time' do + let(:time_estimate) { '1h 30m' } + + it 'updates the time estimate' do + expect { subject }.to change { issue.reload.time_estimate }.from(3600).to(5400) + end + end + end end end end diff --git a/spec/graphql/mutations/merge_requests/update_spec.rb b/spec/graphql/mutations/merge_requests/update_spec.rb index 206abaf34ce..8a10f6cadd0 100644 --- a/spec/graphql/mutations/merge_requests/update_spec.rb +++ b/spec/graphql/mutations/merge_requests/update_spec.rb @@ -26,10 +26,56 @@ RSpec.describe Mutations::MergeRequests::Update do merge_request.project.add_developer(user) end - it 'applies all attributes' do - expect(mutated_merge_request).to eq(merge_request) - expect(mutated_merge_request).to have_attributes(attributes) - expect(subject[:errors]).to be_empty + context 'when all attributes except timeEstimate are provided' do + before do + merge_request.update!(time_estimate: 3600) + end + + it 'applies all attributes' do + expect(mutated_merge_request).to eq(merge_request) + expect(mutated_merge_request).to have_attributes(attributes) + expect(mutated_merge_request.time_estimate).to eq(3600) + expect(subject[:errors]).to be_empty + end + end + + context 'when timeEstimate attribute is provided' do + let(:time_estimate) { '0' } + let(:attributes) { { time_estimate: time_estimate } } + + before do + merge_request.update!(time_estimate: 3600) + end + + context 'when timeEstimate is invalid' do + let(:time_estimate) { '1e' } + + it 'changes are not applied' do + expect { mutation.ready?(time_estimate: time_estimate) } + .to raise_error( + Gitlab::Graphql::Errors::ArgumentError, + 'timeEstimate must be formatted correctly, for example `1h 30m`') + expect(mutated_merge_request.time_estimate).to eq(3600) + end + end + + context 'when timeEstimate is 0' do + let(:time_estimate) { '0' } + + it 'resets the time estimate' do + expect(mutated_merge_request.time_estimate).to eq(0) + expect(subject[:errors]).to be_empty + end + end + + context 'when timeEstimate is a valid human readable time' do + let(:time_estimate) { '1h 30m' } + + it 'updates the time estimate' do + expect(mutated_merge_request.time_estimate).to eq(5400) + expect(subject[:errors]).to be_empty + end + end end context 'the merge request is invalid' do @@ -82,4 +128,53 @@ RSpec.describe Mutations::MergeRequests::Update do end end end + + describe '#ready?' do + let(:extra_args) { {} } + + let(:arguments) do + { + project_path: merge_request.project.full_path, + iid: merge_request.iid + }.merge(extra_args) + end + + subject(:ready) { mutation.ready?(**arguments) } + + context 'when required arguments are not provided' do + let(:arguments) { {} } + + it 'raises an argument error' do + expect { subject }.to raise_error(ArgumentError, 'Arguments must be provided: projectPath, iid') + end + end + + context 'when required arguments are provided' do + it 'returns true' do + expect(subject).to eq(true) + end + end + + context 'when timeEstimate is provided' do + let(:extra_args) { { time_estimate: time_estimate } } + + context 'when the value is invalid' do + let(:time_estimate) { '1e' } + + it 'raises an argument error' do + expect { subject }.to raise_error( + Gitlab::Graphql::Errors::ArgumentError, + 'timeEstimate must be formatted correctly, for example `1h 30m`') + end + end + + context 'when the value valid' do + let(:time_estimate) { '1d' } + + it 'returns true' do + expect(subject).to eq(true) + end + end + end + end end diff --git a/spec/graphql/mutations/saved_replies/create_spec.rb b/spec/graphql/mutations/saved_replies/create_spec.rb index 5141c537b06..9423ba2b354 100644 --- a/spec/graphql/mutations/saved_replies/create_spec.rb +++ b/spec/graphql/mutations/saved_replies/create_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Mutations::SavedReplies::Create do let(:mutation_arguments) { { name: '', content: '' } } it { expect(subject[:saved_reply]).to be_nil } - it { expect(subject[:errors]).to match_array(["Content can't be blank", "Name can't be blank", "Name can contain only lowercase letters, digits, '_' and '-'. Must start with a letter, and cannot end with '-' or '_'"]) } + it { expect(subject[:errors]).to match_array(["Content can't be blank", "Name can't be blank"]) } end context 'when service successfully creates a new saved reply' do diff --git a/spec/graphql/mutations/saved_replies/update_spec.rb b/spec/graphql/mutations/saved_replies/update_spec.rb index 67c2d1348f7..9b0e90b7b41 100644 --- a/spec/graphql/mutations/saved_replies/update_spec.rb +++ b/spec/graphql/mutations/saved_replies/update_spec.rb @@ -34,7 +34,7 @@ RSpec.describe Mutations::SavedReplies::Update do let(:mutation_arguments) { { name: '', content: '' } } it { expect(subject[:saved_reply]).to be_nil } - it { expect(subject[:errors]).to match_array(["Content can't be blank", "Name can't be blank", "Name can contain only lowercase letters, digits, '_' and '-'. Must start with a letter, and cannot end with '-' or '_'"]) } + it { expect(subject[:errors]).to match_array(["Content can't be blank", "Name can't be blank"]) } end context 'when service successfully updates the saved reply' do diff --git a/spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb b/spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb index 59ece15b745..92f4d3dd8e8 100644 --- a/spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb @@ -23,18 +23,18 @@ RSpec.describe Resolvers::Ci::JobTokenScopeResolver do it 'returns the same project in the allow list of projects for the Ci Job Token when scope is not enabled' do allow(project).to receive(:ci_outbound_job_token_scope_enabled?).and_return(false) - expect(resolve_scope.all_projects).to contain_exactly(project) + expect(resolve_scope.outbound_projects).to contain_exactly(project) end it 'returns the same project in the allow list of projects for the Ci Job Token' do - expect(resolve_scope.all_projects).to contain_exactly(project) + expect(resolve_scope.outbound_projects).to contain_exactly(project) end context 'when another projects gets added to the allow list' do let!(:link) { create(:ci_job_token_project_scope_link, source_project: project) } it 'returns both projects' do - expect(resolve_scope.all_projects).to contain_exactly(project, link.target_project) + expect(resolve_scope.outbound_projects).to contain_exactly(project, link.target_project) end end @@ -44,7 +44,7 @@ RSpec.describe Resolvers::Ci::JobTokenScopeResolver do end it 'resolves projects' do - expect(resolve_scope.all_projects).to contain_exactly(project) + expect(resolve_scope.outbound_projects).to contain_exactly(project) end end end diff --git a/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb b/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb index 1d1fb4a9967..da6a84cec44 100644 --- a/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Resolvers::Ci::RunnerPlatformsResolver, feature_category: :runner expect(resolve_subject).to contain_exactly( hash_including(name: :linux), hash_including(name: :osx), hash_including(name: :windows), hash_including(name: :docker), - hash_including(name: :kubernetes) + hash_including(name: :kubernetes), hash_including(name: :aws) ) end end diff --git a/spec/graphql/resolvers/ci/variables_resolver_spec.rb b/spec/graphql/resolvers/ci/variables_resolver_spec.rb new file mode 100644 index 00000000000..16b72e8cb7f --- /dev/null +++ b/spec/graphql/resolvers/ci/variables_resolver_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::VariablesResolver, feature_category: :pipeline_authoring do + include GraphqlHelpers + + describe '#resolve' do + let_it_be(:user) { create(:user) } + let_it_be(:args) { {} } + let_it_be(:obj) { nil } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project) } + + let_it_be(:ci_instance_variables) do + [ + create(:ci_instance_variable, key: 'a'), + create(:ci_instance_variable, key: 'b') + ] + end + + let_it_be(:ci_group_variables) do + [ + create(:ci_group_variable, group: group, key: 'a'), + create(:ci_group_variable, group: group, key: 'b') + ] + end + + let_it_be(:ci_project_variables) do + [ + create(:ci_variable, project: project, key: 'a'), + create(:ci_variable, project: project, key: 'b') + ] + end + + subject(:resolve_variables) { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) } + + context 'when parent object is nil' do + context 'when user is authorized', :enable_admin_mode do + let_it_be(:user) { create(:admin) } + + it "returns the instance's variables" do + expect(resolve_variables.items.to_a).to match_array(ci_instance_variables) + end + end + + context 'when user is not authorized' do + it "returns nil" do + expect(resolve_variables).to be_nil + end + end + end + + context 'when parent object is a Group' do + let_it_be(:obj) { group } + + it "returns the group's variables" do + expect(resolve_variables.items.to_a).to match_array(ci_group_variables) + end + end + + context 'when parent object is a Project' do + let_it_be(:obj) { project } + + it "returns the project's variables" do + expect(resolve_variables.items.to_a).to match_array(ci_project_variables) + end + end + end +end diff --git a/spec/graphql/resolvers/data_transfer_resolver_spec.rb b/spec/graphql/resolvers/data_transfer_resolver_spec.rb new file mode 100644 index 00000000000..f5a088dc1c3 --- /dev/null +++ b/spec/graphql/resolvers/data_transfer_resolver_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::DataTransferResolver, feature_category: :source_code_management do + include GraphqlHelpers + + describe '.source' do + context 'with base DataTransferResolver' do + it 'raises NotImplementedError' do + expect { described_class.source }.to raise_error ::NotImplementedError + end + end + + context 'with projects DataTransferResolver' do + let(:source) { described_class.project.source } + + it 'outputs "Project"' do + expect(source).to eq 'Project' + end + end + + context 'with groups DataTransferResolver' do + let(:source) { described_class.group.source } + + it 'outputs "Group"' do + expect(source).to eq 'Group' + end + end + end +end diff --git a/spec/graphql/resolvers/group_releases_resolver_spec.rb b/spec/graphql/resolvers/group_releases_resolver_spec.rb new file mode 100644 index 00000000000..73386870030 --- /dev/null +++ b/spec/graphql/resolvers/group_releases_resolver_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::GroupReleasesResolver, feature_category: :release_orchestration do + include GraphqlHelpers + + let_it_be(:today) { Time.now } + let_it_be(:yesterday) { today - 1.day } + let_it_be(:tomorrow) { today + 1.day } + + let_it_be(:group) { create(:group, :private) } + let_it_be(:project) { create(:project, :private, namespace: group) } + let_it_be(:release_v1) do + create(:release, project: project, tag: 'v1.0.0', released_at: yesterday, created_at: tomorrow) + end + + let_it_be(:release_v2) do + create(:release, project: project, tag: 'v2.0.0', released_at: today, created_at: yesterday) + end + + let_it_be(:release_v3) do + create(:release, project: project, tag: 'v3.0.0', released_at: tomorrow, created_at: today) + end + + let_it_be(:developer) { create(:user) } + let_it_be(:public_user) { create(:user) } + + let(:args) { { sort: :released_at_desc } } + let(:all_releases) { [release_v1, release_v2, release_v3] } + + before do + group.add_member(developer, :owner) + project.add_developer(developer) + end + + describe '#resolve' do + it_behaves_like 'releases and group releases resolver' + end + + private + + def resolve_releases + context = { current_user: current_user } + resolve(described_class, obj: group, args: args, ctx: context, arg_style: :internal) + end +end diff --git a/spec/graphql/resolvers/groups_resolver_spec.rb b/spec/graphql/resolvers/groups_resolver_spec.rb index e53ca674163..9d1ad46ed0e 100644 --- a/spec/graphql/resolvers/groups_resolver_spec.rb +++ b/spec/graphql/resolvers/groups_resolver_spec.rb @@ -2,131 +2,42 @@ require 'spec_helper' -RSpec.describe Resolvers::GroupsResolver do +RSpec.describe Resolvers::GroupsResolver, feature_category: :subgroups do include GraphqlHelpers describe '#resolve' do - let_it_be(:group) { create(:group, name: 'public-group') } - let_it_be(:private_group) { create(:group, :private, name: 'private-group') } - let_it_be(:subgroup1) { create(:group, parent: group, name: 'Subgroup') } - let_it_be(:subgroup2) { create(:group, parent: subgroup1, name: 'Test Subgroup 2') } - let_it_be(:private_subgroup1) { create(:group, :private, parent: private_group, name: 'Subgroup1') } - let_it_be(:private_subgroup2) { create(:group, :private, parent: private_subgroup1, name: 'Subgroup2') } let_it_be(:user) { create(:user) } + let_it_be(:public_group) { create(:group, name: 'public-group') } + let_it_be(:private_group) { create(:group, :private, name: 'private-group') } - before_all do - private_group.add_developer(user) - end + let(:params) { {} } - shared_examples 'access to all public descendant groups' do - it 'returns all public descendant groups of the parent group ordered by ASC name' do - is_expected.to eq([subgroup1, subgroup2]) - end - end + subject { resolve(described_class, args: params, ctx: { current_user: user }) } - shared_examples 'access to all public subgroups' do - it 'returns all public subgroups of the parent group' do - is_expected.to contain_exactly(subgroup1) - end + it 'includes public groups' do + expect(subject).to contain_exactly(public_group) end - shared_examples 'returning empty results' do - it 'returns empty results' do - is_expected.to be_empty - end + it 'includes accessible private groups' do + private_group.add_developer(user) + expect(subject).to contain_exactly(public_group, private_group) end - context 'when parent group is public' do - subject { resolve(described_class, obj: group, args: params, ctx: { current_user: current_user }) } - - context 'when `include_parent_descendants` is false' do - let(:params) { { include_parent_descendants: false } } - - context 'when user is not logged in' do - let(:current_user) { nil } - - it_behaves_like 'access to all public subgroups' - end + describe 'ordering' do + let_it_be(:other_group) { create(:group, name: 'other-group') } - context 'when user is logged in' do - let(:current_user) { user } - - it_behaves_like 'access to all public subgroups' - end - end - - context 'when `include_parent_descendants` is true' do - let(:params) { { include_parent_descendants: true } } - - context 'when user is not logged in' do - let(:current_user) { nil } - - it_behaves_like 'access to all public descendant groups' - end - - context 'when user is logged in' do - let(:current_user) { user } - - it_behaves_like 'access to all public descendant groups' - - context 'with owned argument set as true' do - before do - subgroup1.add_owner(current_user) - params[:owned] = true - end - - it 'returns only descendant groups owned by the user' do - is_expected.to contain_exactly(subgroup1) - end - end - - context 'with search argument' do - it 'returns only descendant groups with matching name or path' do - params[:search] = 'Test' - is_expected.to contain_exactly(subgroup2) - end - end - end + it 'orders by name ascending' do + expect(subject.map(&:name)).to eq(%w[other-group public-group]) end end - context 'when parent group is private' do - subject { resolve(described_class, obj: private_group, args: params, ctx: { current_user: current_user }) } - - context 'when `include_parent_descendants` is true' do - let(:params) { { include_parent_descendants: true } } - - context 'when user is not logged in' do - let(:current_user) { nil } - - it_behaves_like 'returning empty results' - end - - context 'when user is logged in' do - let(:current_user) { user } - - it 'returns all private descendant groups' do - is_expected.to contain_exactly(private_subgroup1, private_subgroup2) - end - end - end - - context 'when `include_parent_descendants` is false' do - let(:params) { { include_parent_descendants: false } } - - context 'when user is not logged in' do - let(:current_user) { nil } - - it_behaves_like 'returning empty results' - end + context 'with `search` argument' do + let_it_be(:other_group) { create(:group, name: 'other-group') } - context 'when user is logged in' do - let(:current_user) { user } + let(:params) { { search: 'oth' } } - it 'returns private subgroups' do - is_expected.to contain_exactly(private_subgroup1) - end - end + it 'filters groups by name' do + expect(subject).to contain_exactly(other_group) end end end diff --git a/spec/graphql/resolvers/nested_groups_resolver_spec.rb b/spec/graphql/resolvers/nested_groups_resolver_spec.rb new file mode 100644 index 00000000000..e58edc3fd4b --- /dev/null +++ b/spec/graphql/resolvers/nested_groups_resolver_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::NestedGroupsResolver, feature_category: :subgroups do + include GraphqlHelpers + + describe '#resolve' do + let_it_be(:group) { create(:group, name: 'public-group') } + let_it_be(:private_group) { create(:group, :private, name: 'private-group') } + let_it_be(:subgroup1) { create(:group, parent: group, name: 'Subgroup') } + let_it_be(:subgroup2) { create(:group, parent: subgroup1, name: 'Test Subgroup 2') } + let_it_be(:private_subgroup1) { create(:group, :private, parent: private_group, name: 'Subgroup1') } + let_it_be(:private_subgroup2) { create(:group, :private, parent: private_subgroup1, name: 'Subgroup2') } + let_it_be(:user) { create(:user) } + + before_all do + private_group.add_developer(user) + end + + shared_examples 'access to all public descendant groups' do + it 'returns all public descendant groups of the parent group ordered by ASC name' do + is_expected.to eq([subgroup1, subgroup2]) + end + end + + shared_examples 'access to all public subgroups' do + it 'returns all public subgroups of the parent group' do + is_expected.to contain_exactly(subgroup1) + end + end + + shared_examples 'returning empty results' do + it 'returns empty results' do + is_expected.to be_empty + end + end + + context 'when parent group is public' do + subject { resolve(described_class, obj: group, args: params, ctx: { current_user: current_user }) } + + context 'when `include_parent_descendants` is false' do + let(:params) { { include_parent_descendants: false } } + + context 'when user is not logged in' do + let(:current_user) { nil } + + it_behaves_like 'access to all public subgroups' + end + + context 'when user is logged in' do + let(:current_user) { user } + + it_behaves_like 'access to all public subgroups' + end + end + + context 'when `include_parent_descendants` is true' do + let(:params) { { include_parent_descendants: true } } + + context 'when user is not logged in' do + let(:current_user) { nil } + + it_behaves_like 'access to all public descendant groups' + end + + context 'when user is logged in' do + let(:current_user) { user } + + it_behaves_like 'access to all public descendant groups' + + context 'with owned argument set as true' do + before do + subgroup1.add_owner(current_user) + params[:owned] = true + end + + it 'returns only descendant groups owned by the user' do + is_expected.to contain_exactly(subgroup1) + end + end + + context 'with search argument' do + it 'returns only descendant groups with matching name or path' do + params[:search] = 'Test' + is_expected.to contain_exactly(subgroup2) + end + end + end + end + end + + context 'when parent group is private' do + subject { resolve(described_class, obj: private_group, args: params, ctx: { current_user: current_user }) } + + context 'when `include_parent_descendants` is true' do + let(:params) { { include_parent_descendants: true } } + + context 'when user is not logged in' do + let(:current_user) { nil } + + it_behaves_like 'returning empty results' + end + + context 'when user is logged in' do + let(:current_user) { user } + + it 'returns all private descendant groups' do + is_expected.to contain_exactly(private_subgroup1, private_subgroup2) + end + end + end + + context 'when `include_parent_descendants` is false' do + let(:params) { { include_parent_descendants: false } } + + context 'when user is not logged in' do + let(:current_user) { nil } + + it_behaves_like 'returning empty results' + end + + context 'when user is logged in' do + let(:current_user) { user } + + it 'returns private subgroups' do + is_expected.to contain_exactly(private_subgroup1) + end + end + end + end + end +end diff --git a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb index b95bab41e3e..6af2f56cef4 100644 --- a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Resolvers::Projects::JiraProjectsResolver do +RSpec.describe Resolvers::Projects::JiraProjectsResolver, feature_category: :integrations do include GraphqlHelpers specify do diff --git a/spec/graphql/resolvers/releases_resolver_spec.rb b/spec/graphql/resolvers/releases_resolver_spec.rb index 6ba9a6c33a1..58f6257c946 100644 --- a/spec/graphql/resolvers/releases_resolver_spec.rb +++ b/spec/graphql/resolvers/releases_resolver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Resolvers::ReleasesResolver do +RSpec.describe Resolvers::ReleasesResolver, feature_category: :release_orchestration do include GraphqlHelpers let_it_be(:today) { Time.now } @@ -24,60 +24,28 @@ RSpec.describe Resolvers::ReleasesResolver do end describe '#resolve' do - context 'when the user does not have access to the project' do - let(:current_user) { public_user } + it_behaves_like 'releases and group releases resolver' - it 'returns an empty array' do - expect(resolve_releases).to be_empty - end - end - - context "when the user has full access to the project's releases" do + describe 'when order_by is created_at' do let(:current_user) { developer } - it 'returns all releases associated to the project' do - expect(resolve_releases).to match_array(all_releases) - end - - describe 'sorting behavior' do - context 'with sort: :released_at_desc' do - let(:args) { { sort: :released_at_desc } } - - it 'returns the releases ordered by released_at in descending order' do - expect(resolve_releases.to_a) - .to match_array(all_releases) - .and be_sorted(:released_at, :desc) - end - end - - context 'with sort: :released_at_asc' do - let(:args) { { sort: :released_at_asc } } - - it 'returns the releases ordered by released_at in ascending order' do - expect(resolve_releases.to_a) - .to match_array(all_releases) - .and be_sorted(:released_at, :asc) - end - end - - context 'with sort: :created_desc' do - let(:args) { { sort: :created_desc } } + context 'with sort: desc' do + let(:args) { { sort: :created_desc } } - it 'returns the releases ordered by created_at in descending order' do - expect(resolve_releases.to_a) - .to match_array(all_releases) - .and be_sorted(:created_at, :desc) - end + it 'returns the releases ordered by created_at in descending order' do + expect(resolve_releases.to_a) + .to match_array(all_releases) + .and be_sorted(:created_at, :desc) end + end - context 'with sort: :created_asc' do - let(:args) { { sort: :created_asc } } + context 'with sort: asc' do + let(:args) { { sort: :created_asc } } - it 'returns the releases ordered by created_at in ascending order' do - expect(resolve_releases.to_a) - .to match_array(all_releases) - .and be_sorted(:created_at, :asc) - end + it 'returns the releases ordered by created_at in ascending order' do + expect(resolve_releases.to_a) + .to match_array(all_releases) + .and be_sorted(:created_at, :asc) end end end diff --git a/spec/graphql/resolvers/saved_reply_resolver_spec.rb b/spec/graphql/resolvers/saved_reply_resolver_spec.rb new file mode 100644 index 00000000000..f1cb0ca5214 --- /dev/null +++ b/spec/graphql/resolvers/saved_reply_resolver_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::SavedReplyResolver, feature_category: :user_profile do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:saved_reply) { create(:saved_reply, user: current_user) } + + describe 'feature flag disabled' do + before do + stub_feature_flags(saved_replies: false) + end + + it 'does not return saved reply' do + expect(resolve_saved_reply).to be_nil + end + end + + describe 'feature flag enabled' do + it 'returns users saved reply' do + expect(resolve_saved_reply).to eq(saved_reply) + end + + it 'returns nil when saved reply is not found' do + expect(resolve_saved_reply({ id: 'gid://gitlab/Users::SavedReply/100' })).to be_nil + end + + it 'returns nil when saved reply is another users' do + other_users_saved_reply = create(:saved_reply, user: create(:user)) + + expect(resolve_saved_reply({ id: other_users_saved_reply.to_global_id })).to be_nil + end + end + + def resolve_saved_reply(args = { id: saved_reply.to_global_id }) + resolve(described_class, args: args, ctx: { current_user: current_user }) + end +end diff --git a/spec/graphql/resolvers/users/participants_resolver_spec.rb b/spec/graphql/resolvers/users/participants_resolver_spec.rb index 27c3b9643ce..224213d1521 100644 --- a/spec/graphql/resolvers/users/participants_resolver_spec.rb +++ b/spec/graphql/resolvers/users/participants_resolver_spec.rb @@ -115,8 +115,8 @@ RSpec.describe Resolvers::Users::ParticipantsResolver do create(:award_emoji, name: 'thumbsup', awardable: public_note) # 1 extra query per source (3 emojis + 2 notes) to fetch participables collection - # 1 extra query to load work item widgets collection - expect { query.call }.not_to exceed_query_limit(control_count).with_threshold(6) + # 2 extra queries to load work item widgets collection + expect { query.call }.not_to exceed_query_limit(control_count).with_threshold(7) end it 'does not execute N+1 for system note metadata relation' do diff --git a/spec/graphql/resolvers/work_items_resolver_spec.rb b/spec/graphql/resolvers/work_items_resolver_spec.rb index d89ccc7f806..6da62e3adb7 100644 --- a/spec/graphql/resolvers/work_items_resolver_spec.rb +++ b/spec/graphql/resolvers/work_items_resolver_spec.rb @@ -101,7 +101,7 @@ RSpec.describe Resolvers::WorkItemsResolver do end it 'batches queries that only include IIDs', :request_store do - result = batch_sync(max_queries: 7) do + result = batch_sync(max_queries: 8) do [item1, item2] .map { |item| resolve_items(iid: item.iid.to_s) } .flat_map(&:to_a) @@ -111,7 +111,7 @@ RSpec.describe Resolvers::WorkItemsResolver do end it 'finds a specific item with iids', :request_store do - result = batch_sync(max_queries: 7) do + result = batch_sync(max_queries: 8) do resolve_items(iids: [item1.iid]).to_a end diff --git a/spec/graphql/types/achievements/achievement_type_spec.rb b/spec/graphql/types/achievements/achievement_type_spec.rb index 5c98753ac66..f967dc8e25e 100644 --- a/spec/graphql/types/achievements/achievement_type_spec.rb +++ b/spec/graphql/types/achievements/achievement_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['Achievement'], feature_category: :users do +RSpec.describe GitlabSchema.types['Achievement'], feature_category: :user_profile do include GraphqlHelpers let(:fields) do @@ -12,7 +12,6 @@ RSpec.describe GitlabSchema.types['Achievement'], feature_category: :users do name avatar_url description - revokeable created_at updated_at ] diff --git a/spec/graphql/types/ci/job_token_scope_type_spec.rb b/spec/graphql/types/ci/job_token_scope_type_spec.rb index 569b59d6c70..01044364881 100644 --- a/spec/graphql/types/ci/job_token_scope_type_spec.rb +++ b/spec/graphql/types/ci/job_token_scope_type_spec.rb @@ -2,17 +2,24 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['CiJobTokenScopeType'] do +RSpec.describe GitlabSchema.types['CiJobTokenScopeType'], feature_category: :continuous_integration do specify { expect(described_class.graphql_name).to eq('CiJobTokenScopeType') } it 'has the correct fields' do - expected_fields = [:projects] + expected_fields = [:projects, :inboundAllowlist, :outboundAllowlist] expect(described_class).to have_graphql_fields(*expected_fields) end describe 'query' do - let(:project) { create(:project, ci_outbound_job_token_scope_enabled: true).tap(&:save!) } + let(:project) do + create( + :project, + ci_outbound_job_token_scope_enabled: true, + ci_inbound_job_token_scope_enabled: true + ).tap(&:save!) + end + let_it_be(:current_user) { create(:user) } let(:query) do @@ -25,6 +32,16 @@ RSpec.describe GitlabSchema.types['CiJobTokenScopeType'] do path } } + inboundAllowlist { + nodes { + path + } + } + outboundAllowlist { + nodes { + path + } + } } } } @@ -33,30 +50,73 @@ RSpec.describe GitlabSchema.types['CiJobTokenScopeType'] do subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json } - let(:projects_field) { subject.dig('data', 'project', 'ciJobTokenScope', 'projects', 'nodes') } - let(:returned_project_paths) { projects_field.map { |project| project['path'] } } + let(:scope_field) { subject.dig('data', 'project', 'ciJobTokenScope') } + let(:errors_field) { subject['errors'] } + let(:projects_field) { scope_field&.dig('projects', 'nodes') } + let(:outbound_allowlist_field) { scope_field&.dig('outboundAllowlist', 'nodes') } + let(:inbound_allowlist_field) { scope_field&.dig('inboundAllowlist', 'nodes') } + let(:returned_project_paths) { projects_field.map { |p| p['path'] } } + let(:returned_outbound_paths) { outbound_allowlist_field.map { |p| p['path'] } } + let(:returned_inbound_paths) { inbound_allowlist_field.map { |p| p['path'] } } + + context 'without access to scope' do + before do + project.add_member(current_user, :developer) + end + + it 'returns no projects' do + expect(projects_field).to be_nil + expect(outbound_allowlist_field).to be_nil + expect(inbound_allowlist_field).to be_nil + expect(errors_field.first['message']).to include "don't have permission" + end + end context 'with access to scope' do before do project.add_member(current_user, :maintainer) end - context 'when multiple projects in the allow list' do - let!(:link) { create(:ci_job_token_project_scope_link, source_project: project) } + context 'when multiple projects in the allow lists' do + include Ci::JobTokenScopeHelpers + let!(:outbound_allowlist_project) { create_project_in_allowlist(project, direction: :outbound) } + let!(:inbound_allowlist_project) { create_project_in_allowlist(project, direction: :inbound) } + let!(:both_allowlists_project) { create_project_in_both_allowlists(project) } context 'when linked projects are readable' do before do - link.target_project.add_member(current_user, :developer) + outbound_allowlist_project.add_member(current_user, :developer) + inbound_allowlist_project.add_member(current_user, :developer) + both_allowlists_project.add_member(current_user, :developer) end - it 'returns readable projects in scope' do - expect(returned_project_paths).to contain_exactly(project.path, link.target_project.path) + shared_examples 'returns projects' do + it 'returns readable projects in scope' do + outbound_paths = [project.path, outbound_allowlist_project.path, both_allowlists_project.path] + inbound_paths = [project.path, inbound_allowlist_project.path, both_allowlists_project.path] + + expect(returned_project_paths).to contain_exactly(*outbound_paths) + expect(returned_outbound_paths).to contain_exactly(*outbound_paths) + expect(returned_inbound_paths).to contain_exactly(*inbound_paths) + end + end + + it_behaves_like 'returns projects' + + context 'when job token scope is disabled' do + before do + project.ci_cd_settings.update!(job_token_scope_enabled: false) + end + + it_behaves_like 'returns projects' end end - context 'when linked project is not readable' do + context 'when linked projects are not readable' do it 'returns readable projects in scope' do expect(returned_project_paths).to contain_exactly(project.path) + expect(returned_outbound_paths).to contain_exactly(project.path) + expect(returned_inbound_paths).to contain_exactly(project.path) end end @@ -71,6 +131,8 @@ RSpec.describe GitlabSchema.types['CiJobTokenScopeType'] do it 'returns readable projects in scope' do expect(returned_project_paths).to contain_exactly(project.path) + expect(returned_outbound_paths).to contain_exactly(project.path) + expect(returned_inbound_paths).to contain_exactly(project.path) end end end diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb index ce1558c4097..714eaebfe73 100644 --- a/spec/graphql/types/ci/job_type_spec.rb +++ b/spec/graphql/types/ci/job_type_spec.rb @@ -22,6 +22,7 @@ RSpec.describe Types::Ci::JobType do detailedStatus duration downstreamPipeline + erasedAt finished_at id kind @@ -32,6 +33,7 @@ RSpec.describe Types::Ci::JobType do pipeline playable previousStageJobsOrNeeds + project queued_at queued_duration refName diff --git a/spec/graphql/types/ci/pipeline_schedule_variable_type_spec.rb b/spec/graphql/types/ci/pipeline_schedule_variable_type_spec.rb index 1c98539e308..2e5e6ad204f 100644 --- a/spec/graphql/types/ci/pipeline_schedule_variable_type_spec.rb +++ b/spec/graphql/types/ci/pipeline_schedule_variable_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Types::Ci::PipelineScheduleVariableType do +RSpec.describe Types::Ci::PipelineScheduleVariableType, feature_category: :continuous_integration do specify { expect(described_class.graphql_name).to eq('PipelineScheduleVariable') } specify { expect(described_class.interfaces).to contain_exactly(Types::Ci::VariableInterface) } specify { expect(described_class).to require_graphql_authorizations(:read_pipeline_schedule_variables) } diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb index 5683b3f86c4..67209874b54 100644 --- a/spec/graphql/types/ci/pipeline_type_spec.rb +++ b/spec/graphql/types/ci/pipeline_type_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Types::Ci::PipelineType do if Gitlab.ee? expected_fields += %w[ security_report_summary security_report_findings security_report_finding - code_quality_reports dast_profile + code_quality_reports dast_profile code_quality_report_summary ] end diff --git a/spec/graphql/types/ci/runner_architecture_type_spec.rb b/spec/graphql/types/ci/runner_architecture_type_spec.rb index 60709acfd53..58ec73323c6 100644 --- a/spec/graphql/types/ci/runner_architecture_type_spec.rb +++ b/spec/graphql/types/ci/runner_architecture_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Types::Ci::RunnerArchitectureType do +RSpec.describe Types::Ci::RunnerArchitectureType, feature_category: :runner do specify { expect(described_class.graphql_name).to eq('RunnerArchitecture') } it 'exposes the expected fields' do diff --git a/spec/graphql/types/ci/runner_platform_type_spec.rb b/spec/graphql/types/ci/runner_platform_type_spec.rb index 29b8e885183..1b0f5a5ec71 100644 --- a/spec/graphql/types/ci/runner_platform_type_spec.rb +++ b/spec/graphql/types/ci/runner_platform_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Types::Ci::RunnerPlatformType do +RSpec.describe Types::Ci::RunnerPlatformType, feature_category: :runner_fleet do specify { expect(described_class.graphql_name).to eq('RunnerPlatform') } it 'exposes the expected fields' do diff --git a/spec/graphql/types/ci/runner_setup_type_spec.rb b/spec/graphql/types/ci/runner_setup_type_spec.rb index 197e717e964..d3e47b52a80 100644 --- a/spec/graphql/types/ci/runner_setup_type_spec.rb +++ b/spec/graphql/types/ci/runner_setup_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Types::Ci::RunnerSetupType do +RSpec.describe Types::Ci::RunnerSetupType, feature_category: :runner_fleet do specify { expect(described_class.graphql_name).to eq('RunnerSetup') } it 'exposes the expected fields' do diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb index b078d7f5fac..a2d107ae295 100644 --- a/spec/graphql/types/ci/runner_type_spec.rb +++ b/spec/graphql/types/ci/runner_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['CiRunner'] do +RSpec.describe GitlabSchema.types['CiRunner'], feature_category: :runner do specify { expect(described_class.graphql_name).to eq('CiRunner') } specify { expect(described_class).to require_graphql_authorizations(:read_runner) } @@ -13,6 +13,7 @@ RSpec.describe GitlabSchema.types['CiRunner'] do version short_sha revision locked run_untagged ip_address runner_type tag_list project_count job_count admin_url edit_admin_url user_permissions executor_name architecture_name platform_name maintenance_note maintenance_note_html groups projects jobs token_expires_at owner_project job_execution_status + ephemeral_authentication_token ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/ci/runner_upgrade_status_enum_spec.rb b/spec/graphql/types/ci/runner_upgrade_status_enum_spec.rb index ef378f3fc5a..4aa9ad094a6 100644 --- a/spec/graphql/types/ci/runner_upgrade_status_enum_spec.rb +++ b/spec/graphql/types/ci/runner_upgrade_status_enum_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Types::Ci::RunnerUpgradeStatusEnum do +RSpec.describe Types::Ci::RunnerUpgradeStatusEnum, feature_category: :runner_fleet do let(:model_only_enum_values) { %w[not_processed] } let(:expected_graphql_source_values) do Ci::RunnerVersion.statuses.keys - model_only_enum_values @@ -15,6 +15,7 @@ RSpec.describe Types::Ci::RunnerUpgradeStatusEnum do expected_graphql_source_values .map(&:upcase) .map { |v| v == 'INVALID_VERSION' ? 'INVALID' : v } + .map { |v| v == 'UNAVAILABLE' ? 'NOT_AVAILABLE' : v } ) end diff --git a/spec/graphql/types/ci/runner_web_url_edge_spec.rb b/spec/graphql/types/ci/runner_web_url_edge_spec.rb index 08718df0a5b..07a9655b3e1 100644 --- a/spec/graphql/types/ci/runner_web_url_edge_spec.rb +++ b/spec/graphql/types/ci/runner_web_url_edge_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Types::Ci::RunnerWebUrlEdge do +RSpec.describe Types::Ci::RunnerWebUrlEdge, feature_category: :runner_fleet do specify { expect(described_class.graphql_name).to eq('RunnerWebUrlEdge') } it 'contains URL attributes' do diff --git a/spec/graphql/types/ci/variable_sort_enum_spec.rb b/spec/graphql/types/ci/variable_sort_enum_spec.rb new file mode 100644 index 00000000000..1702360a21f --- /dev/null +++ b/spec/graphql/types/ci/variable_sort_enum_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::VariableSortEnum, feature_category: :pipeline_authoring do + it 'exposes the available order methods' do + expect(described_class.values).to match( + 'KEY_ASC' => have_attributes(value: :key_asc), + 'KEY_DESC' => have_attributes(value: :key_desc) + ) + end +end diff --git a/spec/graphql/types/commit_signatures/verification_status_enum_spec.rb b/spec/graphql/types/commit_signatures/verification_status_enum_spec.rb index cb7ce19c9fc..a0d99f5f0c1 100644 --- a/spec/graphql/types/commit_signatures/verification_status_enum_spec.rb +++ b/spec/graphql/types/commit_signatures/verification_status_enum_spec.rb @@ -10,7 +10,7 @@ RSpec.describe GitlabSchema.types['VerificationStatus'] do .to match_array(%w[ UNVERIFIED UNVERIFIED_KEY VERIFIED SAME_USER_DIFFERENT_EMAIL OTHER_USER UNKNOWN_KEY - MULTIPLE_SIGNATURES + MULTIPLE_SIGNATURES REVOKED_KEY ]) end end diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb index 0b65778ce90..6820cf2738e 100644 --- a/spec/graphql/types/group_type_spec.rb +++ b/spec/graphql/types/group_type_spec.rb @@ -26,7 +26,7 @@ RSpec.describe GitlabSchema.types['Group'] do dependency_proxy_image_prefix dependency_proxy_image_ttl_policy shared_runners_setting timelogs organization_state_counts organizations contact_state_counts contacts work_item_types - recent_issue_boards ci_variables + recent_issue_boards ci_variables releases ] expect(described_class).to include_graphql_fields(*expected_fields) @@ -70,6 +70,13 @@ RSpec.describe GitlabSchema.types['Group'] do it { is_expected.to have_graphql_resolver(Resolvers::Crm::OrganizationStateCountsResolver) } end + describe 'releases field' do + subject { described_class.fields['releases'] } + + it { is_expected.to have_graphql_type(Types::ReleaseType.connection_type) } + it { is_expected.to have_graphql_resolver(Resolvers::GroupReleasesResolver) } + end + it_behaves_like 'a GraphQL type with labels' do let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] } end diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb index 498625dc642..7c6cf137a1e 100644 --- a/spec/graphql/types/issue_type_spec.rb +++ b/spec/graphql/types/issue_type_spec.rb @@ -228,29 +228,17 @@ RSpec.describe GitlabSchema.types['Issue'] do subject { GitlabSchema.execute(query, context: { current_user: admin }).as_json } - context 'when `ban_user_feature_flag` is enabled' do - context 'when issue is hidden' do - it 'returns `true`' do - expect(subject.dig('data', 'project', 'issue', 'hidden')).to eq(true) - end - end - - context 'when issue is visible' do - let(:issue) { visible_issue } - - it 'returns `false`' do - expect(subject.dig('data', 'project', 'issue', 'hidden')).to eq(false) - end + context 'when issue is hidden' do + it 'returns `true`' do + expect(subject.dig('data', 'project', 'issue', 'hidden')).to eq(true) end end - context 'when `ban_user_feature_flag` is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end + context 'when issue is visible' do + let(:issue) { visible_issue } - it 'returns `nil`' do - expect(subject.dig('data', 'project', 'issue', 'hidden')).to be_nil + it 'returns `false`' do + expect(subject.dig('data', 'project', 'issue', 'hidden')).to eq(false) end end end diff --git a/spec/graphql/types/notes/deleted_note_type_spec.rb b/spec/graphql/types/notes/deleted_note_type_spec.rb new file mode 100644 index 00000000000..70985484e75 --- /dev/null +++ b/spec/graphql/types/notes/deleted_note_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['DeletedNote'], feature_category: :team_planning do + it 'exposes the expected fields' do + expected_fields = %i[ + id + discussion_id + last_discussion_note + ] + + expect(described_class).to have_graphql_fields(*expected_fields).only + end +end diff --git a/spec/graphql/types/packages/package_details_type_spec.rb b/spec/graphql/types/packages/package_details_type_spec.rb index d5688fc64c5..e4fe53f7660 100644 --- a/spec/graphql/types/packages/package_details_type_spec.rb +++ b/spec/graphql/types/packages/package_details_type_spec.rb @@ -10,7 +10,7 @@ RSpec.describe GitlabSchema.types['PackageDetailsType'] do it 'includes all the package fields' do expected_fields = %w[ id name version created_at updated_at package_type tags project - pipelines versions package_files dependency_links + pipelines versions package_files dependency_links public_package npm_url maven_url conan_url nuget_url pypi_url pypi_setup_url composer_url composer_config_repository_url ] diff --git a/spec/graphql/types/permission_types/merge_request_spec.rb b/spec/graphql/types/permission_types/merge_request_spec.rb index 2849dead9a8..2c5da9a933c 100644 --- a/spec/graphql/types/permission_types/merge_request_spec.rb +++ b/spec/graphql/types/permission_types/merge_request_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Types::PermissionTypes::MergeRequest do :read_merge_request, :admin_merge_request, :update_merge_request, :create_note, :push_to_source_branch, :remove_source_branch, :cherry_pick_on_current_merge_request, :revert_on_current_merge_request, - :can_merge + :can_merge, :can_approve ] expect(described_class).to have_graphql_fields(expected_permissions) diff --git a/spec/graphql/types/permission_types/work_item_spec.rb b/spec/graphql/types/permission_types/work_item_spec.rb index e604ce5d6e0..db6d78b1538 100644 --- a/spec/graphql/types/permission_types/work_item_spec.rb +++ b/spec/graphql/types/permission_types/work_item_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Types::PermissionTypes::WorkItem do it do expected_permissions = [ - :read_work_item, :update_work_item, :delete_work_item + :read_work_item, :update_work_item, :delete_work_item, :admin_work_item ] expected_permissions.each do |permission| diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 4151789372b..7f26190830e 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Project'] do include GraphqlHelpers + include ProjectForksHelper specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) } @@ -37,7 +38,7 @@ RSpec.describe GitlabSchema.types['Project'] do ci_template timelogs merge_commit_template squash_commit_template work_item_types recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables timelog_categories fork_targets branch_rules ci_config_variables pipeline_schedules languages - incident_management_timeline_event_tags + incident_management_timeline_event_tags visible_forks ] expect(described_class).to include_graphql_fields(*expected_fields) @@ -285,6 +286,17 @@ RSpec.describe GitlabSchema.types['Project'] do end end end + + context 'with empty repository' do + let_it_be(:project) { create(:project_empty_repo) } + + it 'raises an error' do + expect(subject['errors'][0]['message']).to eq('You must <a target="_blank" rel="noopener noreferrer" ' \ + 'href="http://localhost/help/user/project/repository/index.md#' \ + 'add-files-to-a-repository">add at least one file to the ' \ + 'repository</a> before using Security features.') + end + end end describe 'issue field' do @@ -848,4 +860,54 @@ RSpec.describe GitlabSchema.types['Project'] do end end end + + describe 'visible_forks' do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:fork_reporter) { fork_project(project, nil, { repository: true }) } + let_it_be(:fork_developer) { fork_project(project, nil, { repository: true }) } + let_it_be(:fork_group_developer) { fork_project(project, nil, { repository: true }) } + let_it_be(:fork_public) { fork_project(project, nil, { repository: true }) } + let_it_be(:fork_private) { fork_project(project, nil, { repository: true }) } + + let(:minimum_access_level) { '' } + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + visibleForks#{minimum_access_level} { + nodes { + fullPath + } + } + } + } + ) + end + + let(:forks) do + subject.dig('data', 'project', 'visibleForks', 'nodes') + end + + subject { GitlabSchema.execute(query, context: { current_user: user }).as_json } + + before do + fork_reporter.add_reporter(user) + fork_developer.add_developer(user) + fork_group_developer.group.add_developer(user) + end + + it 'contains all forks' do + expect(forks.count).to eq(5) + end + + context 'with minimum_access_level DEVELOPER' do + let(:minimum_access_level) { '(minimumAccessLevel: DEVELOPER)' } + + it 'contains forks with developer access' do + expect(forks).to contain_exactly(a_hash_including('fullPath' => fork_developer.full_path), +a_hash_including('fullPath' => fork_group_developer.full_path)) + end + end + end end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index f06759e30c8..100ecc94f35 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -2,49 +2,15 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['Query'] do +RSpec.describe GitlabSchema.types['Query'], feature_category: :shared do + include_context 'with FOSS query type fields' + it 'is called Query' do expect(described_class.graphql_name).to eq('Query') end it 'has the expected fields' do - expected_fields = [ - :board_list, - :ci_application_settings, - :ci_config, - :ci_variables, - :container_repository, - :current_user, - :design_management, - :echo, - :gitpod_enabled, - :group, - :issue, - :issues, - :jobs, - :merge_request, - :metadata, - :milestone, - :namespace, - :package, - :project, - :projects, - :query_complexity, - :runner, - :runner_platforms, - :runner_setup, - :runners, - :snippets, - :timelogs, - :todo, - :topics, - :usage_trends_measurements, - :user, - :users, - :work_item - ] - - expect(described_class).to have_graphql_fields(*expected_fields).at_least + expect(described_class).to have_graphql_fields(*expected_foss_fields).at_least end describe 'namespace field' do diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb index 45cb960cf20..a6b5d454b60 100644 --- a/spec/graphql/types/user_type_spec.rb +++ b/spec/graphql/types/user_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['User'], feature_category: :users do +RSpec.describe GitlabSchema.types['User'], feature_category: :user_profile do specify { expect(described_class.graphql_name).to eq('User') } specify do @@ -46,6 +46,7 @@ RSpec.describe GitlabSchema.types['User'], feature_category: :users do preferencesGitpodPath profileEnableGitpodPath savedReplies + savedReply ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/users/email_type_spec.rb b/spec/graphql/types/users/email_type_spec.rb index fb484915428..107bbf81e98 100644 --- a/spec/graphql/types/users/email_type_spec.rb +++ b/spec/graphql/types/users/email_type_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe GitlabSchema.types['Email'], feature_category: :users do +RSpec.describe GitlabSchema.types['Email'], feature_category: :user_profile do it 'has the correct fields' do expected_fields = [ :id, diff --git a/spec/graphql/types/users/namespace_commit_email_type_spec.rb b/spec/graphql/types/users/namespace_commit_email_type_spec.rb index ccab881676e..27e50f7285e 100644 --- a/spec/graphql/types/users/namespace_commit_email_type_spec.rb +++ b/spec/graphql/types/users/namespace_commit_email_type_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe GitlabSchema.types['NamespaceCommitEmail'], feature_category: :users do +RSpec.describe GitlabSchema.types['NamespaceCommitEmail'], feature_category: :user_profile do it 'has the correct fields' do expected_fields = [ :id, diff --git a/spec/graphql/types/work_item_type_spec.rb b/spec/graphql/types/work_item_type_spec.rb index dded96fde3a..42d56598944 100644 --- a/spec/graphql/types/work_item_type_spec.rb +++ b/spec/graphql/types/work_item_type_spec.rb @@ -11,6 +11,7 @@ RSpec.describe GitlabSchema.types['WorkItem'] do it 'has specific fields' do fields = %i[ + author confidential description description_html |