diff options
Diffstat (limited to 'spec/graphql')
87 files changed, 1147 insertions, 546 deletions
diff --git a/spec/graphql/mutations/base_mutation_spec.rb b/spec/graphql/mutations/base_mutation_spec.rb index 7939fadb37b..6b366b0c234 100644 --- a/spec/graphql/mutations/base_mutation_spec.rb +++ b/spec/graphql/mutations/base_mutation_spec.rb @@ -15,6 +15,7 @@ RSpec.describe ::Mutations::BaseMutation do context 'when argument is nullable and required' do let(:mutation_class) do Class.new(described_class) do + graphql_name 'BaseMutation' argument :foo, GraphQL::Types::String, required: :nullable end end @@ -35,6 +36,7 @@ RSpec.describe ::Mutations::BaseMutation do context 'when argument is required and NOT nullable' do let(:mutation_class) do Class.new(described_class) do + graphql_name 'BaseMutation' argument :foo, GraphQL::Types::String, required: true end end diff --git a/spec/graphql/mutations/boards/update_spec.rb b/spec/graphql/mutations/boards/update_spec.rb index da3dfeecd4d..4785bc94624 100644 --- a/spec/graphql/mutations/boards/update_spec.rb +++ b/spec/graphql/mutations/boards/update_spec.rb @@ -29,14 +29,6 @@ RSpec.describe Mutations::Boards::Update do end end - context 'with invalid params' do - it 'raises an error' do - mutation_params[:id] = project.to_global_id - - expect { subject }.to raise_error(::GraphQL::CoercionError) - end - end - context 'when user can update board' do before do board.resource_parent.add_reporter(user) diff --git a/spec/graphql/mutations/ci/runner/delete_spec.rb b/spec/graphql/mutations/ci/runner/delete_spec.rb index ee640b21918..06d360430f8 100644 --- a/spec/graphql/mutations/ci/runner/delete_spec.rb +++ b/spec/graphql/mutations/ci/runner/delete_spec.rb @@ -44,14 +44,6 @@ RSpec.describe Mutations::Ci::Runner::Delete do end end - context 'with invalid params' do - let(:mutation_params) { { id: "invalid-id" } } - - it 'raises an error' do - expect { subject }.to raise_error(::GraphQL::CoercionError) - end - end - context 'when required arguments are missing' do let(:mutation_params) { {} } diff --git a/spec/graphql/mutations/ci/runner/update_spec.rb b/spec/graphql/mutations/ci/runner/update_spec.rb index 0b3489d37dc..75e9b57e60a 100644 --- a/spec/graphql/mutations/ci/runner/update_spec.rb +++ b/spec/graphql/mutations/ci/runner/update_spec.rb @@ -33,14 +33,6 @@ RSpec.describe Mutations::Ci::Runner::Update do end end - context 'with invalid params' do - it 'raises an error' do - mutation_params[:id] = "invalid-id" - - expect { subject }.to raise_error(::GraphQL::CoercionError) - end - end - context 'when required arguments are missing' do let(:mutation_params) { {} } diff --git a/spec/graphql/mutations/clusters/agent_tokens/create_spec.rb b/spec/graphql/mutations/clusters/agent_tokens/create_spec.rb index fc025c8e3d3..45d421509d0 100644 --- a/spec/graphql/mutations/clusters/agent_tokens/create_spec.rb +++ b/spec/graphql/mutations/clusters/agent_tokens/create_spec.rb @@ -48,14 +48,6 @@ RSpec.describe Mutations::Clusters::AgentTokens::Create do expect(token.description).to eq(description) expect(token.name).to eq(name) end - - context 'invalid params' do - subject { mutation.resolve(cluster_agent_id: cluster_agent.id) } - - it 'generates an error message when id invalid', :aggregate_failures do - expect { subject }.to raise_error(::GraphQL::CoercionError) - end - end end end end diff --git a/spec/graphql/mutations/clusters/agent_tokens/delete_spec.rb b/spec/graphql/mutations/clusters/agent_tokens/delete_spec.rb deleted file mode 100644 index 5cdbc0f6d72..00000000000 --- a/spec/graphql/mutations/clusters/agent_tokens/delete_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Mutations::Clusters::AgentTokens::Delete do - let(:token) { create(:cluster_agent_token) } - let(:user) { create(:user) } - - let(:mutation) do - described_class.new( - object: double, - context: { current_user: user }, - field: double - ) - end - - it { expect(described_class.graphql_name).to eq('ClusterAgentTokenDelete') } - it { expect(described_class).to require_graphql_authorizations(:admin_cluster) } - - describe '#resolve' do - let(:global_id) { token.to_global_id } - - subject { mutation.resolve(id: global_id) } - - context 'without user permissions' do - it 'fails to delete the cluster agent', :aggregate_failures do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - expect { token.reload }.not_to raise_error - end - end - - context 'with user permissions' do - before do - token.agent.project.add_maintainer(user) - end - - it 'deletes a cluster agent', :aggregate_failures do - expect { subject }.to change { ::Clusters::AgentToken.count }.by(-1) - expect { token.reload }.to raise_error(ActiveRecord::RecordNotFound) - end - end - - context 'with invalid params' do - let(:global_id) { token.id } - - it 'raises an error if the cluster agent id is invalid', :aggregate_failures do - expect { subject }.to raise_error(::GraphQL::CoercionError) - expect { token.reload }.not_to raise_error - end - end - end -end diff --git a/spec/graphql/mutations/clusters/agent_tokens/revoke_spec.rb b/spec/graphql/mutations/clusters/agent_tokens/revoke_spec.rb index f5f4c0cefad..1dd4eece246 100644 --- a/spec/graphql/mutations/clusters/agent_tokens/revoke_spec.rb +++ b/spec/graphql/mutations/clusters/agent_tokens/revoke_spec.rb @@ -40,16 +40,6 @@ RSpec.describe Mutations::Clusters::AgentTokens::Revoke do expect(token.reload).to be_revoked end - - context 'supplied ID is invalid' do - let(:global_id) { token.id } - - it 'raises a coercion error' do - expect { subject }.to raise_error(::GraphQL::CoercionError) - - expect(token.reload).not_to be_revoked - end - end end end end diff --git a/spec/graphql/mutations/clusters/agents/delete_spec.rb b/spec/graphql/mutations/clusters/agents/delete_spec.rb index 0aabf53391a..e0ecff5fe44 100644 --- a/spec/graphql/mutations/clusters/agents/delete_spec.rb +++ b/spec/graphql/mutations/clusters/agents/delete_spec.rb @@ -38,14 +38,5 @@ RSpec.describe Mutations::Clusters::Agents::Delete do expect { cluster_agent.reload }.to raise_error(ActiveRecord::RecordNotFound) end end - - context 'with invalid params' do - subject { mutation.resolve(id: cluster_agent.id) } - - it 'raises an error if the cluster agent id is invalid', :aggregate_failures do - expect { subject }.to raise_error(::GraphQL::CoercionError) - expect { cluster_agent.reload }.not_to raise_error - end - end end end diff --git a/spec/graphql/mutations/concerns/mutations/finds_by_gid_spec.rb b/spec/graphql/mutations/concerns/mutations/finds_by_gid_spec.rb index 37e0fd611e4..451f6d1fe06 100644 --- a/spec/graphql/mutations/concerns/mutations/finds_by_gid_spec.rb +++ b/spec/graphql/mutations/concerns/mutations/finds_by_gid_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Mutations::FindsByGid do end end - let(:query) { double('Query', schema: GitlabSchema) } + let(:query) { query_double(schema: GitlabSchema) } let(:context) { GraphQL::Query::Context.new(query: query, object: nil, values: { current_user: user }) } let(:user) { create(:user) } let(:gid) { user.to_global_id } diff --git a/spec/graphql/mutations/container_expiration_policies/update_spec.rb b/spec/graphql/mutations/container_expiration_policies/update_spec.rb index e22fb951172..e070336ef76 100644 --- a/spec/graphql/mutations/container_expiration_policies/update_spec.rb +++ b/spec/graphql/mutations/container_expiration_policies/update_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do let(:container_expiration_policy) { project.container_expiration_policy } let(:params) { { project_path: project.full_path, cadence: '3month', keep_n: 100, older_than: '14d' } } - specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) } + specify { expect(described_class).to require_graphql_authorizations(:admin_container_image) } describe '#resolve' do subject { described_class.new(object: project, context: { current_user: user }, field: nil).resolve(**params) } @@ -76,7 +76,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do context 'with existing container expiration policy' do where(:user_role, :shared_examples_name) do :maintainer | 'updating the container expiration policy' - :developer | 'updating the container expiration policy' + :developer | 'denying access to container expiration policy' :reporter | 'denying access to container expiration policy' :guest | 'denying access to container expiration policy' :anonymous | 'denying access to container expiration policy' @@ -96,7 +96,7 @@ RSpec.describe Mutations::ContainerExpirationPolicies::Update do where(:user_role, :shared_examples_name) do :maintainer | 'creating the container expiration policy' - :developer | 'creating the container expiration policy' + :developer | 'denying access to container expiration policy' :reporter | 'denying access to container expiration policy' :guest | 'denying access to container expiration policy' :anonymous | 'denying access to container expiration policy' diff --git a/spec/graphql/mutations/container_repositories/destroy_spec.rb b/spec/graphql/mutations/container_repositories/destroy_spec.rb index 3903196a511..97da7846339 100644 --- a/spec/graphql/mutations/container_repositories/destroy_spec.rb +++ b/spec/graphql/mutations/container_repositories/destroy_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Mutations::ContainerRepositories::Destroy do let_it_be(:user) { create(:user) } let(:project) { container_repository.project } - let(:id) { container_repository.to_global_id.to_s } + let(:id) { container_repository.to_global_id } specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) } @@ -57,11 +57,5 @@ RSpec.describe Mutations::ContainerRepositories::Destroy do it_behaves_like params[:shared_examples_name] end end - - context 'with invalid id' do - let(:id) { 'gid://gitlab/ContainerRepository/5555' } - - it_behaves_like 'denying access to container respository' - end end end diff --git a/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb b/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb index f22d9ffe753..3e5f28ee244 100644 --- a/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb +++ b/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb @@ -3,10 +3,12 @@ require 'spec_helper' RSpec.describe Mutations::ContainerRepositories::DestroyTags do + include GraphqlHelpers + include_context 'container repository delete tags service shared context' using RSpec::Parameterized::TableSyntax - let(:id) { repository.to_global_id.to_s } + let(:id) { repository.to_global_id } specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) } @@ -67,8 +69,8 @@ RSpec.describe Mutations::ContainerRepositories::DestroyTags do end end - context 'with invalid id' do - let(:id) { 'gid://gitlab/ContainerRepository/5555' } + context 'with non-existing id' do + let(:id) { global_id_of(id: non_existing_record_id, model_name: 'ContainerRepository') } it_behaves_like 'denying access to container respository' end diff --git a/spec/graphql/mutations/customer_relations/contacts/create_spec.rb b/spec/graphql/mutations/customer_relations/contacts/create_spec.rb index d17d11305b1..dafc7b4c367 100644 --- a/spec/graphql/mutations/customer_relations/contacts/create_spec.rb +++ b/spec/graphql/mutations/customer_relations/contacts/create_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Mutations::CustomerRelations::Contacts::Create do + include GraphqlHelpers + let_it_be(:user) { create(:user) } let(:group) { create(:group, :crm_enabled) } @@ -78,9 +80,9 @@ RSpec.describe Mutations::CustomerRelations::Contacts::Create do end end - context 'when organization_id is invalid' do + context 'when organization does not exist' do before do - valid_params[:organization_id] = "gid://gitlab/CustomerRelations::Organization/#{non_existing_record_id}" + valid_params[:organization_id] = global_id_of(model_name: 'CustomerRelations::Organization', id: non_existing_record_id) end it 'returns the relevant error' do diff --git a/spec/graphql/mutations/dependency_proxy/group_settings/update_spec.rb b/spec/graphql/mutations/dependency_proxy/group_settings/update_spec.rb index 35d3224d5ba..ae368e4d37e 100644 --- a/spec/graphql/mutations/dependency_proxy/group_settings/update_spec.rb +++ b/spec/graphql/mutations/dependency_proxy/group_settings/update_spec.rb @@ -37,7 +37,7 @@ RSpec.describe Mutations::DependencyProxy::GroupSettings::Update do where(:user_role, :shared_examples_name) do :maintainer | 'updating the dependency proxy group settings' - :developer | 'updating the dependency proxy group settings' + :developer | 'denying access to dependency proxy group settings' :reporter | 'denying access to dependency proxy group settings' :guest | 'denying access to dependency proxy group settings' :anonymous | 'denying access to dependency proxy group settings' diff --git a/spec/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb b/spec/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb index 792e87f0d25..1e5059d7ef7 100644 --- a/spec/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb +++ b/spec/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb @@ -72,7 +72,7 @@ RSpec.describe Mutations::DependencyProxy::ImageTtlGroupPolicy::Update do where(:user_role, :shared_examples_name) do :maintainer | 'updating the dependency proxy image ttl policy' - :developer | 'updating the dependency proxy image ttl policy' + :developer | 'denying access to dependency proxy image ttl policy' :reporter | 'denying access to dependency proxy image ttl policy' :guest | 'denying access to dependency proxy image ttl policy' :anonymous | 'denying access to dependency proxy image ttl policy' @@ -92,7 +92,7 @@ RSpec.describe Mutations::DependencyProxy::ImageTtlGroupPolicy::Update do where(:user_role, :shared_examples_name) do :maintainer | 'creating the dependency proxy image ttl policy' - :developer | 'creating the dependency proxy image ttl policy' + :developer | 'denying access to dependency proxy image ttl policy' :reporter | 'denying access to dependency proxy image ttl policy' :guest | 'denying access to dependency proxy image ttl policy' :anonymous | 'denying access to dependency proxy image ttl policy' diff --git a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb index 2041b86d6e7..3f7347798e5 100644 --- a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb +++ b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Mutations::Discussions::ToggleResolve do + include GraphqlHelpers + subject(:mutation) do described_class.new(object: nil, context: { current_user: user }, field: nil) end @@ -15,7 +17,7 @@ RSpec.describe Mutations::Discussions::ToggleResolve do mutation.resolve(id: id_arg, resolve: resolve_arg) end - let(:id_arg) { discussion.to_global_id.to_s } + let(:id_arg) { global_id_of(discussion) } let(:resolve_arg) { true } let(:mutated_discussion) { subject[:discussion] } let(:errors) { subject[:errors] } @@ -36,7 +38,7 @@ RSpec.describe Mutations::Discussions::ToggleResolve do let_it_be(:user) { create(:user, developer_projects: [project]) } context 'when discussion cannot be found' do - let(:id_arg) { "#{discussion.to_global_id}foo" } + let(:id_arg) { global_id_of(id: non_existing_record_id, model_name: discussion.class.name) } it 'raises an error' do expect { subject }.to raise_error( @@ -46,17 +48,6 @@ RSpec.describe Mutations::Discussions::ToggleResolve do end end - context 'when discussion is not a Discussion' do - let(:discussion) { create(:note, noteable: noteable, project: project) } - - it 'raises an error' do - expect { subject }.to raise_error( - GraphQL::CoercionError, - "\"#{discussion.to_global_id}\" does not represent an instance of Discussion" - ) - end - end - shared_examples 'returns a resolved discussion without errors' do it 'returns a resolved discussion' do expect(mutated_discussion).to be_resolved diff --git a/spec/graphql/mutations/environments/canary_ingress/update_spec.rb b/spec/graphql/mutations/environments/canary_ingress/update_spec.rb index fdf9cbaf25b..b93fb36a8ff 100644 --- a/spec/graphql/mutations/environments/canary_ingress/update_spec.rb +++ b/spec/graphql/mutations/environments/canary_ingress/update_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Mutations::Environments::CanaryIngress::Update do describe '#resolve' do subject { mutation.resolve(id: environment_id, weight: weight) } - let(:environment_id) { environment.to_global_id.to_s } + let(:environment_id) { environment.to_global_id } let(:weight) { 50 } let(:update_service) { double('update_service') } @@ -62,14 +62,6 @@ RSpec.describe Mutations::Environments::CanaryIngress::Update do end end - context 'when environment is not found' do - let(:environment_id) { non_existing_record_id.to_s } - - it 'raises an error' do - expect { subject }.to raise_error(GraphQL::CoercionError) - end - end - context 'when user is reporter who does not have permission to access the environment' do let(:user) { reporter } diff --git a/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb new file mode 100644 index 00000000000..63faecad5d5 --- /dev/null +++ b/spec/graphql/mutations/incident_management/timeline_event/create_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::IncidentManagement::TimelineEvent::Create do + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:incident) { create(:incident, project: project) } + + let(:args) { { note: 'note', occurred_at: Time.current } } + + specify { expect(described_class).to require_graphql_authorizations(:admin_incident_management_timeline_event) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(incident_id: incident.to_global_id, **args) } + + context 'when a user has permissions to create a timeline event' do + let(:expected_timeline_event) do + instance_double( + 'IncidentManagement::TimelineEvent', + note: args[:note], + occurred_at: args[:occurred_at].to_s, + incident: incident, + author: current_user, + promoted_from_note: nil + ) + end + + before do + project.add_developer(current_user) + end + + it_behaves_like 'creating an incident timeline event' + + context 'when TimelineEvents::CreateService responds with an error' do + let(:args) { {} } + + it_behaves_like 'responding with an incident timeline errors', + errors: ["Occurred at can't be blank, Note can't be blank, and Note html can't be blank"] + end + end + + it_behaves_like 'failing to create an incident timeline event' + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end diff --git a/spec/graphql/mutations/incident_management/timeline_event/destroy_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/destroy_spec.rb new file mode 100644 index 00000000000..4dd7b2ccb14 --- /dev/null +++ b/spec/graphql/mutations/incident_management/timeline_event/destroy_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::IncidentManagement::TimelineEvent::Destroy do + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:incident) { create(:incident, project: project) } + + let(:timeline_event) { create(:incident_management_timeline_event, incident: incident, project: project) } + let(:args) { { id: timeline_event.to_global_id } } + + specify { expect(described_class).to require_graphql_authorizations(:admin_incident_management_timeline_event) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(**args) } + + context 'when a user has permissions to delete timeline event' do + before do + project.add_developer(current_user) + end + + context 'when TimelineEvents::DestroyService responds with success' do + it 'returns the timeline event with no errors' do + expect(resolve).to eq( + timeline_event: timeline_event, + errors: [] + ) + end + end + + context 'when TimelineEvents::DestroyService responds with an error' do + before do + allow_next_instance_of(::IncidentManagement::TimelineEvents::DestroyService) do |service| + allow(service) + .to receive(:execute) + .and_return(ServiceResponse.error(payload: { timeline_event: nil }, message: 'An error has occurred')) + end + end + + it 'returns errors' do + expect(resolve).to eq( + timeline_event: nil, + errors: ['An error has occurred'] + ) + end + end + end + + context 'when a user has no permissions to delete timeline event' do + before do + project.add_guest(current_user) + end + + it 'raises an error' do + expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end diff --git a/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb new file mode 100644 index 00000000000..598ee496cf1 --- /dev/null +++ b/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::IncidentManagement::TimelineEvent::PromoteFromNote do + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:incident) { create(:incident, project: project) } + let_it_be(:comment) { create(:note, project: project, noteable: incident) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:issue_comment) { create(:note, project: project, noteable: issue) } + let_it_be(:alert) { create(:alert_management_alert, project: project) } + let_it_be(:alert_comment) { create(:note, project: project, noteable: alert) } + + let(:args) { { note_id: comment.to_global_id.to_s } } + + specify { expect(described_class).to require_graphql_authorizations(:admin_incident_management_timeline_event) } + + describe '#resolve' do + subject(:resolve) { mutation_for(project, current_user).resolve(**args) } + + context 'when a user has permissions to create timeline event' do + let(:expected_timeline_event) do + instance_double( + 'IncidentManagement::TimelineEvent', + note: comment.note, + occurred_at: comment.created_at.to_s, + incident: incident, + author: current_user, + promoted_from_note: comment + ) + end + + before do + project.add_developer(current_user) + end + + it_behaves_like 'creating an incident timeline event' + + context 'when TimelineEvents::CreateService responds with an error' do + before do + allow_next_instance_of(::IncidentManagement::TimelineEvents::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.error(payload: { timeline_event: nil }, message: 'Some error') + ) + end + end + + it_behaves_like 'responding with an incident timeline errors', errors: ['Some error'] + end + end + + context 'when note does not exist' do + let(:args) { { note_id: 'gid://gitlab/Note/0' } } + + it 'raises an error' do + expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when note does not belong to an incident' do + let(:args) { { note_id: issue_comment.to_global_id.to_s } } + + it 'raises an error' do + expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when note belongs to anything else but issuable' do + let(:args) { { note_id: alert_comment.to_global_id.to_s } } + + it 'raises an error' do + expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + it_behaves_like 'failing to create an incident timeline event' + end + + private + + def mutation_for(project, user) + described_class.new(object: project, context: { current_user: user }, field: nil) + end +end diff --git a/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb new file mode 100644 index 00000000000..8296e5c6c15 --- /dev/null +++ b/spec/graphql/mutations/incident_management/timeline_event/update_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::IncidentManagement::TimelineEvent::Update do + let_it_be(:developer) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:incident) { create(:incident, project: project) } + let_it_be_with_reload(:timeline_event) do + create(:incident_management_timeline_event, project: project, incident: incident) + end + + let(:args) do + { + id: timeline_event_id, + note: note, + occurred_at: occurred_at + } + end + + let(:note) { 'Updated Note' } + let(:timeline_event_id) { GitlabSchema.id_from_object(timeline_event).to_s } + let(:occurred_at) { 1.minute.ago } + + before do + project.add_developer(developer) + project.add_reporter(reporter) + end + + describe '#resolve' do + let(:current_user) { developer } + + subject(:resolve) { mutation_for(current_user).resolve(**args) } + + shared_examples 'failed update with a top-level access error' do |error| + specify do + expect { resolve }.to raise_error( + Gitlab::Graphql::Errors::ResourceNotAvailable, + error || Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR + ) + end + end + + context 'when user has permissions to update the timeline event' do + context 'when timeline event exists' do + it 'updates the timeline event' do + expect { resolve }.to change { timeline_event.reload.note }.to(note) + .and change { timeline_event.reload.occurred_at.to_s }.to(occurred_at.to_s) + end + + it 'returns updated timeline event' do + expect(resolve).to eq( + timeline_event: timeline_event.reload, + errors: [] + ) + end + + context 'when there is a validation error' do + let(:occurred_at) { 'invalid date' } + + it 'does not update the timeline event' do + expect { resolve }.not_to change { timeline_event.reload.updated_at } + end + + it 'responds with error' do + expect(resolve).to eq( + timeline_event: nil, + errors: ["Occurred at can't be blank"] + ) + end + end + end + + context 'when timeline event cannot be found' do + let(:timeline_event_id) do + Gitlab::GlobalId.build( + nil, + model_name: ::IncidentManagement::TimelineEvent.name, + id: non_existing_record_id + ).to_s + end + + it_behaves_like 'failed update with a top-level access error' + end + end + + context 'when user does not have permissions to update the timeline event' do + let(:current_user) { reporter } + + it_behaves_like 'failed update with a top-level access error' + end + end + + private + + def mutation_for(user) + described_class.new(object: nil, context: { current_user: user }, field: nil) + end +end diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb index 263122e5d5f..83edd670695 100644 --- a/spec/graphql/mutations/issues/set_due_date_spec.rb +++ b/spec/graphql/mutations/issues/set_due_date_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Mutations::Issues::SetDueDate do it 'returns the issue with updated due date', :aggregate_failures do expect(mutated_issue).to eq(issue) - expect(mutated_issue.due_date).to eq(Date.today + 2.days) + expect(mutated_issue.due_date).to eq(due_date.to_date) expect(subject[:errors]).to be_empty end diff --git a/spec/graphql/mutations/merge_requests/accept_spec.rb b/spec/graphql/mutations/merge_requests/accept_spec.rb index c97c78ec206..c99b1d988c5 100644 --- a/spec/graphql/mutations/merge_requests/accept_spec.rb +++ b/spec/graphql/mutations/merge_requests/accept_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' RSpec.describe Mutations::MergeRequests::Accept do + include GraphqlHelpers include AfterNextHelpers subject(:mutation) { described_class.new(context: context, object: nil, field: nil) } @@ -12,7 +13,7 @@ RSpec.describe Mutations::MergeRequests::Accept do let(:project) { create(:project, :public, :repository) } let(:context) do GraphQL::Query::Context.new( - query: double('query', schema: GitlabSchema), + query: query_double(schema: GitlabSchema), values: { current_user: user }, object: nil ) diff --git a/spec/graphql/mutations/merge_requests/create_spec.rb b/spec/graphql/mutations/merge_requests/create_spec.rb index 83af1e3f1b3..e1edb60e4ff 100644 --- a/spec/graphql/mutations/merge_requests/create_spec.rb +++ b/spec/graphql/mutations/merge_requests/create_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Mutations::MergeRequests::Create do + include GraphqlHelpers + subject(:mutation) { described_class.new(object: nil, context: context, field: nil) } let_it_be(:project) { create(:project, :public, :repository) } @@ -10,7 +12,7 @@ RSpec.describe Mutations::MergeRequests::Create do let(:context) do GraphQL::Query::Context.new( - query: double('query', schema: nil), + query: query_double(schema: nil), values: { current_user: user }, object: nil ) diff --git a/spec/graphql/mutations/namespace/package_settings/update_spec.rb b/spec/graphql/mutations/namespace/package_settings/update_spec.rb index 978c81fadfa..631e02ff3dc 100644 --- a/spec/graphql/mutations/namespace/package_settings/update_spec.rb +++ b/spec/graphql/mutations/namespace/package_settings/update_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update do let(:params) { { namespace_path: namespace.full_path } } - specify { expect(described_class).to require_graphql_authorizations(:create_package_settings) } + specify { expect(described_class).to require_graphql_authorizations(:admin_package) } describe '#resolve' do subject { described_class.new(object: namespace, context: { current_user: user }, field: nil).resolve(**params) } @@ -68,7 +68,7 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update do where(:user_role, :shared_examples_name) do :maintainer | 'updating the namespace package setting' - :developer | 'updating the namespace package setting' + :developer | 'denying access to namespace package setting' :reporter | 'denying access to namespace package setting' :guest | 'denying access to namespace package setting' :anonymous | 'denying access to namespace package setting' @@ -88,7 +88,7 @@ RSpec.describe Mutations::Namespace::PackageSettings::Update do where(:user_role, :shared_examples_name) do :maintainer | 'creating the namespace package setting' - :developer | 'creating the namespace package setting' + :developer | 'denying access to namespace package setting' :reporter | 'denying access to namespace package setting' :guest | 'denying access to namespace package setting' :anonymous | 'denying access to namespace package setting' diff --git a/spec/graphql/mutations/release_asset_links/delete_spec.rb b/spec/graphql/mutations/release_asset_links/delete_spec.rb index cda292f2ffa..cca7bd2ba38 100644 --- a/spec/graphql/mutations/release_asset_links/delete_spec.rb +++ b/spec/graphql/mutations/release_asset_links/delete_spec.rb @@ -52,18 +52,12 @@ RSpec.describe Mutations::ReleaseAssetLinks::Delete do end context "when the link doesn't exist" do - let(:mutation_arguments) { super().merge(id: "gid://gitlab/Releases::Link/#{non_existing_record_id}") } - - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + let(:mutation_arguments) do + super().merge(id: global_id_of(id: non_existing_record_id, model_name: release_link.class.name)) end - end - - context "when the provided ID is invalid" do - let(:mutation_arguments) { super().merge(id: 'not-a-valid-gid') } it 'raises an error' do - expect { subject }.to raise_error(::GraphQL::CoercionError) + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) end end end diff --git a/spec/graphql/mutations/release_asset_links/update_spec.rb b/spec/graphql/mutations/release_asset_links/update_spec.rb index 64648687336..e119cf9cc77 100644 --- a/spec/graphql/mutations/release_asset_links/update_spec.rb +++ b/spec/graphql/mutations/release_asset_links/update_spec.rb @@ -186,18 +186,12 @@ RSpec.describe Mutations::ReleaseAssetLinks::Update do end context "when the link doesn't exist" do - let(:mutation_arguments) { super().merge(id: "gid://gitlab/Releases::Link/#{non_existing_record_id}") } - - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + let(:mutation_arguments) do + super().merge(id: global_id_of(id: non_existing_record_id, model_name: "Releases::Link")) end - end - - context "when the provided ID is invalid" do - let(:mutation_arguments) { super().merge(id: 'not-a-valid-gid') } it 'raises an error' do - expect { subject }.to raise_error(::GraphQL::CoercionError) + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) end end end diff --git a/spec/graphql/mutations/timelogs/delete_spec.rb b/spec/graphql/mutations/timelogs/delete_spec.rb new file mode 100644 index 00000000000..f4a258e0f78 --- /dev/null +++ b/spec/graphql/mutations/timelogs/delete_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Timelogs::Delete do + include GraphqlHelpers + + let_it_be(:author) { create(:user) } + let_it_be(:maintainer) { create(:user) } + let_it_be(:administrator) { create(:user, :admin) } + let_it_be(:project) { create(:project, :public) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be_with_reload(:timelog) { create(:timelog, user: author, issue: issue, time_spent: 1800) } + + let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } + let(:timelog_id) { global_id_of(timelog) } + let(:mutation_arguments) { { id: timelog_id } } + + describe '#resolve' do + subject(:resolve) do + mutation.resolve(**mutation_arguments) + end + + context 'when the timelog id is not valid' do + let(:current_user) { author } + let(:timelog_id) { global_id_of(model_name: 'Timelog', id: non_existing_record_id) } + + it 'raises Gitlab::Graphql::Errors::ResourceNotAvailable' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when the current user is not the timelog\'s author, not a maintainer and not an admin' do + let(:current_user) { create(:user) } + + it 'raises Gitlab::Graphql::Errors::ResourceNotAvailable' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when the current user is the timelog\'s author' do + let(:current_user) { author } + + it 'deletes the timelog' do + expect { subject }.to change { Timelog.count }.by(-1) + end + + it 'returns the deleted timelog' do + expect(subject[:timelog]).to eq(timelog) + end + + it 'returns no errors' do + expect(subject[:errors]).to be_empty + end + end + + context 'when the current user is not the timelog\'s author but a maintainer of the project' do + let(:current_user) { maintainer } + + before do + project.add_maintainer(maintainer) + end + + it 'deletes the timelog' do + expect { subject }.to change { Timelog.count }.by(-1) + end + + it 'returns the deleted timelog' do + expect(subject[:timelog]).to eq(timelog) + end + + it 'returns no errors' do + expect(subject[:errors]).to be_empty + end + end + + context 'when the current user is not the timelog\'s author, not a maintainer but an admin', :enable_admin_mode do + let(:current_user) { administrator } + + it 'deletes the timelog' do + expect { subject }.to change { Timelog.count }.by(-1) + end + + it 'returns the deleted timelog' do + expect(subject[:timelog]).to eq(timelog) + end + + it 'returns no errors' do + expect(subject[:errors]).to be_empty + end + end + end +end diff --git a/spec/graphql/mutations/todos/create_spec.rb b/spec/graphql/mutations/todos/create_spec.rb index bbb033e2f33..8c6dca98bad 100644 --- a/spec/graphql/mutations/todos/create_spec.rb +++ b/spec/graphql/mutations/todos/create_spec.rb @@ -10,12 +10,19 @@ RSpec.describe Mutations::Todos::Create do context 'when target does not support todos' do it 'raises error' do current_user = create(:user) - mutation = described_class.new(object: nil, context: { current_user: current_user }, field: nil) - target = create(:milestone) - expect { mutation.resolve(target_id: global_id_of(target)) } - .to raise_error(GraphQL::CoercionError) + ctx = { current_user: current_user } + input = { target_id: global_id_of(target).to_s } + mutation = graphql_mutation(described_class, input) + + response = GitlabSchema.execute(mutation.query, context: ctx, variables: mutation.variables).to_h + + expect(response).to include( + 'errors' => contain_exactly( + include('message' => /invalid value for targetId/) + ) + ) end end diff --git a/spec/graphql/mutations/todos/mark_done_spec.rb b/spec/graphql/mutations/todos/mark_done_spec.rb index 9723ac8af42..51df2032cf1 100644 --- a/spec/graphql/mutations/todos/mark_done_spec.rb +++ b/spec/graphql/mutations/todos/mark_done_spec.rb @@ -56,15 +56,6 @@ RSpec.describe Mutations::Todos::MarkDone do expect(todo2.reload.state).to eq('done') expect(other_user_todo.reload.state).to eq('pending') end - - it 'ignores invalid GIDs' do - expect { mutation.resolve(id: author.to_global_id.to_s) } - .to raise_error(::GraphQL::CoercionError) - - expect(todo1.reload.state).to eq('pending') - expect(todo2.reload.state).to eq('done') - expect(other_user_todo.reload.state).to eq('pending') - end end def mark_done_mutation(todo) diff --git a/spec/graphql/mutations/todos/restore_many_spec.rb b/spec/graphql/mutations/todos/restore_many_spec.rb index dc10355ef22..d43f1c8a2e9 100644 --- a/spec/graphql/mutations/todos/restore_many_spec.rb +++ b/spec/graphql/mutations/todos/restore_many_spec.rb @@ -49,13 +49,6 @@ RSpec.describe Mutations::Todos::RestoreMany do expect_states_were_not_changed end - it 'raises an error with invalid or non-Todo GIDs' do - expect { mutation.resolve(ids: [author.to_global_id.to_s]) } - .to raise_error(GraphQL::CoercionError) - - expect_states_were_not_changed - end - it 'restores multiple todos' do todo4 = create(:todo, user: current_user, author: author, state: :done) diff --git a/spec/graphql/mutations/todos/restore_spec.rb b/spec/graphql/mutations/todos/restore_spec.rb index 954bb3db668..fad9d6c08a6 100644 --- a/spec/graphql/mutations/todos/restore_spec.rb +++ b/spec/graphql/mutations/todos/restore_spec.rb @@ -56,15 +56,6 @@ RSpec.describe Mutations::Todos::Restore do expect(todo2.reload.state).to eq('pending') expect(other_user_todo.reload.state).to eq('done') end - - it 'raises error for invalid GID' do - expect { mutation.resolve(id: author.to_global_id.to_s) } - .to raise_error(::GraphQL::CoercionError) - - expect(todo1.reload.state).to eq('done') - expect(todo2.reload.state).to eq('pending') - expect(other_user_todo.reload.state).to eq('done') - end end def restore_mutation(todo) diff --git a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb index c042f6dac19..14ebe85d80e 100644 --- a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb +++ b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb @@ -39,8 +39,8 @@ RSpec.describe Resolvers::AlertManagement::AlertResolver do end context 'filtering by domain' do - let_it_be(:alert1) { create(:alert_management_alert, project: project, monitoring_tool: 'Cilium', domain: :threat_monitoring) } - let_it_be(:alert2) { create(:alert_management_alert, project: project, monitoring_tool: 'Cilium', domain: :threat_monitoring) } + let_it_be(:alert1) { create(:alert_management_alert, project: project, monitoring_tool: 'other', domain: :threat_monitoring) } + let_it_be(:alert2) { create(:alert_management_alert, project: project, monitoring_tool: 'other', domain: :threat_monitoring) } let_it_be(:alert3) { create(:alert_management_alert, project: project, monitoring_tool: 'generic') } let(:args) { { domain: 'operations' } } diff --git a/spec/graphql/resolvers/ci/config_resolver_spec.rb b/spec/graphql/resolvers/ci/config_resolver_spec.rb index 3ff6d8f4347..7a6104fc503 100644 --- a/spec/graphql/resolvers/ci/config_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/config_resolver_spec.rb @@ -37,13 +37,15 @@ RSpec.describe Resolvers::Ci::ConfigResolver do merged_yaml: content, jobs: [], errors: [], - warnings: [] + warnings: [], + includes: [] ) end it 'lints the ci config file and returns the merged yaml file' do expect(response[:status]).to eq(:valid) expect(response[:merged_yaml]).to eq(content) + expect(response[:includes]).to eq([]) expect(response[:errors]).to be_empty expect(::Gitlab::Ci::Lint).to have_received(:new).with(current_user: user, project: project, sha: sha) end @@ -69,7 +71,8 @@ RSpec.describe Resolvers::Ci::ConfigResolver do jobs: [], merged_yaml: content, errors: ['Invalid configuration format'], - warnings: [] + warnings: [], + includes: [] ) end diff --git a/spec/graphql/resolvers/concerns/resolves_ids_spec.rb b/spec/graphql/resolvers/concerns/resolves_ids_spec.rb index 1dd27c0eff0..84741b7a603 100644 --- a/spec/graphql/resolvers/concerns/resolves_ids_spec.rb +++ b/spec/graphql/resolvers/concerns/resolves_ids_spec.rb @@ -3,33 +3,32 @@ require 'spec_helper' RSpec.describe ResolvesIds do + include GraphqlHelpers + # gid://gitlab/Project/6 # gid://gitlab/Issue/6 # gid://gitlab/Project/6 gid://gitlab/Issue/6 context 'with a single project' do - let(:ids) { 'gid://gitlab/Project/6' } - let(:type) { ::Types::GlobalIDType[::Project] } + let(:ids) { global_id_of(model_name: 'Project', id: 6) } it 'returns the correct array' do - expect(resolve_ids).to match_array(['6']) + expect(resolve_ids).to contain_exactly('6') end end context 'with a single issue' do - let(:ids) { 'gid://gitlab/Issue/9' } - let(:type) { ::Types::GlobalIDType[::Issue] } + let(:ids) { global_id_of(model_name: 'Issue', id: 9) } it 'returns the correct array' do - expect(resolve_ids).to match_array(['9']) + expect(resolve_ids).to contain_exactly('9') end end context 'with multiple users' do - let(:ids) { ['gid://gitlab/User/7', 'gid://gitlab/User/13', 'gid://gitlab/User/21'] } - let(:type) { ::Types::GlobalIDType[::User] } + let(:ids) { [7, 13, 21].map { global_id_of(model_name: 'User', id: _1) } } it 'returns the correct array' do - expect(resolve_ids).to match_array(%w[7 13 21]) + expect(resolve_ids).to eq %w[7 13 21] end end @@ -38,6 +37,6 @@ RSpec.describe ResolvesIds do end def resolve_ids - mock_resolver.resolve_ids(ids, type) + mock_resolver.resolve_ids(ids) end end diff --git a/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb index a16e8821cb5..3fe1ec4b5a4 100644 --- a/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Resolvers::DesignManagement::DesignAtVersionResolver do let(:current_user) { user } let(:object) { issue.design_collection } - let(:global_id) { GitlabSchema.id_from_object(design_at_version).to_s } + let(:global_id) { GitlabSchema.id_from_object(design_at_version) } let(:design_at_version) { ::DesignManagement::DesignAtVersion.new(design: design_a, version: version_a) } diff --git a/spec/graphql/resolvers/design_management/design_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_resolver_spec.rb index 4c8b3116875..0915dddf438 100644 --- a/spec/graphql/resolvers/design_management/design_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/design_resolver_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Resolvers::DesignManagement::DesignResolver do create(:design, issue: create(:issue, project: project), versions: [create(:design_version)]) end - let(:args) { { id: GitlabSchema.id_from_object(first_design).to_s } } + let(:args) { { id: GitlabSchema.id_from_object(first_design) } } let(:gql_context) { { current_user: current_user } } before do @@ -50,7 +50,7 @@ RSpec.describe Resolvers::DesignManagement::DesignResolver do end context 'when both arguments have been passed' do - let(:args) { { filename: first_design.filename, id: GitlabSchema.id_from_object(first_design).to_s } } + let(:args) { { filename: first_design.filename, id: GitlabSchema.id_from_object(first_design) } } it 'generates an error' do expect_graphql_error_to_be_created(::Gitlab::Graphql::Errors::ArgumentError, /may/) do @@ -71,15 +71,6 @@ RSpec.describe Resolvers::DesignManagement::DesignResolver do expect(resolve_design).to be_nil end end - - context 'the ID does not belong to a design at all' do - let(:args) { { id: global_id_of(issue) } } - let(:msg) { /does not represent an instance of DesignManagement::Design/ } - - it 'complains meaningfully' do - expect { resolve_design }.to raise_error(msg) - end - end end context 'by filename' do diff --git a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb index b091e58b06f..64eae14d888 100644 --- a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb @@ -109,6 +109,8 @@ RSpec.describe Resolvers::DesignManagement::DesignsResolver do end def resolve_designs - resolve(described_class, obj: issue.design_collection, args: args, ctx: gql_context) + Gitlab::Graphql::Lazy.force( + resolve(described_class, obj: issue.design_collection, args: args, ctx: gql_context) + ) end end diff --git a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb index 8b9874c3580..00f37a8e5f6 100644 --- a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb @@ -50,15 +50,6 @@ RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do it { is_expected.to be_nil } end - - context 'we pass the id of something that is not a design_version' do - let(:params) { { id: global_id_of(project) } } - let(:appropriate_error) { ::GraphQL::CoercionError } - - it 'raises an appropriate error' do - expect { result }.to raise_error(appropriate_error) - end - end end def resolve_version(obj, context = { current_user: current_user }) diff --git a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb index d98138f6385..8eab0222cf6 100644 --- a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb @@ -18,8 +18,7 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do let(:project) { issue.project } let(:params) { {} } let(:current_user) { authorized_user } - let(:parent_args) { { irrelevant: 1.2 } } - let(:parent) { double('Parent', parent: nil, irep_node: double(arguments: parent_args)) } + let(:query_context) { { current_user: current_user } } before do enable_design_management @@ -107,7 +106,9 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do end context 'by at_version in parent' do - let(:parent_args) { { atVersion: global_id_of(first_version) } } + before do + query_context[:at_version_argument] = first_version.to_global_id + end it_behaves_like 'a query for all_versions up to the first_version' end @@ -126,8 +127,8 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do it_behaves_like 'a source of versions' end - def resolve_versions(obj, context = { current_user: current_user }) - eager_resolve(resolver, obj: obj, parent: parent, args: params, ctx: context) + def resolve_versions(obj) + eager_resolve(resolver, obj: obj, args: params, ctx: query_context) end end end diff --git a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb index 2aef483ac95..f80b33e644e 100644 --- a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb +++ b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb @@ -8,28 +8,23 @@ RSpec.describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do let_it_be(:project) { create(:project) } let_it_be(:current_user) { create(:user) } - let(:issue_details_service) { spy('ErrorTracking::IssueDetailsService') } + let(:issue_details_service) { instance_double('ErrorTracking::IssueDetailsService') } + let(:service_response) { {} } - specify do - expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryDetailedErrorType) + before_all do + project.add_developer(current_user) end before do - project.add_developer(current_user) - allow(ErrorTracking::IssueDetailsService) .to receive(:new) - .and_return issue_details_service - end + .and_return(issue_details_service) - shared_examples 'it resolves to nil' do - it 'resolves to nil' do - allow(issue_details_service).to receive(:execute) - .and_return(issue: nil) + allow(issue_details_service).to receive(:execute).and_return(service_response) + end - result = resolve_error(args) - expect(result).to be_nil - end + specify do + expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryDetailedErrorType) end describe '#resolve' do @@ -41,13 +36,9 @@ RSpec.describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do expect(issue_details_service).to have_received(:execute) end - context 'error matched' do - let(:detailed_error) { build(:error_tracking_sentry_detailed_error) } - - before do - allow(issue_details_service).to receive(:execute) - .and_return(issue: detailed_error) - end + context 'when error matches' do + let(:detailed_error) { build_stubbed(:error_tracking_sentry_detailed_error) } + let(:service_response) { { issue: detailed_error } } it 'resolves to a detailed error' do expect(resolve_error(args)).to eq detailed_error @@ -58,24 +49,23 @@ RSpec.describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do end end - context 'if id does not match issue' do - it_behaves_like 'it resolves to nil' - end - - context 'blank id' do - let(:args) { { id: '' } } + context 'when id does not match issue' do + let(:service_response) { { issue: nil } } - it 'responds with an error' do - expect { resolve_error(args) }.to raise_error(::GraphQL::CoercionError) + it 'resolves to nil' do + result = resolve_error(args) + expect(result).to be_nil end end end + private + def resolve_error(args = {}, context = { current_user: current_user }) resolve(described_class, obj: project, args: args, ctx: context) end def issue_global_id(issue_id) - Gitlab::ErrorTracking::DetailedError.new(id: issue_id).to_global_id.to_s + Gitlab::ErrorTracking::DetailedError.new(id: issue_id).to_global_id end end diff --git a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb index 20c2bdcd4e1..5834faea97e 100644 --- a/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb +++ b/spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb @@ -8,18 +8,22 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorCollectionResolver do let_it_be(:project) { create(:project) } let_it_be(:current_user) { create(:user) } - let(:list_issues_service) { spy('ErrorTracking::ListIssuesService') } + let(:list_issues_service) { instance_double('ErrorTracking::ListIssuesService') } - specify do - expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryErrorCollectionType) + before_all do + project.add_developer(current_user) end before do - project.add_developer(current_user) - allow(ErrorTracking::ListIssuesService) .to receive(:new) .and_return list_issues_service + + allow(list_issues_service).to receive(:external_url) + end + + specify do + expect(described_class).to have_nullable_graphql_type(Types::ErrorTracking::SentryErrorCollectionType) end describe '#resolve' do @@ -34,8 +38,7 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorCollectionResolver do .to receive(:external_url) .and_return(fake_url) - result = resolve_error_collection - expect(result.external_url).to eq fake_url + expect(resolve_error_collection.external_url).to eq fake_url end it 'provides the project' do diff --git a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb index 68badb8e333..65b6c551dde 100644 --- a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb +++ b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do let_it_be(:current_user) { create(:user) } let_it_be(:error_collection) { Gitlab::ErrorTracking::ErrorCollection.new(project: project) } - let(:list_issues_service) { spy('ErrorTracking::ListIssuesService') } + let(:list_issues_service) { instance_double('ErrorTracking::ListIssuesService') } let(:issues) { nil } let(:pagination) { nil } @@ -19,23 +19,25 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do end describe '#resolve' do + before do + allow(ErrorTracking::ListIssuesService) + .to receive(:new) + .and_return list_issues_service + + allow(list_issues_service).to receive(:execute).and_return({}) + end + context 'with insufficient user permission' do - let(:user) { create(:user) } + let(:current_user) { create(:user) } it 'returns nil' do - context = { current_user: user } - - expect(resolve_errors({}, context)).to eq nil + expect(resolve_errors).to eq nil end end context 'with sufficient permission' do - before do + before_all do project.add_developer(current_user) - - allow(ErrorTracking::ListIssuesService) - .to receive(:new) - .and_return list_issues_service end context 'when after arg given' do @@ -52,14 +54,9 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do end context 'when no issues fetched' do - before do - allow(list_issues_service) - .to receive(:execute) - .and_return( - issues: nil - ) - end it 'returns nil' do + expect(list_issues_service).to receive(:execute).and_return(issues: nil) + expect(resolve_errors).to eq nil end end diff --git a/spec/graphql/resolvers/incident_management/timeline_events_resolver_spec.rb b/spec/graphql/resolvers/incident_management/timeline_events_resolver_spec.rb new file mode 100644 index 00000000000..046cf242d56 --- /dev/null +++ b/spec/graphql/resolvers/incident_management/timeline_events_resolver_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::IncidentManagement::TimelineEventsResolver do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:incident) { create(:incident, project: project) } + let_it_be(:first_timeline_event) do + create(:incident_management_timeline_event, project: project, incident: incident) + end + + let_it_be(:second_timeline_event) do + create(:incident_management_timeline_event, project: project, incident: incident) + end + + let(:args) { { incident_id: incident.to_global_id } } + let(:resolver) { described_class } + + subject(:resolved_timeline_events) { sync(resolve_timeline_events(args, current_user: current_user).to_a) } + + before do + project.add_guest(current_user) + end + + specify do + expect(resolver).to have_nullable_graphql_type(Types::IncidentManagement::TimelineEventType.connection_type) + end + + it 'returns timeline events', :aggregate_failures do + expect(resolved_timeline_events.length).to eq(2) + expect(resolved_timeline_events.first).to be_a(::IncidentManagement::TimelineEvent) + end + + context 'when user does not have permissions' do + let(:non_member) { create(:user) } + + subject(:resolved_timeline_events) { sync(resolve_timeline_events(args, current_user: non_member).to_a) } + + before do + project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + end + + it 'returns no timeline events' do + expect(resolved_timeline_events.length).to eq(0) + end + end + + context 'when resolving a single item' do + let(:resolver) { described_class.single } + + subject(:resolved_timeline_event) { sync(resolve_timeline_events(args, current_user: current_user)) } + + context 'when id given' do + let(:args) { { incident_id: incident.to_global_id, id: first_timeline_event.to_global_id } } + + it 'returns the timeline event' do + expect(resolved_timeline_event).to eq(first_timeline_event) + end + end + end + + private + + def resolve_timeline_events(args = {}, context = { current_user: current_user }) + resolve(resolver, obj: incident, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 81aeee0a3d2..e6ec9d8c895 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -389,6 +389,34 @@ RSpec.describe Resolvers::IssuesResolver do end end + describe 'filtering by crm' do + let_it_be(:organization) { create(:organization, group: group) } + let_it_be(:contact1) { create(:contact, group: group, organization: organization) } + let_it_be(:contact2) { create(:contact, group: group, organization: organization) } + let_it_be(:contact3) { create(:contact, group: group) } + let_it_be(:crm_issue1) { create(:issue, project: project) } + let_it_be(:crm_issue2) { create(:issue, project: project) } + let_it_be(:crm_issue3) { create(:issue, project: project) } + + before_all do + create(:issue_customer_relations_contact, issue: crm_issue1, contact: contact1) + create(:issue_customer_relations_contact, issue: crm_issue2, contact: contact2) + create(:issue_customer_relations_contact, issue: crm_issue3, contact: contact3) + end + + context 'contact' do + it 'returns only the issues for the contact' do + expect(resolve_issues({ crm_contact_id: contact1.id })).to contain_exactly(crm_issue1) + end + end + + context 'organization' do + it 'returns only the issues for the contact' do + expect(resolve_issues({ crm_organization_id: organization.id })).to contain_exactly(crm_issue1, crm_issue2) + end + end + end + describe 'sorting' do context 'when sorting by created' do it 'sorts issues ascending' do @@ -603,13 +631,13 @@ RSpec.describe Resolvers::IssuesResolver do end it 'finds a specific issue with iid', :request_store do - result = batch_sync(max_queries: 6) { resolve_issues(iid: issue1.iid).to_a } + result = batch_sync(max_queries: 7) { resolve_issues(iid: issue1.iid).to_a } expect(result).to contain_exactly(issue1) end it 'batches queries that only include IIDs', :request_store do - result = batch_sync(max_queries: 6) do + result = batch_sync(max_queries: 7) do [issue1, issue2] .map { |issue| resolve_issues(iid: issue.iid.to_s) } .flat_map(&:to_a) @@ -619,7 +647,7 @@ RSpec.describe Resolvers::IssuesResolver do end it 'finds a specific issue with iids', :request_store do - result = batch_sync(max_queries: 6) do + result = batch_sync(max_queries: 7) do resolve_issues(iids: [issue1.iid]).to_a end diff --git a/spec/graphql/resolvers/package_pipelines_resolver_spec.rb b/spec/graphql/resolvers/package_pipelines_resolver_spec.rb index c757c876616..a52dee59bc6 100644 --- a/spec/graphql/resolvers/package_pipelines_resolver_spec.rb +++ b/spec/graphql/resolvers/package_pipelines_resolver_spec.rb @@ -9,10 +9,23 @@ RSpec.describe Resolvers::PackagePipelinesResolver do let_it_be(:pipelines) { create_list(:ci_pipeline, 3, project: package.project) } let(:user) { package.project.first_owner } - let(:args) { {} } describe '#resolve' do - subject { resolve(described_class, obj: package, args: args, ctx: { current_user: user }) } + let(:returned_pipelines) { graphql_dig_at(subject, 'data', 'package', 'pipelines', 'nodes') } + let(:returned_errors) { graphql_dig_at(subject, 'errors', 'message') } + let(:pagination_args) { {} } + let(:query) do + pipelines_nodes = 'nodes { id }' + graphql_query_for( + :package, + { id: global_id_of(package) }, + query_graphql_field('pipelines', pagination_args, pipelines_nodes) + ) + end + + subject do + GitlabSchema.execute(query, context: { current_user: user }) + end before do pipelines.each do |pipeline| @@ -20,67 +33,115 @@ RSpec.describe Resolvers::PackagePipelinesResolver do end end - it { is_expected.to contain_exactly(*pipelines) } + it 'contains the expected pipelines' do + expect_to_contain_exactly(*pipelines) + end + + context 'with valid after' do + let(:pagination_args) { { first: 1, after: encode_cursor(id: pipelines[1].id) } } + + it 'contains the expected pipelines' do + expect_to_contain_exactly(pipelines[0]) + end + end + + context 'with valid before' do + let(:pagination_args) { { last: 1, before: encode_cursor(id: pipelines[1].id) } } + + it 'contains the expected pipelines' do + expect_to_contain_exactly(pipelines[2]) + end + end context 'with invalid after' do - let(:args) { { first: 1, after: 'not_json_string' } } + let(:pagination_args) { { first: 1, after: 'not_json_string' } } it 'generates an argument error' do - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do - subject - end + expect(returned_errors).to include('Please provide a valid cursor') end end context 'with invalid after key' do - let(:args) { { first: 1, after: encode_cursor(foo: 3) } } + let(:pagination_args) { { first: 1, after: encode_cursor(foo: 3) } } it 'generates an argument error' do - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do - subject - end + expect(returned_errors).to include('Please provide a valid cursor') end end context 'with invalid before' do - let(:args) { { last: 1, before: 'not_json_string' } } + let(:pagination_args) { { last: 1, before: 'not_json_string' } } it 'generates an argument error' do - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do - subject - end + expect(returned_errors).to include('Please provide a valid cursor') end end context 'with invalid before key' do - let(:args) { { last: 1, before: encode_cursor(foo: 3) } } + let(:pagination_args) { { last: 1, before: encode_cursor(foo: 3) } } it 'generates an argument error' do - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do - subject - end + expect(returned_errors).to include('Please provide a valid cursor') end end - context 'field options' do - let(:field) do - field_options = described_class.field_options.merge( - owner: resolver_parent, - name: 'dummy_field' - ) - ::Types::BaseField.new(**field_options) - end + context 'with unauthorized user' do + let_it_be(:user) { create(:user) } - it 'sets them properly' do - expect(field).not_to be_connection - expect(field.extras).to match_array([:lookahead]) + it 'returns nothing' do + expect(returned_pipelines).to be_nil end end - context 'with unauthorized user' do - let_it_be(:user) { create(:user) } + context 'with many packages' do + let_it_be_with_reload(:other_package) { create(:package, project: package.project) } + let_it_be(:other_pipelines) { create_list(:ci_pipeline, 3, project: package.project) } + + let(:returned_pipelines) do + graphql_dig_at(subject, 'data', 'project', 'packages', 'nodes', 'pipelines', 'nodes') + end - it { is_expected.to be_nil } + let(:query) do + pipelines_query = query_graphql_field('pipelines', pagination_args, 'nodes { id }') + <<~QUERY + { + project(fullPath: "#{package.project.full_path}") { + packages { + nodes { #{pipelines_query} } + } + } + } + QUERY + end + + before do + other_pipelines.each do |pipeline| + create(:package_build_info, package: other_package, pipeline: pipeline) + end + end + + it 'contains the expected pipelines' do + expect_to_contain_exactly(*(pipelines + other_pipelines)) + end + + it 'handles n+1 situations' do + control = ActiveRecord::QueryRecorder.new do + GitlabSchema.execute(query, context: { current_user: user }) + end + + create_package_with_pipelines(package.project) + + expectation = expect { GitlabSchema.execute(query, context: { current_user: user }) } + + expectation.not_to exceed_query_limit(control) + end + + def create_package_with_pipelines(project) + extra_package = create(:package, project: project) + create_list(:ci_pipeline, 3, project: project).each do |pipeline| + create(:package_build_info, package: extra_package, pipeline: pipeline) + end + end end def encode_cursor(json) @@ -89,5 +150,25 @@ RSpec.describe Resolvers::PackagePipelinesResolver do nonce: true ) end + + def expect_to_contain_exactly(*pipelines) + entities = pipelines.map { |pipeline| a_graphql_entity_for(pipeline) } + expect(returned_pipelines).to match_array(entities) + end + end + + describe '.field options' do + let(:field) do + field_options = described_class.field_options.merge( + owner: resolver_parent, + name: 'dummy_field' + ) + ::Types::BaseField.new(**field_options) + end + + it 'sets them properly' do + expect(field).not_to be_connection + expect(field.extras).to match_array([:lookahead]) + end end end diff --git a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb index b963f2509db..1d04db3ea6e 100644 --- a/spec/graphql/resolvers/projects/snippets_resolver_spec.rb +++ b/spec/graphql/resolvers/projects/snippets_resolver_spec.rb @@ -58,12 +58,6 @@ RSpec.describe Resolvers::Projects::SnippetsResolver do expect(snippets).to contain_exactly(project_snippet, other_project_snippet) end - - it 'returns an error if the gid is invalid' do - expect do - resolve_snippets(args: { ids: ['foo'] }) - end.to raise_error(GraphQL::CoercionError) - end end context 'when no project is provided' do diff --git a/spec/graphql/resolvers/snippets_resolver_spec.rb b/spec/graphql/resolvers/snippets_resolver_spec.rb index f9feb8901cd..ee9a6e67243 100644 --- a/spec/graphql/resolvers/snippets_resolver_spec.rb +++ b/spec/graphql/resolvers/snippets_resolver_spec.rb @@ -40,12 +40,6 @@ RSpec.describe Resolvers::SnippetsResolver do expect(snippets).to contain_exactly(personal_snippet, project_snippet) end - - it 'returns an error if the param id is invalid' do - expect do - resolve_snippets(args: { author_id: 'foo' }) - end.to raise_error(GraphQL::CoercionError) - end end it 'returns the snippets by type' do @@ -61,12 +55,6 @@ RSpec.describe Resolvers::SnippetsResolver do expect(snippets).to contain_exactly(project_snippet, other_project_snippet) end - - it 'returns an error if the param id is invalid' do - expect do - resolve_snippets(args: { project_id: 'foo' }) - end.to raise_error(GraphQL::CoercionError) - end end it 'returns the snippets by visibility' do @@ -98,16 +86,6 @@ RSpec.describe Resolvers::SnippetsResolver do expect(found).to match_array(snippets) end - it 'returns an error if the id cannot be coerced' do - args = { - ids: [personal_snippet.to_global_id, 'foo'] - } - - expect do - resolve_snippets(args: args) - end.to raise_error(GraphQL::CoercionError, '"foo" is not a valid Global ID') - end - it 'generates an error if both project and author are provided' do expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do args = { diff --git a/spec/graphql/resolvers/timelog_resolver_spec.rb b/spec/graphql/resolvers/timelog_resolver_spec.rb index 84fa2932829..da2747fdf72 100644 --- a/spec/graphql/resolvers/timelog_resolver_spec.rb +++ b/spec/graphql/resolvers/timelog_resolver_spec.rb @@ -265,7 +265,7 @@ RSpec.describe Resolvers::TimelogResolver do context 'when > `default_max_page_size` records' do let(:object) { nil } let!(:timelog_list) { create_list(:timelog, 101, issue: issue) } - let(:args) { { project_id: "gid://gitlab/Project/#{project.id}" } } + let(:args) { { project_id: global_id_of(project) } } let(:extra_args) { {} } it 'pagination returns `default_max_page_size` and sets `has_next_page` true' do diff --git a/spec/graphql/resolvers/users/snippets_resolver_spec.rb b/spec/graphql/resolvers/users/snippets_resolver_spec.rb index 04fe3213a99..12baed2560e 100644 --- a/spec/graphql/resolvers/users/snippets_resolver_spec.rb +++ b/spec/graphql/resolvers/users/snippets_resolver_spec.rb @@ -64,16 +64,6 @@ RSpec.describe Resolvers::Users::SnippetsResolver do expect(found).to match_array(snippets) end - - it 'returns an error if the gid is invalid' do - args = { - ids: [global_id_of(private_personal_snippet), 'foo'] - } - - expect do - resolve_snippets(args: args) - end.to raise_error(GraphQL::CoercionError) - end end context 'when user profile is private' do diff --git a/spec/graphql/resolvers/work_item_resolver_spec.rb b/spec/graphql/resolvers/work_item_resolver_spec.rb index bfa0cf1d8a2..c44ed395102 100644 --- a/spec/graphql/resolvers/work_item_resolver_spec.rb +++ b/spec/graphql/resolvers/work_item_resolver_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Resolvers::WorkItemResolver do let(:current_user) { developer } - subject(:resolved_work_item) { resolve_work_item('id' => work_item.to_gid.to_s) } + subject(:resolved_work_item) { resolve_work_item('id' => work_item.to_gid) } context 'when the user can read the work item' do it { is_expected.to eq(work_item) } diff --git a/spec/graphql/subscriptions/issuable_updated_spec.rb b/spec/graphql/subscriptions/issuable_updated_spec.rb index c15b4f532ef..0b8fcf67513 100644 --- a/spec/graphql/subscriptions/issuable_updated_spec.rb +++ b/spec/graphql/subscriptions/issuable_updated_spec.rb @@ -39,14 +39,6 @@ RSpec.describe Subscriptions::IssuableUpdated do expect { subject }.to raise_error(GraphQL::ExecutionError) end end - - context 'when a GraphQL::Types::ID is provided' do - let(:issuable_id) { issue.to_gid.to_s } - - it 'raises an exception' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) - end - end end context 'subscription updates' do diff --git a/spec/graphql/types/alert_management/domain_filter_enum_spec.rb b/spec/graphql/types/alert_management/domain_filter_enum_spec.rb index 2111a33b8b4..9e8d7589352 100644 --- a/spec/graphql/types/alert_management/domain_filter_enum_spec.rb +++ b/spec/graphql/types/alert_management/domain_filter_enum_spec.rb @@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['AlertManagementDomainFilter'] do specify { expect(described_class.graphql_name).to eq('AlertManagementDomainFilter') } it 'exposes all the severity values' do - expect(described_class.values.keys).to include(*%w[threat_monitoring operations]) + expect(described_class.values.keys).to include(*%w[operations threat_monitoring]) end end diff --git a/spec/graphql/types/ci/config/config_type_spec.rb b/spec/graphql/types/ci/config/config_type_spec.rb index 0012ae9f51f..78dd5afc1d8 100644 --- a/spec/graphql/types/ci/config/config_type_spec.rb +++ b/spec/graphql/types/ci/config/config_type_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Types::Ci::Config::ConfigType do it 'exposes the expected fields' do expected_fields = %i[ errors + includes mergedYaml stages status diff --git a/spec/graphql/types/ci/config/include_type_enum_spec.rb b/spec/graphql/types/ci/config/include_type_enum_spec.rb new file mode 100644 index 00000000000..a88316ae6f2 --- /dev/null +++ b/spec/graphql/types/ci/config/include_type_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiConfigIncludeType'] do + it { expect(described_class.graphql_name).to eq('CiConfigIncludeType') } + + it 'exposes all the existing include types' do + expect(described_class.values.keys).to match_array(%w[remote local file template]) + end +end diff --git a/spec/graphql/types/ci/config/include_type_spec.rb b/spec/graphql/types/ci/config/include_type_spec.rb new file mode 100644 index 00000000000..971e185ccd9 --- /dev/null +++ b/spec/graphql/types/ci/config/include_type_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::Config::IncludeType do + specify { expect(described_class.graphql_name).to eq('CiConfigInclude') } + + it 'exposes the expected fields' do + expected_fields = %i[ + context_project + context_sha + extra + location + blob + raw + type + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb index 7697cd0ef79..26ac7a4da8d 100644 --- a/spec/graphql/types/ci/runner_type_spec.rb +++ b/spec/graphql/types/ci/runner_type_spec.rb @@ -11,8 +11,8 @@ RSpec.describe GitlabSchema.types['CiRunner'] do expected_fields = %w[ id description created_at contacted_at maximum_timeout access_level active paused status 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 - groups projects jobs token_expires_at + project_count job_count admin_url edit_admin_url user_permissions executor_name architecture_name platform_name + maintenance_note groups projects jobs token_expires_at ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/ci/runner_upgrade_status_type_enum_spec.rb b/spec/graphql/types/ci/runner_upgrade_status_type_enum_spec.rb new file mode 100644 index 00000000000..81a852471b9 --- /dev/null +++ b/spec/graphql/types/ci/runner_upgrade_status_type_enum_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::Ci::RunnerUpgradeStatusTypeEnum do + specify { expect(described_class.graphql_name).to eq('CiRunnerUpgradeStatusType') } + + it 'exposes all upgrade status values' do + expect(described_class.values.keys).to eq( + ['UNKNOWN'] + ::Gitlab::Ci::RunnerUpgradeCheck::STATUSES.map { |sym, _| sym.to_s.upcase } + ) + end +end diff --git a/spec/graphql/types/color_type_spec.rb b/spec/graphql/types/color_type_spec.rb new file mode 100644 index 00000000000..57c26e12b51 --- /dev/null +++ b/spec/graphql/types/color_type_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::ColorType do + let(:hex) { '#663399' } + let(:color_name) { 'rebeccapurple' } + let(:color) { ::Gitlab::Color.of(hex) } + let(:named_color) { ::Gitlab::Color.of(color_name) } + + specify { expect(described_class.graphql_name).to eq('Color') } + + it 'coerces Color object into hex string' do + expect(described_class.coerce_isolated_result(color)).to eq(hex) + end + + it 'coerces an hex string into Color object' do + expect(described_class.coerce_isolated_input(hex)).to eq(color) + end + + it 'coerces an named Color into hex string' do + expect(described_class.coerce_isolated_result(named_color)).to eq(hex) + end + + it 'coerces an named color into Color object' do + expect(described_class.coerce_isolated_input(color_name)).to eq(named_color) + end + + it 'rejects invalid input' do + expect { described_class.coerce_isolated_input('not valid') } + .to raise_error(GraphQL::CoercionError) + end + + it 'rejects nil' do + expect { described_class.coerce_isolated_input(nil) } + .to raise_error(GraphQL::CoercionError) + end +end diff --git a/spec/graphql/types/container_expiration_policy_type_spec.rb b/spec/graphql/types/container_expiration_policy_type_spec.rb index 9e9ddaf1cb0..95c2be9dfcc 100644 --- a/spec/graphql/types/container_expiration_policy_type_spec.rb +++ b/spec/graphql/types/container_expiration_policy_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['ContainerExpirationPolicy'] do specify { expect(described_class.description).to eq('A tag expiration policy designed to keep only the images that matter most') } - specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) } + specify { expect(described_class).to require_graphql_authorizations(:admin_container_image) } describe 'older_than field' do subject { described_class.fields['olderThan'] } diff --git a/spec/graphql/types/container_repository_details_type_spec.rb b/spec/graphql/types/container_repository_details_type_spec.rb index d94516c6fce..62e72089e09 100644 --- a/spec/graphql/types/container_repository_details_type_spec.rb +++ b/spec/graphql/types/container_repository_details_type_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status tags size - project migration_state] + project migration_state last_cleanup_deleted_tags_count] it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') } diff --git a/spec/graphql/types/container_repository_type_spec.rb b/spec/graphql/types/container_repository_type_spec.rb index 9815449dd68..bc92fa24050 100644 --- a/spec/graphql/types/container_repository_type_spec.rb +++ b/spec/graphql/types/container_repository_type_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['ContainerRepository'] do fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status project - migration_state] + migration_state last_cleanup_deleted_tags_count] it { expect(described_class.graphql_name).to eq('ContainerRepository') } diff --git a/spec/graphql/types/current_user_todos_type_spec.rb b/spec/graphql/types/current_user_todos_type_spec.rb index a0015e96788..4ce97e1c006 100644 --- a/spec/graphql/types/current_user_todos_type_spec.rb +++ b/spec/graphql/types/current_user_todos_type_spec.rb @@ -3,7 +3,214 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['CurrentUserTodos'] do + include GraphqlHelpers + specify { expect(described_class.graphql_name).to eq('CurrentUserTodos') } specify { expect(described_class).to have_graphql_fields(:current_user_todos).only } + + # Request store is necessary to prevent duplicate max-member-access lookups + describe '.current_user_todos', :request_store, :aggregate_failures do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:issue_a) { create(:issue, project: project) } + let_it_be(:issue_b) { create(:issue, project: project) } + + let_it_be(:todo_a) { create(:todo, :pending, user: user, project: project, target: issue_a) } + let_it_be(:todo_b) { create(:todo, :done, user: user, project: project, target: issue_a) } + let_it_be(:todo_c) { create(:todo, :pending, user: user, project: project, target: issue_b) } + let_it_be(:todo_d) { create(:todo, :done, user: user, project: project, target: issue_b) } + + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + let_it_be(:todo_e) { create(:todo, :pending, user: user, project: project, target: merge_request) } + + let(:object_type) do + fresh_object_type('HasTodos').tap { _1.implements(Types::CurrentUserTodos) } + end + + let(:id_enum) do + Class.new(Types::BaseEnum) do + graphql_name 'AorB' + + value 'A' + value 'B' + end + end + + let(:query_type) do + i_a = issue_a + i_b = issue_b + issue_id = id_enum + mr = merge_request + + q = fresh_object_type('Query') + + q.field :issue, null: false, type: object_type do + argument :id, type: issue_id, required: true + end + + q.field :mr, null: false, type: object_type + + q.define_method(:issue) do |id:| + case id + when 'A' + i_a + when 'B' + i_b + end + end + + q.define_method(:mr) { mr } + + q + end + + let(:todo_fragment) do + <<-GQL + fragment todos on HasTodos { + todos: currentUserTodos { + nodes { id } + } + } + GQL + end + + let(:base_query) do + <<-GQL + query { + issue(id: A) { ... todos } + } + #{todo_fragment} + GQL + end + + let(:query_without_state_arguments) do + <<-GQL + query { + a: issue(id: A) { + ... todos + } + b: issue(id: B) { + ... todos + } + c: mr { + ... todos + } + d: mr { + ... todos + } + e: issue(id: A) { + ... todos + } + } + + #{todo_fragment} + GQL + end + + let(:with_state_arguments) do + <<-GQL + query { + a: issue(id: A) { + todos: currentUserTodos(state: pending) { nodes { id } } + } + b: issue(id: B) { + todos: currentUserTodos(state: done) { nodes { id } } + } + c: mr { + ... todos + } + } + + #{todo_fragment} + GQL + end + + before_all do + project.add_developer(user) + end + + it 'batches todo lookups, linear in the number of target types/state arguments' do + # The baseline is 4 queries: + # + # When we batch queries, we see the following three groups of queries: + # # user authorization + # 1. SELECT "users".* FROM "users" + # INNER JOIN "project_authorizations" + # ON "users"."id" = "project_authorizations"."user_id" + # WHERE "project_authorizations"."project_id" = project_id + # AND "project_authorizations"."access_level" = 50 + # 2. SELECT MAX("project_authorizations"."access_level") AS maximum_access_level, + # "project_authorizations"."user_id" AS project_authorizations_user_id + # FROM "project_authorizations" + # WHERE "project_authorizations"."project_id" = project_id + # AND "project_authorizations"."user_id" = user_id + # GROUP BY "project_authorizations"."user_id" + # + # # find todos for issues + # 1. SELECT "todos".* FROM "todos" + # WHERE "todos"."user_id" = user_id + # AND ("todos"."state" IN ('done','pending')) + # AND "todos"."target_id" IN (issue_a, issue_b) + # AND "todos"."target_type" = 'Issue' ORDER BY "todos"."id" DESC + # + # # find todos for merge_requests + # 1. SELECT "todos".* FROM "todos" WHERE "todos"."user_id" = user_id + # AND ("todos"."state" IN ('done','pending')) + # AND "todos"."target_id" = merge_request + # AND "todos"."target_type" = 'MergeRequest' ORDER BY "todos"."id" DESC + baseline = ActiveRecord::QueryRecorder.new do + execute_query(query_type, graphql: base_query) + end + + expect do + execute_query(query_type, graphql: query_without_state_arguments) + end.not_to exceed_query_limit(baseline) # at present this is 3 + + expect do + execute_query(query_type, graphql: with_state_arguments) + end.not_to exceed_query_limit(baseline.count + 1) + end + + it 'returns correct data' do + result = execute_query(query_type, + graphql: query_without_state_arguments, + raise_on_error: true).to_h + + expect(result.dig('data', 'a', 'todos', 'nodes')).to contain_exactly( + a_graphql_entity_for(todo_a), + a_graphql_entity_for(todo_b) + ) + expect(result.dig('data', 'b', 'todos', 'nodes')).to contain_exactly( + a_graphql_entity_for(todo_c), + a_graphql_entity_for(todo_d) + ) + expect(result.dig('data', 'c', 'todos', 'nodes')).to contain_exactly( + a_graphql_entity_for(todo_e) + ) + expect(result.dig('data', 'd', 'todos', 'nodes')).to contain_exactly( + a_graphql_entity_for(todo_e) + ) + expect(result.dig('data', 'e', 'todos', 'nodes')).to contain_exactly( + a_graphql_entity_for(todo_a), + a_graphql_entity_for(todo_b) + ) + end + + it 'returns correct data, when state arguments are supplied' do + result = execute_query(query_type, + raise_on_error: true, + graphql: with_state_arguments).to_h + + expect(result.dig('data', 'a', 'todos', 'nodes')).to contain_exactly( + a_graphql_entity_for(todo_a) + ) + expect(result.dig('data', 'b', 'todos', 'nodes')).to contain_exactly( + a_graphql_entity_for(todo_d) + ) + expect(result.dig('data', 'c', 'todos', 'nodes')).to contain_exactly( + a_graphql_entity_for(todo_e) + ) + end + end end diff --git a/spec/graphql/types/customer_relations/contact_type_spec.rb b/spec/graphql/types/customer_relations/contact_type_spec.rb index bb447f405b6..965cc4b60e6 100644 --- a/spec/graphql/types/customer_relations/contact_type_spec.rb +++ b/spec/graphql/types/customer_relations/contact_type_spec.rb @@ -3,7 +3,20 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['CustomerRelationsContact'] do - let(:fields) { %i[id organization first_name last_name phone email description created_at updated_at] } + let(:fields) do + %w[ + id + organization + first_name + last_name + phone + email + description + active + created_at + updated_at + ] + end it { expect(described_class.graphql_name).to eq('CustomerRelationsContact') } it { expect(described_class).to have_graphql_fields(fields) } diff --git a/spec/graphql/types/customer_relations/organization_type_spec.rb b/spec/graphql/types/customer_relations/organization_type_spec.rb index 93844df1239..ae1f41ca098 100644 --- a/spec/graphql/types/customer_relations/organization_type_spec.rb +++ b/spec/graphql/types/customer_relations/organization_type_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['CustomerRelationsOrganization'] do - let(:fields) { %i[id name default_rate description created_at updated_at] } + let(:fields) { %i[id name default_rate description active created_at updated_at] } it { expect(described_class.graphql_name).to eq('CustomerRelationsOrganization') } it { expect(described_class).to have_graphql_fields(fields) } diff --git a/spec/graphql/types/dependency_proxy/group_setting_type_spec.rb b/spec/graphql/types/dependency_proxy/group_setting_type_spec.rb index 7c6d7b8aece..cd648cf4b4d 100644 --- a/spec/graphql/types/dependency_proxy/group_setting_type_spec.rb +++ b/spec/graphql/types/dependency_proxy/group_setting_type_spec.rb @@ -10,4 +10,10 @@ RSpec.describe GitlabSchema.types['DependencyProxySetting'] do expect(described_class).to include_graphql_fields(*expected_fields) end + + it { expect(described_class).to require_graphql_authorizations(:admin_dependency_proxy) } + + it { expect(described_class.graphql_name).to eq('DependencyProxySetting') } + + it { expect(described_class.description).to eq('Group-level Dependency Proxy settings') } end diff --git a/spec/graphql/types/dependency_proxy/image_ttl_group_policy_type_spec.rb b/spec/graphql/types/dependency_proxy/image_ttl_group_policy_type_spec.rb index 46347e0434f..af0f91a844e 100644 --- a/spec/graphql/types/dependency_proxy/image_ttl_group_policy_type_spec.rb +++ b/spec/graphql/types/dependency_proxy/image_ttl_group_policy_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['DependencyProxyImageTtlGroupPolicy'] do it { expect(described_class.description).to eq('Group-level Dependency Proxy TTL policy settings') } - it { expect(described_class).to require_graphql_authorizations(:read_dependency_proxy) } + it { expect(described_class).to require_graphql_authorizations(:admin_dependency_proxy) } it 'includes dependency proxy image ttl policy fields' do expected_fields = %w[enabled ttl created_at updated_at] diff --git a/spec/graphql/types/duration_type_spec.rb b/spec/graphql/types/duration_type_spec.rb index 5b88819f157..4199e6cc41b 100644 --- a/spec/graphql/types/duration_type_spec.rb +++ b/spec/graphql/types/duration_type_spec.rb @@ -17,11 +17,6 @@ RSpec.describe GitlabSchema.types['Duration'] do expect(described_class.coerce_isolated_input(0.5)).to eq(0.5) end - it 'rejects invalid input' do - expect { described_class.coerce_isolated_input('not valid') } - .to raise_error(GraphQL::CoercionError) - end - it 'rejects nil' do expect { described_class.coerce_isolated_input(nil) } .to raise_error(GraphQL::CoercionError) diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb index 8df92c818fc..a57db9234f1 100644 --- a/spec/graphql/types/global_id_type_spec.rb +++ b/spec/graphql/types/global_id_type_spec.rb @@ -246,131 +246,6 @@ RSpec.describe Types::GlobalIDType do end end - describe 'compatibility' do - def query(doc, vars) - GraphQL::Query.new(schema, document: doc, context: {}, variables: vars) - end - - def run_query(gql_query, vars) - query(GraphQL.parse(gql_query), vars).result - end - - all_types = [::GraphQL::Types::ID, ::Types::GlobalIDType, ::Types::GlobalIDType[::Project]] - - shared_examples 'a working query' do - # Simplified schema to test compatibility - let!(:schema) do - # capture values so they can be closed over - arg_type = argument_type - res_type = result_type - - project = Class.new(GraphQL::Schema::Object) do - graphql_name 'Project' - field :name, String, null: false - field :id, res_type, null: false, resolver_method: :global_id - - def global_id - object.to_global_id - end - end - - Class.new(GraphQL::Schema) do - query(Class.new(GraphQL::Schema::Object) do - graphql_name 'Query' - - field :project_by_id, project, null: true do - argument :id, arg_type, required: true - end - - # This is needed so that all types are always registered as input types - field :echo, String, null: true do - argument :id, ::GraphQL::Types::ID, required: false - argument :gid, ::Types::GlobalIDType, required: false - argument :pid, ::Types::GlobalIDType[::Project], required: false - end - - def project_by_id(id:) - gid = ::Types::GlobalIDType[::Project].coerce_isolated_input(id) - gid.model_class.find(gid.model_id) - end - - def echo(id: nil, gid: nil, pid: nil) - "id: #{id}, gid: #{gid}, pid: #{pid}" - end - end) - end - end - - it 'works' do - res = run_query(document, 'projectId' => project.to_global_id.to_s) - - expect(res['errors']).to be_blank - expect(res.dig('data', 'project', 'name')).to eq(project.name) - expect(res.dig('data', 'project', 'id')).to eq(project.to_global_id.to_s) - end - end - - context 'when the client declares the argument as ID the actual argument can be any type' do - let(:document) do - <<-GRAPHQL - query($projectId: ID!){ - project: projectById(id: $projectId) { - name, id - } - } - GRAPHQL - end - - where(:result_type, :argument_type) do - all_types.flat_map { |arg_type| all_types.zip([arg_type].cycle) } - end - - with_them do - it_behaves_like 'a working query' - end - end - - context 'when the client passes the argument as GlobalID' do - let(:document) do - <<-GRAPHQL - query($projectId: GlobalID!) { - project: projectById(id: $projectId) { - name, id - } - } - GRAPHQL - end - - let(:argument_type) { ::Types::GlobalIDType } - - where(:result_type) { all_types } - - with_them do - it_behaves_like 'a working query' - end - end - - context 'when the client passes the argument as ProjectID' do - let(:document) do - <<-GRAPHQL - query($projectId: ProjectID!) { - project: projectById(id: $projectId) { - name, id - } - } - GRAPHQL - end - - let(:argument_type) { ::Types::GlobalIDType[::Project] } - - where(:result_type) { all_types } - - with_them do - it_behaves_like 'a working query' - end - end - end - describe '.model_name_to_graphql_name' do it 'returns a graphql name for the given model name' do expect(described_class.model_name_to_graphql_name('DesignManagement::Design')).to eq('DesignManagementDesignID') diff --git a/spec/graphql/types/incident_management/timeline_event_type_spec.rb b/spec/graphql/types/incident_management/timeline_event_type_spec.rb new file mode 100644 index 00000000000..5a6bc461f20 --- /dev/null +++ b/spec/graphql/types/incident_management/timeline_event_type_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['TimelineEventType'] do + specify { expect(described_class.graphql_name).to eq('TimelineEventType') } + + specify { expect(described_class).to require_graphql_authorizations(:read_incident_management_timeline_event) } + + it 'exposes the expected fields' do + expected_fields = %i[ + id + author + updated_by_user + incident + note + note_html + promoted_from_note + editable + action + occurred_at + created_at + updated_at + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb index e9e92bbdc85..7ab254238fb 100644 --- a/spec/graphql/types/merge_request_type_spec.rb +++ b/spec/graphql/types/merge_request_type_spec.rb @@ -34,7 +34,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do milestone assignees reviewers participants subscribed labels discussion_locked time_estimate total_time_spent human_time_estimate human_total_time_spent reference author merged_at commit_count current_user_todos conflicts auto_merge_enabled approved_by source_branch_protected - default_merge_commit_message_with_description squash_on_merge available_auto_merge_strategies + squash_on_merge available_auto_merge_strategies has_ci mergeable commits committers commits_without_merge_commits squash security_auto_fix default_squash_commit_message auto_merge_strategy merge_user ] diff --git a/spec/graphql/types/mutation_type_spec.rb b/spec/graphql/types/mutation_type_spec.rb index 1fc46f2d511..95d835c88cf 100644 --- a/spec/graphql/types/mutation_type_spec.rb +++ b/spec/graphql/types/mutation_type_spec.rb @@ -7,14 +7,6 @@ RSpec.describe Types::MutationType do expect(described_class).to have_graphql_mutation(Mutations::MergeRequests::SetDraft) end - describe 'deprecated mutations' do - describe 'clusterAgentTokenDelete' do - let(:field) { get_field('clusterAgentTokenDelete') } - - it { expect(field.deprecation_reason).to eq('Tokens must be revoked with ClusterAgentTokenRevoke. Deprecated in 14.7.') } - end - end - def get_field(name) described_class.fields[GraphqlHelpers.fieldnamerize(name)] end diff --git a/spec/graphql/types/namespace/package_settings_type_spec.rb b/spec/graphql/types/namespace/package_settings_type_spec.rb index b9592d230ca..f63a0a7010f 100644 --- a/spec/graphql/types/namespace/package_settings_type_spec.rb +++ b/spec/graphql/types/namespace/package_settings_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['PackageSettings'] do specify { expect(described_class.description).to eq('Namespace-level Package Registry settings') } - specify { expect(described_class).to require_graphql_authorizations(:read_package_settings) } + specify { expect(described_class).to require_graphql_authorizations(:admin_package) } describe 'maven_duplicate_exception_regex field' do subject { described_class.fields['mavenDuplicateExceptionRegex'] } diff --git a/spec/graphql/types/packages/package_base_type_spec.rb b/spec/graphql/types/packages/package_base_type_spec.rb new file mode 100644 index 00000000000..7156f22c513 --- /dev/null +++ b/spec/graphql/types/packages/package_base_type_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PackageBase'] do + specify { expect(described_class.description).to eq('Represents a package in the Package Registry') } + + specify { expect(described_class).to require_graphql_authorizations(:read_package) } + + it 'includes all expected fields' do + expected_fields = %w[ + id name version package_type + created_at updated_at + project + tags metadata + status can_destroy + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + 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 ceeb000ff85..d5688fc64c5 100644 --- a/spec/graphql/types/packages/package_details_type_spec.rb +++ b/spec/graphql/types/packages/package_details_type_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['PackageDetailsType'] do + specify { expect(described_class.description).to eq('Represents a package details in the Package Registry') } + + specify { expect(described_class).to require_graphql_authorizations(:read_package) } + it 'includes all the package fields' do expected_fields = %w[ id name version created_at updated_at package_type tags project @@ -13,13 +17,4 @@ RSpec.describe GitlabSchema.types['PackageDetailsType'] do expect(described_class).to include_graphql_fields(*expected_fields) end - - it 'overrides the pipelines field' do - field = described_class.fields['pipelines'] - - expect(field).to have_graphql_type(Types::Ci::PipelineType.connection_type) - expect(field).to have_graphql_extension(Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension) - expect(field).to have_graphql_resolver(Resolvers::PackagePipelinesResolver) - expect(field).not_to be_connection - end end diff --git a/spec/graphql/types/packages/package_type_spec.rb b/spec/graphql/types/packages/package_type_spec.rb index 3267c765dc7..df8135ed87e 100644 --- a/spec/graphql/types/packages/package_type_spec.rb +++ b/spec/graphql/types/packages/package_type_spec.rb @@ -3,12 +3,16 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Package'] do - it 'includes all the package fields' do + specify { expect(described_class.description).to eq('Represents a package with pipelines in the Package Registry') } + + specify { expect(described_class).to require_graphql_authorizations(:read_package) } + + it 'includes all the package fields and pipelines' do expected_fields = %w[ id name version package_type created_at updated_at project - tags pipelines metadata versions + tags pipelines metadata status can_destroy ] diff --git a/spec/graphql/types/permission_types/work_item_spec.rb b/spec/graphql/types/permission_types/work_item_spec.rb new file mode 100644 index 00000000000..e604ce5d6e0 --- /dev/null +++ b/spec/graphql/types/permission_types/work_item_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::PermissionTypes::WorkItem do + it do + expected_permissions = [ + :read_work_item, :update_work_item, :delete_work_item + ] + + expected_permissions.each do |permission| + expect(described_class).to have_graphql_field(permission) + end + end +end diff --git a/spec/graphql/types/project_statistics_type_spec.rb b/spec/graphql/types/project_statistics_type_spec.rb index f515907b6a8..a958a5150aa 100644 --- a/spec/graphql/types/project_statistics_type_spec.rb +++ b/spec/graphql/types/project_statistics_type_spec.rb @@ -6,6 +6,7 @@ RSpec.describe GitlabSchema.types['ProjectStatistics'] do it 'has all the required fields' do expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size, :build_artifacts_size, :packages_size, :commit_count, - :wiki_size, :snippets_size, :pipeline_artifacts_size, :uploads_size) + :wiki_size, :snippets_size, :pipeline_artifacts_size, + :uploads_size, :container_registry_size) end end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 7433d465b38..a08bd717c72 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -29,6 +29,7 @@ RSpec.describe GitlabSchema.types['Project'] do grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments environment boards jira_import_status jira_imports services releases release alert_management_alerts alert_management_alert alert_management_alert_status_counts + incident_management_timeline_event incident_management_timeline_events container_expiration_policy service_desk_enabled service_desk_address issue_status_counts terraform_states alert_management_integrations container_repositories container_repositories_count diff --git a/spec/graphql/types/projects/topic_type_spec.rb b/spec/graphql/types/projects/topic_type_spec.rb index 01c19e111be..318307fa6f4 100644 --- a/spec/graphql/types/projects/topic_type_spec.rb +++ b/spec/graphql/types/projects/topic_type_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Types::Projects::TopicType do expect(described_class).to have_graphql_fields( :id, :name, + :title, :description, :description_html, :avatar_url diff --git a/spec/graphql/types/range_input_type_spec.rb b/spec/graphql/types/range_input_type_spec.rb index dbfcf4a41c7..239f4e4ba15 100644 --- a/spec/graphql/types/range_input_type_spec.rb +++ b/spec/graphql/types/range_input_type_spec.rb @@ -12,7 +12,7 @@ RSpec.describe ::Types::RangeInputType do input = { start: 1, end: 10 } output = { start: 1, end: 10 } - expect(type.coerce_isolated_input(input)).to eq(output) + expect(type.coerce_isolated_input(input).to_h).to eq(output) end it 'rejects inverted ranges' do @@ -28,7 +28,7 @@ RSpec.describe ::Types::RangeInputType do values: {}, object: nil ) - instance = described_class[of_integer].new(context: context, defaults_used: [], ruby_kwargs: {}) + instance = described_class[of_integer].new({}, context: context, defaults_used: [], ruby_kwargs: {}) expect(instance).to be_a_kind_of(described_class) expect(instance).to be_a_kind_of(described_class[of_integer]) diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb index 7818be6ee02..07c8378e7a6 100644 --- a/spec/graphql/types/root_storage_statistics_type_spec.rb +++ b/spec/graphql/types/root_storage_statistics_type_spec.rb @@ -8,7 +8,8 @@ RSpec.describe GitlabSchema.types['RootStorageStatistics'] do it 'has all the required fields' do expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size, :build_artifacts_size, :packages_size, :wiki_size, :snippets_size, - :pipeline_artifacts_size, :uploads_size, :dependency_proxy_size) + :pipeline_artifacts_size, :uploads_size, :dependency_proxy_size, + :container_registry_size) end specify { expect(described_class).to require_graphql_authorizations(:read_statistics) } diff --git a/spec/graphql/types/terraform/state_version_type_spec.rb b/spec/graphql/types/terraform/state_version_type_spec.rb index b015a2045da..6a17d932d03 100644 --- a/spec/graphql/types/terraform/state_version_type_spec.rb +++ b/spec/graphql/types/terraform/state_version_type_spec.rb @@ -52,8 +52,8 @@ RSpec.describe GitlabSchema.types['TerraformStateVersion'] do shared_examples 'returning latest version' do it 'returns latest version of terraform state' do - expect(execute.dig('data', 'project', 'terraformState', 'latestVersion', 'id')).to eq( - global_id_of(terraform_state.latest_version) + expect(execute.dig('data', 'project', 'terraformState', 'latestVersion')).to match a_graphql_entity_for( + terraform_state.latest_version ) end end diff --git a/spec/graphql/types/timeframe_type_spec.rb b/spec/graphql/types/timeframe_type_spec.rb index dfde3242897..288124ad24b 100644 --- a/spec/graphql/types/timeframe_type_spec.rb +++ b/spec/graphql/types/timeframe_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Timeframe'] do let(:output) { { start: Date.parse(input[:start]), end: Date.parse(input[:end]) } } it 'coerces ISO-dates into Time objects' do - expect(described_class.coerce_isolated_input(input)).to eq(output) + expect(described_class.coerce_isolated_input(input).to_h).to eq(output) end it 'rejects invalid input' do @@ -20,7 +20,7 @@ RSpec.describe GitlabSchema.types['Timeframe'] do it 'accepts times as input' do with_time = input.merge(start: '2018-06-04T13:48:14Z') - expect(described_class.coerce_isolated_input(with_time)).to eq(output) + expect(described_class.coerce_isolated_input(with_time).to_h).to eq(output) end it 'requires both ends of the range' do diff --git a/spec/graphql/types/timelog_type_spec.rb b/spec/graphql/types/timelog_type_spec.rb index dc1b1e2253e..c897a25d10d 100644 --- a/spec/graphql/types/timelog_type_spec.rb +++ b/spec/graphql/types/timelog_type_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Timelog'] do - let(:fields) { %i[spent_at time_spent user issue merge_request note summary] } + let_it_be(:fields) { %i[id spent_at time_spent user issue merge_request note summary userPermissions] } it { expect(described_class.graphql_name).to eq('Timelog') } it { expect(described_class).to have_graphql_fields(fields) } it { expect(described_class).to require_graphql_authorizations(:read_issue) } + it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Timelog) } describe 'user field' do subject { described_class.fields['user'] } diff --git a/spec/graphql/types/user_merge_request_interaction_type_spec.rb b/spec/graphql/types/user_merge_request_interaction_type_spec.rb index 1eaaa0c23d0..4782a1faf8d 100644 --- a/spec/graphql/types/user_merge_request_interaction_type_spec.rb +++ b/spec/graphql/types/user_merge_request_interaction_type_spec.rb @@ -76,6 +76,7 @@ RSpec.describe GitlabSchema.types['UserMergeRequestInteraction'] do context 'when the user has been asked to review the MR' do before do merge_request.reviewers << user + merge_request.find_reviewer(user).update!(state: :attention_requested) end it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['ATTENTION_REQUESTED'].value) } diff --git a/spec/graphql/types/work_item_type_spec.rb b/spec/graphql/types/work_item_type_spec.rb index 6a5b4a0882e..a0480506156 100644 --- a/spec/graphql/types/work_item_type_spec.rb +++ b/spec/graphql/types/work_item_type_spec.rb @@ -7,8 +7,10 @@ RSpec.describe GitlabSchema.types['WorkItem'] do specify { expect(described_class).to require_graphql_authorizations(:read_work_item) } + specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::WorkItem) } + it 'has specific fields' do - fields = %i[description description_html id iid lock_version state title title_html work_item_type] + fields = %i[description description_html id iid lock_version state title title_html userPermissions work_item_type] fields.each do |field_name| expect(described_class).to have_graphql_fields(*fields) |