diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 08:27:35 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-19 08:27:35 +0000 |
commit | 7e9c479f7de77702622631cff2628a9c8dcbc627 (patch) | |
tree | c8f718a08e110ad7e1894510980d2155a6549197 /spec/requests/api/graphql | |
parent | e852b0ae16db4052c1c567d9efa4facc81146e88 (diff) | |
download | gitlab-ce-7e9c479f7de77702622631cff2628a9c8dcbc627.tar.gz |
Add latest changes from gitlab-org/gitlab@13-6-stable-eev13.6.0-rc42
Diffstat (limited to 'spec/requests/api/graphql')
50 files changed, 2411 insertions, 123 deletions
diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb index 7d416f4720b..618705e5f94 100644 --- a/spec/requests/api/graphql/ci/jobs_spec.rb +++ b/spec/requests/api/graphql/ci/jobs_spec.rb @@ -34,6 +34,9 @@ RSpec.describe 'Query.project.pipeline.stages.groups.jobs' do jobs { nodes { name + pipeline { + id + } } } } @@ -53,7 +56,7 @@ RSpec.describe 'Query.project.pipeline.stages.groups.jobs' do end context 'when fetching jobs from the pipeline' do - it 'avoids N+1 queries' do + it 'avoids N+1 queries', :aggregate_failures do control_count = ActiveRecord::QueryRecorder.new do post_graphql(query, current_user: user) end @@ -86,8 +89,27 @@ RSpec.describe 'Query.project.pipeline.stages.groups.jobs' do docker_jobs = docker_group.dig('jobs', 'nodes') rspec_jobs = rspec_group.dig('jobs', 'nodes') - expect(docker_jobs).to eq([{ 'name' => 'docker 1 2' }, { 'name' => 'docker 2 2' }]) - expect(rspec_jobs).to eq([{ 'name' => 'rspec 1 2' }, { 'name' => 'rspec 2 2' }]) + expect(docker_jobs).to eq([ + { + 'name' => 'docker 1 2', + 'pipeline' => { 'id' => pipeline.to_global_id.to_s } + }, + { + 'name' => 'docker 2 2', + 'pipeline' => { 'id' => pipeline.to_global_id.to_s } + } + ]) + + expect(rspec_jobs).to eq([ + { + 'name' => 'rspec 1 2', + 'pipeline' => { 'id' => pipeline.to_global_id.to_s } + }, + { + 'name' => 'rspec 2 2', + 'pipeline' => { 'id' => pipeline.to_global_id.to_s } + } + ]) end end end diff --git a/spec/requests/api/graphql/ci/pipelines_spec.rb b/spec/requests/api/graphql/ci/pipelines_spec.rb new file mode 100644 index 00000000000..414ddabbac9 --- /dev/null +++ b/spec/requests/api/graphql/ci/pipelines_spec.rb @@ -0,0 +1,221 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.project(fullPath).pipelines' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository, :public) } + let_it_be(:first_user) { create(:user) } + let_it_be(:second_user) { create(:user) } + + describe '.jobs' do + let_it_be(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + pipelines { + nodes { + jobs { + nodes { + name + } + } + } + } + } + } + ) + end + + it 'fetches the jobs without an N+1' do + pipeline = create(:ci_pipeline, project: project) + create(:ci_build, pipeline: pipeline, name: 'Job 1') + + control_count = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: first_user) + end + + pipeline = create(:ci_pipeline, project: project) + create(:ci_build, pipeline: pipeline, name: 'Job 2') + + expect do + post_graphql(query, current_user: second_user) + end.not_to exceed_query_limit(control_count) + + expect(response).to have_gitlab_http_status(:ok) + + pipelines_data = graphql_data.dig('project', 'pipelines', 'nodes') + + job_names = pipelines_data.map do |pipeline_data| + jobs_data = pipeline_data.dig('jobs', 'nodes') + jobs_data.map { |job_data| job_data['name'] } + end.flatten + + expect(job_names).to contain_exactly('Job 1', 'Job 2') + end + end + + describe '.jobs(securityReportTypes)' do + let_it_be(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + pipelines { + nodes { + jobs(securityReportTypes: [SAST]) { + nodes { + name + } + } + } + } + } + } + ) + end + + it 'fetches the jobs matching the report type filter' do + pipeline = create(:ci_pipeline, project: project) + create(:ci_build, :dast, name: 'DAST Job 1', pipeline: pipeline) + create(:ci_build, :sast, name: 'SAST Job 1', pipeline: pipeline) + + post_graphql(query, current_user: first_user) + + expect(response).to have_gitlab_http_status(:ok) + + pipelines_data = graphql_data.dig('project', 'pipelines', 'nodes') + + job_names = pipelines_data.map do |pipeline_data| + jobs_data = pipeline_data.dig('jobs', 'nodes') + jobs_data.map { |job_data| job_data['name'] } + end.flatten + + expect(job_names).to contain_exactly('SAST Job 1') + end + end + + describe 'upstream' do + let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: first_user) } + let_it_be(:upstream_project) { create(:project, :repository, :public) } + let_it_be(:upstream_pipeline) { create(:ci_pipeline, project: upstream_project, user: first_user) } + let(:upstream_pipelines_graphql_data) { graphql_data.dig(*%w[project pipelines nodes]).first['upstream'] } + + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + pipelines { + nodes { + upstream { + iid + } + } + } + } + } + ) + end + + before do + create(:ci_sources_pipeline, source_pipeline: upstream_pipeline, pipeline: pipeline ) + + post_graphql(query, current_user: first_user) + end + + it_behaves_like 'a working graphql query' + + it 'returns the upstream pipeline of a pipeline' do + expect(upstream_pipelines_graphql_data['iid'].to_i).to eq(upstream_pipeline.iid) + end + + context 'when fetching the upstream pipeline from the pipeline' do + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: first_user) + end + + pipeline_2 = create(:ci_pipeline, project: project, user: first_user) + upstream_pipeline_2 = create(:ci_pipeline, project: upstream_project, user: first_user) + create(:ci_sources_pipeline, source_pipeline: upstream_pipeline_2, pipeline: pipeline_2 ) + pipeline_3 = create(:ci_pipeline, project: project, user: first_user) + upstream_pipeline_3 = create(:ci_pipeline, project: upstream_project, user: first_user) + create(:ci_sources_pipeline, source_pipeline: upstream_pipeline_3, pipeline: pipeline_3 ) + + expect do + post_graphql(query, current_user: second_user) + end.not_to exceed_query_limit(control_count) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + describe 'downstream' do + let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: first_user) } + let(:pipeline_2) { create(:ci_pipeline, project: project, user: first_user) } + + let_it_be(:downstream_project) { create(:project, :repository, :public) } + let_it_be(:downstream_pipeline_a) { create(:ci_pipeline, project: downstream_project, user: first_user) } + let_it_be(:downstream_pipeline_b) { create(:ci_pipeline, project: downstream_project, user: first_user) } + + let(:pipelines_graphql_data) { graphql_data.dig(*%w[project pipelines nodes]) } + + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + pipelines { + nodes { + downstream { + nodes { + iid + } + } + } + } + } + } + ) + end + + before do + create(:ci_sources_pipeline, source_pipeline: pipeline, pipeline: downstream_pipeline_a) + create(:ci_sources_pipeline, source_pipeline: pipeline_2, pipeline: downstream_pipeline_b) + + post_graphql(query, current_user: first_user) + end + + it_behaves_like 'a working graphql query' + + it 'returns the downstream pipelines of a pipeline' do + downstream_pipelines_graphql_data = pipelines_graphql_data.map { |pip| pip['downstream']['nodes'] }.flatten + + expect( + downstream_pipelines_graphql_data.map { |pip| pip['iid'].to_i } + ).to contain_exactly(downstream_pipeline_a.iid, downstream_pipeline_b.iid) + end + + context 'when fetching the downstream pipelines from the pipeline' do + it 'avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: first_user) + end + + downstream_pipeline_2a = create(:ci_pipeline, project: downstream_project, user: first_user) + create(:ci_sources_pipeline, source_pipeline: pipeline, pipeline: downstream_pipeline_2a) + downsteam_pipeline_3a = create(:ci_pipeline, project: downstream_project, user: first_user) + create(:ci_sources_pipeline, source_pipeline: pipeline, pipeline: downsteam_pipeline_3a) + + downstream_pipeline_2b = create(:ci_pipeline, project: downstream_project, user: first_user) + create(:ci_sources_pipeline, source_pipeline: pipeline_2, pipeline: downstream_pipeline_2b) + downsteam_pipeline_3b = create(:ci_pipeline, project: downstream_project, user: first_user) + create(:ci_sources_pipeline, source_pipeline: pipeline_2, pipeline: downsteam_pipeline_3b) + + expect do + post_graphql(query, current_user: second_user) + end.not_to exceed_query_limit(control_count) + end + end + end +end diff --git a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb new file mode 100644 index 00000000000..3c1c63c1670 --- /dev/null +++ b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'container repository details' do + using RSpec::Parameterized::TableSyntax + include GraphqlHelpers + + let_it_be_with_reload(:project) { create(:project) } + let_it_be(:container_repository) { create(:container_repository, project: project) } + + let(:query) do + graphql_query_for( + 'containerRepository', + { id: container_repository_global_id }, + all_graphql_fields_for('ContainerRepositoryDetails') + ) + end + + let(:user) { project.owner } + let(:variables) { {} } + let(:tags) { %w(latest tag1 tag2 tag3 tag4 tag5) } + let(:container_repository_global_id) { container_repository.to_global_id.to_s } + let(:container_repository_details_response) { graphql_data.dig('containerRepository') } + + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(repository: container_repository.path, tags: tags, with_manifest: true) + end + + subject { post_graphql(query, current_user: user, variables: variables) } + + it_behaves_like 'a working graphql query' do + before do + subject + end + + it 'matches the JSON schema' do + expect(container_repository_details_response).to match_schema('graphql/container_repository_details') + end + end + + context 'with different permissions' do + let_it_be(:user) { create(:user) } + + let(:tags_response) { container_repository_details_response.dig('tags', 'nodes') } + + where(:project_visibility, :role, :access_granted, :can_delete) do + :private | :maintainer | true | true + :private | :developer | true | true + :private | :reporter | true | false + :private | :guest | false | false + :private | :anonymous | false | false + :public | :maintainer | true | true + :public | :developer | true | true + :public | :reporter | true | false + :public | :guest | true | false + :public | :anonymous | true | false + end + + with_them do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility.to_s.upcase, false)) + project.add_user(user, role) unless role == :anonymous + end + + it 'return the proper response' do + subject + + if access_granted + expect(tags_response.size).to eq(tags.size) + expect(container_repository_details_response.dig('canDelete')).to eq(can_delete) + else + expect(container_repository_details_response).to eq(nil) + end + end + end + end + + context 'limiting the number of tags' do + let(:limit) { 2 } + let(:tags_response) { container_repository_details_response.dig('tags', 'edges') } + let(:variables) do + { id: container_repository_global_id, n: limit } + end + + let(:query) do + <<~GQL + query($id: ID!, $n: Int) { + containerRepository(id: $id) { + tags(first: $n) { + edges { + node { + #{all_graphql_fields_for('ContainerRepositoryTag')} + } + } + } + } + } + GQL + end + + it 'only returns n tags' do + subject + + expect(tags_response.size).to eq(limit) + end + end +end diff --git a/spec/requests/api/graphql/custom_emoji_query_spec.rb b/spec/requests/api/graphql/custom_emoji_query_spec.rb new file mode 100644 index 00000000000..d5a423d0eba --- /dev/null +++ b/spec/requests/api/graphql/custom_emoji_query_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting custom emoji within namespace' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:group) { create(:group, :private) } + let_it_be(:custom_emoji) { create(:custom_emoji, group: group) } + + before do + stub_feature_flags(custom_emoji: true) + group.add_developer(current_user) + end + + describe "Query CustomEmoji on Group" do + def custom_emoji_query(group) + graphql_query_for('group', 'fullPath' => group.full_path) + end + + it 'returns emojis when authorised' do + post_graphql(custom_emoji_query(group), current_user: current_user) + + expect(response).to have_gitlab_http_status(:ok) + expect(graphql_data['group']['customEmoji']['nodes'].count). to eq(1) + expect(graphql_data['group']['customEmoji']['nodes'].first['name']). to eq(custom_emoji.name) + end + + it 'returns nil when unauthorised' do + user = create(:user) + post_graphql(custom_emoji_query(group), current_user: user) + + expect(graphql_data['group']).to be_nil + end + end +end diff --git a/spec/requests/api/graphql/group/container_repositories_spec.rb b/spec/requests/api/graphql/group/container_repositories_spec.rb new file mode 100644 index 00000000000..bcf689a5e8f --- /dev/null +++ b/spec/requests/api/graphql/group/container_repositories_spec.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'getting container repositories in a group' do + using RSpec::Parameterized::TableSyntax + include GraphqlHelpers + + let_it_be(:owner) { create(:user) } + let_it_be_with_reload(:group) { create(:group) } + let_it_be_with_reload(:project) { create(:project, group: group) } + let_it_be(:container_repository) { create(:container_repository, project: project) } + let_it_be(:container_repositories_delete_scheduled) { create_list(:container_repository, 2, :status_delete_scheduled, project: project) } + let_it_be(:container_repositories_delete_failed) { create_list(:container_repository, 2, :status_delete_failed, project: project) } + let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten } + let_it_be(:container_expiration_policy) { project.container_expiration_policy } + + let(:fields) do + <<~GQL + edges { + node { + #{all_graphql_fields_for('container_repositories'.classify)} + } + } + GQL + end + + let(:query) do + graphql_query_for( + 'group', + { 'fullPath' => group.full_path }, + query_graphql_field('container_repositories', {}, fields) + ) + end + + let(:user) { owner } + let(:variables) { {} } + let(:container_repositories_response) { graphql_data.dig('group', 'containerRepositories', 'edges') } + + before do + group.add_owner(owner) + stub_container_registry_config(enabled: true) + container_repositories.each do |repository| + stub_container_registry_tags(repository: repository.path, tags: %w(tag1 tag2 tag3), with_manifest: false) + end + end + + subject { post_graphql(query, current_user: user, variables: variables) } + + it_behaves_like 'a working graphql query' do + before do + subject + end + end + + context 'with different permissions' do + let_it_be(:user) { create(:user) } + + where(:group_visibility, :role, :access_granted, :can_delete) do + :private | :maintainer | true | true + :private | :developer | true | true + :private | :reporter | true | false + :private | :guest | false | false + :private | :anonymous | false | false + :public | :maintainer | true | true + :public | :developer | true | true + :public | :reporter | true | false + :public | :guest | false | false + :public | :anonymous | false | false + end + + with_them do + before do + group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false)) + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false)) + + group.add_user(user, role) unless role == :anonymous + end + + it 'return the proper response' do + subject + + if access_granted + expect(container_repositories_response.size).to eq(container_repositories.size) + container_repositories_response.each do |repository_response| + expect(repository_response.dig('node', 'canDelete')).to eq(can_delete) + end + else + expect(container_repositories_response).to eq(nil) + end + end + end + end + + context 'limiting the number of repositories' do + let(:limit) { 1 } + let(:variables) do + { path: group.full_path, n: limit } + end + + let(:query) do + <<~GQL + query($path: ID!, $n: Int) { + group(fullPath: $path) { + containerRepositories(first: $n) { #{fields} } + } + } + GQL + end + + it 'only returns N repositories' do + subject + + expect(container_repositories_response.size).to eq(limit) + end + end + + context 'filter by name' do + let_it_be(:container_repository) { create(:container_repository, name: 'fooBar', project: project) } + + let(:name) { 'ooba' } + let(:query) do + <<~GQL + query($path: ID!, $name: String) { + group(fullPath: $path) { + containerRepositories(name: $name) { #{fields} } + } + } + GQL + end + + let(:variables) do + { path: group.full_path, name: name } + end + + before do + stub_container_registry_tags(repository: container_repository.path, tags: %w(tag4 tag5 tag6), with_manifest: false) + end + + it 'returns the searched container repository' do + subject + + expect(container_repositories_response.size).to eq(1) + expect(container_repositories_response.first.dig('node', 'id')).to eq(container_repository.to_global_id.to_s) + end + end +end diff --git a/spec/requests/api/graphql/instance_statistics_measurements_spec.rb b/spec/requests/api/graphql/instance_statistics_measurements_spec.rb index 5d7dbcf2e3c..eb73dc59253 100644 --- a/spec/requests/api/graphql/instance_statistics_measurements_spec.rb +++ b/spec/requests/api/graphql/instance_statistics_measurements_spec.rb @@ -9,7 +9,8 @@ RSpec.describe 'InstanceStatisticsMeasurements' do let!(:instance_statistics_measurement_1) { create(:instance_statistics_measurement, :project_count, recorded_at: 20.days.ago, count: 5) } let!(:instance_statistics_measurement_2) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.days.ago, count: 10) } - let(:query) { graphql_query_for(:instanceStatisticsMeasurements, 'identifier: PROJECTS', 'nodes { count identifier }') } + let(:arguments) { 'identifier: PROJECTS' } + let(:query) { graphql_query_for(:instanceStatisticsMeasurements, arguments, 'nodes { count identifier }') } before do post_graphql(query, current_user: current_user) @@ -21,4 +22,14 @@ RSpec.describe 'InstanceStatisticsMeasurements' do { "count" => 5, 'identifier' => 'PROJECTS' } ]) end + + context 'with recorded_at filters' do + let(:arguments) { %(identifier: PROJECTS, recordedAfter: "#{15.days.ago.to_date}", recordedBefore: "#{5.days.ago.to_date}") } + + it 'returns filtered measurement objects' do + expect(graphql_data.dig('instanceStatisticsMeasurements', 'nodes')).to eq([ + { "count" => 10, 'identifier' => 'PROJECTS' } + ]) + end + end end diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb index 1c9d6b25856..d7fa680d29b 100644 --- a/spec/requests/api/graphql/issue/issue_spec.rb +++ b/spec/requests/api/graphql/issue/issue_spec.rb @@ -71,14 +71,34 @@ RSpec.describe 'Query.issue(id)' do end context 'selecting multiple fields' do - let(:issue_fields) { %w(title description) } + let(:issue_fields) { ['title', 'description', 'updatedBy { username }'] } it 'returns the Issue with the specified fields' do post_graphql(query, current_user: current_user) - expect(issue_data.keys).to eq( %w(title description) ) + expect(issue_data.keys).to eq( %w(title description updatedBy) ) expect(issue_data['title']).to eq(issue.title) expect(issue_data['description']).to eq(issue.description) + expect(issue_data['updatedBy']['username']).to eq(issue.author.username) + end + end + + context 'when issue got moved' do + let_it_be(:issue_fields) { ['moved', 'movedTo { title }'] } + let_it_be(:new_issue) { create(:issue) } + let_it_be(:issue) { create(:issue, project: project, moved_to: new_issue) } + let_it_be(:issue_params) { { 'id' => issue.to_global_id.to_s } } + + before_all do + new_issue.project.add_developer(current_user) + end + + it 'returns correct attributes' do + post_graphql(query, current_user: current_user) + + expect(issue_data.keys).to eq( %w(moved movedTo) ) + expect(issue_data['moved']).to eq(true) + expect(issue_data['movedTo']['title']).to eq(new_issue.title) end end diff --git a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb index ca5a9165760..72ec2b8e070 100644 --- a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb +++ b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb @@ -17,6 +17,7 @@ RSpec.describe 'Getting Metrics Dashboard Annotations' do let_it_be(:to_old_annotation) do create(:metrics_dashboard_annotation, environment: environment, starting_at: Time.parse(from).advance(minutes: -5), dashboard_path: path) end + let_it_be(:to_new_annotation) do create(:metrics_dashboard_annotation, environment: environment, starting_at: to.advance(minutes: 5), dashboard_path: path) end diff --git a/spec/requests/api/graphql/mutations/alert_management/http_integration/create_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/create_spec.rb new file mode 100644 index 00000000000..a285cebc805 --- /dev/null +++ b/spec/requests/api/graphql/mutations/alert_management/http_integration/create_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Creating a new HTTP Integration' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let(:variables) do + { + project_path: project.full_path, + active: false, + name: 'New HTTP Integration' + } + end + + let(:mutation) do + graphql_mutation(:http_integration_create, variables) do + <<~QL + clientMutationId + errors + integration { + id + type + name + active + token + url + apiUrl + } + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:http_integration_create) } + + before do + project.add_maintainer(current_user) + end + + it 'creates a new integration' do + post_graphql_mutation(mutation, current_user: current_user) + + new_integration = ::AlertManagement::HttpIntegration.last! + integration_response = mutation_response['integration'] + + expect(response).to have_gitlab_http_status(:success) + expect(integration_response['id']).to eq(GitlabSchema.id_from_object(new_integration).to_s) + expect(integration_response['type']).to eq('HTTP') + expect(integration_response['name']).to eq(new_integration.name) + expect(integration_response['active']).to eq(new_integration.active) + expect(integration_response['token']).to eq(new_integration.token) + expect(integration_response['url']).to eq(new_integration.url) + expect(integration_response['apiUrl']).to eq(nil) + end + + [:project_path, :active, :name].each do |argument| + context "without required argument #{argument}" do + before do + variables.delete(argument) + end + + it_behaves_like 'an invalid argument to the mutation', argument_name: argument + end + end +end diff --git a/spec/requests/api/graphql/mutations/alert_management/http_integration/destroy_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/destroy_spec.rb new file mode 100644 index 00000000000..1ecb5c76b57 --- /dev/null +++ b/spec/requests/api/graphql/mutations/alert_management/http_integration/destroy_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Removing an HTTP Integration' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:integration) { create(:alert_management_http_integration, project: project) } + + let(:mutation) do + variables = { + id: GitlabSchema.id_from_object(integration).to_s + } + graphql_mutation(:http_integration_destroy, variables) do + <<~QL + clientMutationId + errors + integration { + id + type + name + active + token + url + apiUrl + } + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:http_integration_destroy) } + + before do + project.add_maintainer(user) + end + + it 'removes the integration' do + post_graphql_mutation(mutation, current_user: user) + + integration_response = mutation_response['integration'] + + expect(response).to have_gitlab_http_status(:success) + expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s) + expect(integration_response['type']).to eq('HTTP') + expect(integration_response['name']).to eq(integration.name) + expect(integration_response['active']).to eq(integration.active) + expect(integration_response['token']).to eq(integration.token) + expect(integration_response['url']).to eq(integration.url) + expect(integration_response['apiUrl']).to eq(nil) + + expect { integration.reload }.to raise_error ActiveRecord::RecordNotFound + end +end diff --git a/spec/requests/api/graphql/mutations/alert_management/http_integration/reset_token_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/reset_token_spec.rb new file mode 100644 index 00000000000..badd9412589 --- /dev/null +++ b/spec/requests/api/graphql/mutations/alert_management/http_integration/reset_token_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Resetting a token on an existing HTTP Integration' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:integration) { create(:alert_management_http_integration, project: project) } + + let(:mutation) do + variables = { + id: GitlabSchema.id_from_object(integration).to_s + } + graphql_mutation(:http_integration_reset_token, variables) do + <<~QL + clientMutationId + errors + integration { + id + token + } + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:http_integration_reset_token) } + + before do + project.add_maintainer(user) + end + + it 'updates the integration' do + previous_token = integration.token + + post_graphql_mutation(mutation, current_user: user) + integration_response = mutation_response['integration'] + + expect(response).to have_gitlab_http_status(:success) + expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s) + expect(integration_response['token']).not_to eq(previous_token) + expect(integration_response['token']).to eq(integration.reload.token) + end +end diff --git a/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb new file mode 100644 index 00000000000..bf7eb3d980c --- /dev/null +++ b/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Updating an existing HTTP Integration' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:integration) { create(:alert_management_http_integration, project: project) } + + let(:mutation) do + variables = { + id: GitlabSchema.id_from_object(integration).to_s, + name: 'Modified Name', + active: false + } + graphql_mutation(:http_integration_update, variables) do + <<~QL + clientMutationId + errors + integration { + id + name + active + url + } + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:http_integration_update) } + + before do + project.add_maintainer(user) + end + + it 'updates the integration' do + post_graphql_mutation(mutation, current_user: user) + + integration_response = mutation_response['integration'] + + expect(response).to have_gitlab_http_status(:success) + expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s) + expect(integration_response['name']).to eq('Modified Name') + expect(integration_response['active']).to be_falsey + expect(integration_response['url']).to include('modified-name') + end +end diff --git a/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/create_spec.rb b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/create_spec.rb new file mode 100644 index 00000000000..0ef61ae0d5b --- /dev/null +++ b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/create_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Creating a new Prometheus Integration' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let(:variables) do + { + project_path: project.full_path, + active: false, + api_url: 'https://prometheus-url.com' + } + end + + let(:mutation) do + graphql_mutation(:prometheus_integration_create, variables) do + <<~QL + clientMutationId + errors + integration { + id + type + name + active + token + url + apiUrl + } + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:prometheus_integration_create) } + + before do + project.add_maintainer(current_user) + end + + it 'creates a new integration' do + post_graphql_mutation(mutation, current_user: current_user) + + new_integration = ::PrometheusService.last! + integration_response = mutation_response['integration'] + + expect(response).to have_gitlab_http_status(:success) + expect(integration_response['id']).to eq(GitlabSchema.id_from_object(new_integration).to_s) + expect(integration_response['type']).to eq('PROMETHEUS') + expect(integration_response['name']).to eq(new_integration.title) + expect(integration_response['active']).to eq(new_integration.manual_configuration?) + expect(integration_response['token']).to eq(new_integration.project.alerting_setting.token) + expect(integration_response['url']).to eq("http://localhost/#{project.full_path}/prometheus/alerts/notify.json") + expect(integration_response['apiUrl']).to eq(new_integration.api_url) + end + + [:project_path, :active, :api_url].each do |argument| + context "without required argument #{argument}" do + before do + variables.delete(argument) + end + + it_behaves_like 'an invalid argument to the mutation', argument_name: argument + end + end +end diff --git a/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb new file mode 100644 index 00000000000..d8d0ace5981 --- /dev/null +++ b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Resetting a token on an existing Prometheus Integration' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:integration) { create(:prometheus_service, project: project) } + + let(:mutation) do + variables = { + id: GitlabSchema.id_from_object(integration).to_s + } + graphql_mutation(:prometheus_integration_reset_token, variables) do + <<~QL + clientMutationId + errors + integration { + id + token + } + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:prometheus_integration_reset_token) } + + before do + project.add_maintainer(user) + end + + it 'creates a token' do + post_graphql_mutation(mutation, current_user: user) + integration_response = mutation_response['integration'] + + expect(response).to have_gitlab_http_status(:success) + expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s) + expect(integration_response['token']).not_to be_nil + expect(integration_response['token']).to eq(project.alerting_setting.token) + end + + context 'with an existing alerting setting' do + let_it_be(:alerting_setting) { create(:project_alerting_setting, project: project) } + + it 'updates the token' do + previous_token = alerting_setting.token + + post_graphql_mutation(mutation, current_user: user) + integration_response = mutation_response['integration'] + + expect(response).to have_gitlab_http_status(:success) + expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s) + expect(integration_response['token']).not_to eq(previous_token) + expect(integration_response['token']).to eq(alerting_setting.reload.token) + end + end +end diff --git a/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/update_spec.rb b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/update_spec.rb new file mode 100644 index 00000000000..6c4a647a353 --- /dev/null +++ b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/update_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Updating an existing Prometheus Integration' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:integration) { create(:prometheus_service, project: project) } + + let(:mutation) do + variables = { + id: GitlabSchema.id_from_object(integration).to_s, + api_url: 'http://modified-url.com', + active: true + } + graphql_mutation(:prometheus_integration_update, variables) do + <<~QL + clientMutationId + errors + integration { + id + active + apiUrl + } + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:prometheus_integration_update) } + + before do + project.add_maintainer(user) + end + + it 'updates the integration' do + post_graphql_mutation(mutation, current_user: user) + + integration_response = mutation_response['integration'] + + expect(response).to have_gitlab_http_status(:success) + expect(integration_response['id']).to eq(GitlabSchema.id_from_object(integration).to_s) + expect(integration_response['apiUrl']).to eq('http://modified-url.com') + expect(integration_response['active']).to be_truthy + end +end diff --git a/spec/requests/api/graphql/mutations/commits/create_spec.rb b/spec/requests/api/graphql/mutations/commits/create_spec.rb index ac4fa7cfe83..375d4f10b40 100644 --- a/spec/requests/api/graphql/mutations/commits/create_spec.rb +++ b/spec/requests/api/graphql/mutations/commits/create_spec.rb @@ -23,6 +23,18 @@ RSpec.describe 'Creation of a new commit' do let(:mutation) { graphql_mutation(:commit_create, input) } let(:mutation_response) { graphql_mutation_response(:commit_create) } + shared_examples 'a commit is successful' do + it 'creates a new commit' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + + expect(mutation_response['commit']).to include( + 'title' => message + ) + end + end + context 'the user is not allowed to create a commit' do it_behaves_like 'a mutation that returns a top-level access error' end @@ -32,14 +44,7 @@ RSpec.describe 'Creation of a new commit' do project.add_developer(current_user) end - it 'creates a new commit' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['commit']).to include( - 'title' => message - ) - end + it_behaves_like 'a commit is successful' context 'when branch is not correct' do let(:branch) { 'unknown' } @@ -47,5 +52,22 @@ RSpec.describe 'Creation of a new commit' do it_behaves_like 'a mutation that returns errors in the response', errors: ['You can only create or edit files when you are on a branch'] end + + context 'when branch is new, and a start_branch is defined' do + let(:input) { { project_path: project.full_path, branch: branch, start_branch: start_branch, message: message, actions: actions } } + let(:branch) { 'new-branch' } + let(:start_branch) { 'master' } + let(:actions) do + [ + { + action: 'CREATE', + filePath: 'ANOTHER_FILE.md', + content: 'Bye' + } + ] + end + + it_behaves_like 'a commit is successful' + end end end diff --git a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb index 7bef812bfec..23e8e366483 100644 --- a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb +++ b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb @@ -73,6 +73,29 @@ RSpec.describe 'Updating the container expiration policy' do end end + RSpec.shared_examples 'rejecting blank name_regex when enabled' do + context "for blank name_regex" do + let(:params) do + { + project_path: project.full_path, + name_regex: '', + enabled: true + } + end + + it_behaves_like 'returning response status', :success + + it_behaves_like 'not creating the container expiration policy' + + it 'returns an error' do + subject + + expect(graphql_data['updateContainerExpirationPolicy']['errors'].size).to eq(1) + expect(graphql_data['updateContainerExpirationPolicy']['errors']).to include("Name regex can't be blank") + end + end + end + RSpec.shared_examples 'accepting the mutation request updating the container expiration policy' do it_behaves_like 'updating the container expiration policy attributes', mode: :update, from: { cadence: '1d', keep_n: 10, older_than: '90d' }, to: { cadence: '3month', keep_n: 100, older_than: '14d' } @@ -80,6 +103,7 @@ RSpec.describe 'Updating the container expiration policy' do it_behaves_like 'rejecting invalid regex for', :name_regex it_behaves_like 'rejecting invalid regex for', :name_regex_keep + it_behaves_like 'rejecting blank name_regex when enabled' end RSpec.shared_examples 'accepting the mutation request creating the container expiration policy' do @@ -89,6 +113,7 @@ RSpec.describe 'Updating the container expiration policy' do it_behaves_like 'rejecting invalid regex for', :name_regex it_behaves_like 'rejecting invalid regex for', :name_regex_keep + it_behaves_like 'rejecting blank name_regex when enabled' end RSpec.shared_examples 'denying the mutation request' do diff --git a/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb new file mode 100644 index 00000000000..645edfc2e43 --- /dev/null +++ b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Destroying a container repository' do + using RSpec::Parameterized::TableSyntax + + include GraphqlHelpers + + let_it_be_with_reload(:container_repository) { create(:container_repository) } + let_it_be(:user) { create(:user) } + + let(:project) { container_repository.project } + let(:id) { container_repository.to_global_id.to_s } + + let(:query) do + <<~GQL + containerRepository { + #{all_graphql_fields_for('ContainerRepository')} + } + errors + GQL + end + + let(:params) { { id: container_repository.to_global_id.to_s } } + let(:mutation) { graphql_mutation(:destroy_container_repository, params, query) } + let(:mutation_response) { graphql_mutation_response(:destroyContainerRepository) } + let(:container_repository_mutation_response) { mutation_response['containerRepository'] } + + before do + stub_container_registry_config(enabled: true) + stub_container_registry_tags(tags: %w[a b c]) + end + + shared_examples 'destroying the container repository' do + it 'destroy the container repository' do + expect(::Packages::CreateEventService) + .to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original + expect(DeleteContainerRepositoryWorker) + .to receive(:perform_async).with(user.id, container_repository.id) + + expect { subject }.to change { ::Packages::Event.count }.by(1) + + expect(container_repository_mutation_response).to match_schema('graphql/container_repository') + expect(container_repository_mutation_response['status']).to eq('DELETE_SCHEDULED') + end + + it_behaves_like 'returning response status', :success + end + + shared_examples 'denying the mutation request' do + it 'does not destroy the container repository' do + expect(DeleteContainerRepositoryWorker) + .not_to receive(:perform_async).with(user.id, container_repository.id) + + expect { subject }.not_to change { ::Packages::Event.count } + + expect(mutation_response).to be_nil + end + + it_behaves_like 'returning response status', :success + end + + describe 'post graphql mutation' do + subject { post_graphql_mutation(mutation, current_user: user) } + + context 'with valid id' do + where(:user_role, :shared_examples_name) do + :maintainer | 'destroying the container repository' + :developer | 'destroying the container repository' + :reporter | 'denying the mutation request' + :guest | 'denying the mutation request' + :anonymous | 'denying the mutation request' + end + + with_them do + before do + project.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end + + context 'with invalid id' do + let(:params) { { id: 'gid://gitlab/ContainerRepository/5555' } } + + it_behaves_like 'denying the mutation request' + end + end +end diff --git a/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb b/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb new file mode 100644 index 00000000000..c91437fa355 --- /dev/null +++ b/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Creation of a new Custom Emoji' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:group) { create(:group) } + + let(:attributes) do + { + name: 'my_new_emoji', + url: 'https://example.com/image.png', + group_path: group.full_path + } + end + + let(:mutation) do + graphql_mutation(:create_custom_emoji, attributes) + end + + context 'when the user has no permission' do + it 'does not create custom emoji' do + expect { post_graphql_mutation(mutation, current_user: current_user) }.not_to change(CustomEmoji, :count) + end + end + + context 'when user has permission' do + before do + group.add_developer(current_user) + end + + it 'creates custom emoji' do + expect { post_graphql_mutation(mutation, current_user: current_user) }.to change(CustomEmoji, :count).by(1) + + gql_response = graphql_mutation_response(:create_custom_emoji) + expect(gql_response['errors']).to eq([]) + expect(gql_response['customEmoji']['name']).to eq(attributes[:name]) + expect(gql_response['customEmoji']['url']).to eq(attributes[:url]) + end + end +end diff --git a/spec/requests/api/graphql/mutations/labels/create_spec.rb b/spec/requests/api/graphql/mutations/labels/create_spec.rb new file mode 100644 index 00000000000..28284408306 --- /dev/null +++ b/spec/requests/api/graphql/mutations/labels/create_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Labels::Create do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + + let(:params) do + { + 'title' => 'foo', + 'description' => 'some description', + 'color' => '#FF0000' + } + end + + let(:mutation) { graphql_mutation(:label_create, params.merge(extra_params)) } + + subject { post_graphql_mutation(mutation, current_user: current_user) } + + def mutation_response + graphql_mutation_response(:label_create) + end + + shared_examples_for 'labels create mutation' do + context 'when the user does not have permission to create a label' do + it_behaves_like 'a mutation that returns a top-level access error' + + it 'does not create the label' do + expect { subject }.not_to change { Label.count } + end + end + + context 'when the user has permission to create a label' do + before do + parent.add_developer(current_user) + end + + context 'when the parent (project_path or group_path) param is given' do + it 'creates the label' do + expect { subject }.to change { Label.count }.to(1) + + expect(mutation_response).to include( + 'label' => a_hash_including(params)) + end + + it 'does not create a label when there are errors' do + label_factory = parent.is_a?(Group) ? :group_label : :label + create(label_factory, title: 'foo', parent.class.name.underscore.to_sym => parent) + + expect { subject }.not_to change { Label.count } + + expect(mutation_response).to have_key('label') + expect(mutation_response['label']).to be_nil + expect(mutation_response['errors'].first).to eq('Title has already been taken') + end + end + end + end + + context 'when creating a project label' do + let_it_be(:parent) { create(:project) } + let(:extra_params) { { project_path: parent.full_path } } + + it_behaves_like 'labels create mutation' + end + + context 'when creating a group label' do + let_it_be(:parent) { create(:group) } + let(:extra_params) { { group_path: parent.full_path } } + + it_behaves_like 'labels create mutation' + end + + context 'when neither project_path nor group_path param is given' do + let(:mutation) { graphql_mutation(:label_create, params) } + + it_behaves_like 'a mutation that returns top-level errors', + errors: ['Exactly one of group_path or project_path arguments is required'] + + it 'does not create the label' do + expect { subject }.not_to change { Label.count } + end + end +end diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb index 81d13b29dde..2a39757e103 100644 --- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb +++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb @@ -101,9 +101,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create do graphql_mutation(:create_annotation, variables) end - it_behaves_like 'a mutation that returns top-level errors' do - let(:match_errors) { include(/is not a valid Global ID/) } - end + it_behaves_like 'an invalid argument to the mutation', argument_name: :environment_id end end end @@ -190,9 +188,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create do graphql_mutation(:create_annotation, variables) end - it_behaves_like 'a mutation that returns top-level errors' do - let(:match_errors) { include(/is not a valid Global ID/) } - end + it_behaves_like 'an invalid argument to the mutation', argument_name: :cluster_id end end @@ -213,35 +209,26 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create do it_behaves_like 'a mutation that returns top-level errors', errors: [described_class::ANNOTATION_SOURCE_ARGUMENT_ERROR] end - context 'when a non-cluster or environment id is provided' do - let(:gid) { { environment_id: project.to_global_id.to_s } } - let(:mutation) do - variables = { - starting_at: starting_at, - ending_at: ending_at, - dashboard_path: dashboard_path, - description: description - }.merge!(gid) - - graphql_mutation(:create_annotation, variables) - end - - before do - project.add_developer(current_user) - end + [:environment_id, :cluster_id].each do |arg_name| + context "when #{arg_name} is given an ID of the wrong type" do + let(:gid) { global_id_of(project) } + let(:mutation) do + variables = { + starting_at: starting_at, + ending_at: ending_at, + dashboard_path: dashboard_path, + description: description, + arg_name => gid + } - describe 'non-environment id' do - it_behaves_like 'a mutation that returns top-level errors' do - let(:match_errors) { include(/does not represent an instance of Environment/) } + graphql_mutation(:create_annotation, variables) end - end - - describe 'non-cluster id' do - let(:gid) { { cluster_id: project.to_global_id.to_s } } - it_behaves_like 'a mutation that returns top-level errors' do - let(:match_errors) { include(/does not represent an instance of Clusters::Cluster/) } + before do + project.add_developer(current_user) end + + it_behaves_like 'an invalid argument to the mutation', argument_name: arg_name end end end diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb index 9a612c841a2..b956734068c 100644 --- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete do let(:variables) { { id: GitlabSchema.id_from_object(project).to_s } } it_behaves_like 'a mutation that returns top-level errors' do - let(:match_errors) { eq(["#{variables[:id]} is not a valid ID for #{annotation.class}."]) } + let(:match_errors) { contain_exactly(include('invalid value for id')) } end end diff --git a/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb new file mode 100644 index 00000000000..4efa7f9d509 --- /dev/null +++ b/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Repositioning an ImageDiffNote' do + include GraphqlHelpers + + let_it_be(:noteable) { create(:merge_request) } + let_it_be(:project) { noteable.project } + let(:note) { create(:image_diff_note_on_merge_request, noteable: noteable, project: project) } + let(:new_position) { { x: 10 } } + let(:current_user) { project.creator } + + let(:mutation_variables) do + { + id: global_id_of(note), + position: new_position + } + end + + let(:mutation) do + graphql_mutation(:reposition_image_diff_note, mutation_variables) do + <<~QL + note { + id + } + errors + QL + end + end + + def mutation_response + graphql_mutation_response(:reposition_image_diff_note) + end + + it 'updates the note', :aggregate_failures do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change { note.reset.position.x }.to(10) + + expect(mutation_response['note']).to eq('id' => global_id_of(note)) + expect(mutation_response['errors']).to be_empty + end + + context 'when the note is not a DiffNote' do + let(:note) { project } + + it_behaves_like 'a mutation that returns top-level errors' do + let(:match_errors) { include(/does not represent an instance of DiffNote/) } + end + end + + context 'when a position arg is nil' do + let(:new_position) { { x: nil, y: 10 } } + + it 'does not set the property to nil', :aggregate_failures do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { note.reset.position.x } + + expect(mutation_response['note']).to eq('id' => global_id_of(note)) + expect(mutation_response['errors']).to be_empty + end + end + + context 'when all position args are nil' do + let(:new_position) { { x: nil } } + + it_behaves_like 'a mutation that returns top-level errors' do + let(:match_errors) { include(/RepositionImageDiffNoteInput! was provided invalid value/) } + end + + it 'contains an explanation for the error' do + post_graphql_mutation(mutation, current_user: current_user) + + explanation = graphql_errors.first['extensions']['problems'].first['explanation'] + + expect(explanation).to eq('At least one property of `UpdateDiffImagePositionInput` must be set') + end + end +end diff --git a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb index efa2ceb65c2..713b26a6a9b 100644 --- a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb @@ -20,6 +20,7 @@ RSpec.describe 'Updating an image DiffNote' do position_type: 'image' ) end + let_it_be(:updated_body) { 'Updated body' } let_it_be(:updated_width) { 50 } let_it_be(:updated_height) { 100 } @@ -31,7 +32,7 @@ RSpec.describe 'Updating an image DiffNote' do height: updated_height, x: updated_x, y: updated_y - } + }.compact.presence end let!(:diff_note) do @@ -45,10 +46,11 @@ RSpec.describe 'Updating an image DiffNote' do let(:mutation) do variables = { id: GitlabSchema.id_from_object(diff_note).to_s, - body: updated_body, - position: updated_position + body: updated_body } + variables[:position] = updated_position if updated_position + graphql_mutation(:update_image_diff_note, variables) end diff --git a/spec/requests/api/graphql/mutations/releases/create_spec.rb b/spec/requests/api/graphql/mutations/releases/create_spec.rb new file mode 100644 index 00000000000..d745eb3083d --- /dev/null +++ b/spec/requests/api/graphql/mutations/releases/create_spec.rb @@ -0,0 +1,375 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Creation of a new release' do + include GraphqlHelpers + include Presentable + + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:milestone_12_3) { create(:milestone, project: project, title: '12.3') } + let_it_be(:milestone_12_4) { create(:milestone, project: project, title: '12.4') } + let_it_be(:public_user) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:developer) { create(:user) } + + let(:mutation_name) { :release_create } + + let(:tag_name) { 'v7.12.5'} + let(:ref) { 'master'} + let(:name) { 'Version 7.12.5'} + let(:description) { 'Release 7.12.5 :rocket:' } + let(:released_at) { '2018-12-10' } + let(:milestones) { [milestone_12_3.title, milestone_12_4.title] } + let(:asset_link) { { name: 'An asset link', url: 'https://gitlab.example.com/link', directAssetPath: '/permanent/link', linkType: 'OTHER' } } + let(:assets) { { links: [asset_link] } } + + let(:mutation_arguments) do + { + projectPath: project.full_path, + tagName: tag_name, + ref: ref, + name: name, + description: description, + releasedAt: released_at, + milestones: milestones, + assets: assets + } + end + + let(:mutation) do + graphql_mutation(mutation_name, mutation_arguments, <<~FIELDS) + release { + tagName + name + description + releasedAt + createdAt + milestones { + nodes { + title + } + } + assets { + links { + nodes { + name + url + linkType + external + directAssetUrl + } + } + } + } + errors + FIELDS + end + + let(:create_release) { post_graphql_mutation(mutation, current_user: current_user) } + let(:mutation_response) { graphql_mutation_response(mutation_name)&.with_indifferent_access } + + around do |example| + freeze_time { example.run } + end + + before do + project.add_guest(guest) + project.add_reporter(reporter) + project.add_developer(developer) + + stub_default_url_options(host: 'www.example.com') + end + + shared_examples 'no errors' do + it 'returns no errors' do + create_release + + expect(graphql_errors).not_to be_present + end + end + + shared_examples 'top-level error with message' do |error_message| + it 'returns a top-level error with message' do + create_release + + expect(mutation_response).to be_nil + expect(graphql_errors.count).to eq(1) + expect(graphql_errors.first['message']).to eq(error_message) + end + end + + shared_examples 'errors-as-data with message' do |error_message| + it 'returns an error-as-data with message' do + create_release + + expect(mutation_response[:release]).to be_nil + expect(mutation_response[:errors].count).to eq(1) + expect(mutation_response[:errors].first).to match(error_message) + end + end + + context 'when the current user has access to create releases' do + let(:current_user) { developer } + + context 'when all available mutation arguments are provided' do + it_behaves_like 'no errors' + + # rubocop: disable CodeReuse/ActiveRecord + it 'returns the new release data' do + create_release + + release = mutation_response[:release] + expected_direct_asset_url = Gitlab::Routing.url_helpers.project_release_url(project, Release.find_by(tag: tag_name)) << "/downloads#{asset_link[:directAssetPath]}" + + expected_attributes = { + tagName: tag_name, + name: name, + description: description, + releasedAt: Time.parse(released_at).utc.iso8601, + createdAt: Time.current.utc.iso8601, + assets: { + links: { + nodes: [{ + name: asset_link[:name], + url: asset_link[:url], + linkType: asset_link[:linkType], + external: true, + directAssetUrl: expected_direct_asset_url + }] + } + } + } + + expect(release).to include(expected_attributes) + + # Right now the milestones are returned in a non-deterministic order. + # This `milestones` test should be moved up into the expect(release) + # above (and `.to include` updated to `.to eq`) once + # https://gitlab.com/gitlab-org/gitlab/-/issues/259012 is addressed. + expect(release['milestones']['nodes']).to match_array([ + { 'title' => '12.4' }, + { 'title' => '12.3' } + ]) + end + # rubocop: enable CodeReuse/ActiveRecord + end + + context 'when only the required mutation arguments are provided' do + let(:mutation_arguments) { super().slice(:projectPath, :tagName, :ref) } + + it_behaves_like 'no errors' + + it 'returns the new release data' do + create_release + + expected_response = { + tagName: tag_name, + name: tag_name, + description: nil, + releasedAt: Time.current.utc.iso8601, + createdAt: Time.current.utc.iso8601, + milestones: { + nodes: [] + }, + assets: { + links: { + nodes: [] + } + } + }.with_indifferent_access + + expect(mutation_response[:release]).to eq(expected_response) + end + end + + context 'when the provided tag already exists' do + let(:tag_name) { 'v1.1.0' } + + it_behaves_like 'no errors' + + it 'does not create a new tag' do + expect { create_release }.not_to change { Project.find_by_id(project.id).repository.tag_count } + end + end + + context 'when the provided tag does not already exist' do + let(:tag_name) { 'v7.12.5-alpha' } + + it_behaves_like 'no errors' + + it 'creates a new tag' do + expect { create_release }.to change { Project.find_by_id(project.id).repository.tag_count }.by(1) + end + end + + context 'when a local timezone is provided for releasedAt' do + let(:released_at) { Time.parse(super()).in_time_zone('Hawaii').iso8601 } + + it_behaves_like 'no errors' + + it 'returns the correct releasedAt date in UTC' do + create_release + + expect(mutation_response[:release]).to include({ releasedAt: Time.parse(released_at).utc.iso8601 }) + end + end + + context 'when no releasedAt is provided' do + let(:mutation_arguments) { super().except(:releasedAt) } + + it_behaves_like 'no errors' + + it 'sets releasedAt to the current time' do + create_release + + expect(mutation_response[:release]).to include({ releasedAt: Time.current.utc.iso8601 }) + end + end + + context "when a release asset doesn't include an explicit linkType" do + let(:asset_link) { super().except(:linkType) } + + it_behaves_like 'no errors' + + it 'defaults the linkType to OTHER' do + create_release + + returned_asset_link_type = mutation_response.dig(:release, :assets, :links, :nodes, 0, :linkType) + + expect(returned_asset_link_type).to eq('OTHER') + end + end + + context "when a release asset doesn't include a directAssetPath" do + let(:asset_link) { super().except(:directAssetPath) } + + it_behaves_like 'no errors' + + it 'returns the provided url as the directAssetUrl' do + create_release + + returned_asset_link_type = mutation_response.dig(:release, :assets, :links, :nodes, 0, :directAssetUrl) + + expect(returned_asset_link_type).to eq(asset_link[:url]) + end + end + + context 'empty milestones' do + shared_examples 'no associated milestones' do + it_behaves_like 'no errors' + + it 'creates a release with no associated milestones' do + create_release + + returned_milestones = mutation_response.dig(:release, :milestones, :nodes) + + expect(returned_milestones.count).to eq(0) + end + end + + context 'when the milestones parameter is not provided' do + let(:mutation_arguments) { super().except(:milestones) } + + it_behaves_like 'no associated milestones' + end + + context 'when the milestones parameter is null' do + let(:milestones) { nil } + + it_behaves_like 'no associated milestones' + end + + context 'when the milestones parameter is an empty array' do + let(:milestones) { [] } + + it_behaves_like 'no associated milestones' + end + end + + context 'validation' do + context 'when a release is already associated to the specified tag' do + before do + create(:release, project: project, tag: tag_name) + end + + it_behaves_like 'errors-as-data with message', 'Release already exists' + end + + context "when a provided milestone doesn\'t exist" do + let(:milestones) { ['a fake milestone'] } + + it_behaves_like 'errors-as-data with message', 'Milestone(s) not found: a fake milestone' + end + + context "when a provided milestone belongs to a different project than the release" do + let(:milestone_in_different_project) { create(:milestone, title: 'different milestone') } + let(:milestones) { [milestone_in_different_project.title] } + + it_behaves_like 'errors-as-data with message', "Milestone(s) not found: different milestone" + end + + context 'when two release assets share the same name' do + let(:asset_link_1) { { name: 'My link', url: 'https://example.com/1' } } + let(:asset_link_2) { { name: 'My link', url: 'https://example.com/2' } } + let(:assets) { { links: [asset_link_1, asset_link_2] } } + + # Right now the raw Postgres error message is sent to the user as the validation message. + # We should catch this validation error and return a nicer message: + # https://gitlab.com/gitlab-org/gitlab/-/issues/277087 + it_behaves_like 'errors-as-data with message', 'PG::UniqueViolation' + end + + context 'when two release assets share the same URL' do + let(:asset_link_1) { { name: 'My first link', url: 'https://example.com' } } + let(:asset_link_2) { { name: 'My second link', url: 'https://example.com' } } + let(:assets) { { links: [asset_link_1, asset_link_2] } } + + # Same note as above about the ugly error message + it_behaves_like 'errors-as-data with message', 'PG::UniqueViolation' + end + + context 'when the provided tag name is HEAD' do + let(:tag_name) { 'HEAD' } + + it_behaves_like 'errors-as-data with message', 'Tag name invalid' + end + + context 'when the provided tag name is empty' do + let(:tag_name) { '' } + + it_behaves_like 'errors-as-data with message', 'Tag name invalid' + end + + context "when the provided tag doesn't already exist, and no ref parameter was provided" do + let(:ref) { nil } + let(:tag_name) { 'v7.12.5-beta' } + + it_behaves_like 'errors-as-data with message', 'Ref is not specified' + end + end + end + + context "when the current user doesn't have access to create releases" do + expected_error_message = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + + context 'when the current user is a Reporter' do + let(:current_user) { reporter } + + it_behaves_like 'top-level error with message', expected_error_message + end + + context 'when the current user is a Guest' do + let(:current_user) { guest } + + it_behaves_like 'top-level error with message', expected_error_message + end + + context 'when the current user is a public user' do + let(:current_user) { public_user } + + it_behaves_like 'top-level error with message', expected_error_message + end + end +end diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb index b71f87d2702..1be8ce142ac 100644 --- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb @@ -53,10 +53,11 @@ RSpec.describe 'Destroying a Snippet' do let!(:snippet_gid) { project.to_gid.to_s } it 'returns an error' do + err_message = %Q["#{snippet_gid}" does not represent an instance of Snippet] + post_graphql_mutation(mutation, current_user: current_user) - expect(graphql_errors) - .to include(a_hash_including('message' => "#{snippet_gid} is not a valid ID for Snippet.")) + expect(graphql_errors).to include(a_hash_including('message' => a_string_including(err_message))) end it 'does not destroy the Snippet' do diff --git a/spec/requests/api/graphql/mutations/todos/create_spec.rb b/spec/requests/api/graphql/mutations/todos/create_spec.rb new file mode 100644 index 00000000000..aca00519682 --- /dev/null +++ b/spec/requests/api/graphql/mutations/todos/create_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Create a todo' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:target) { create(:issue) } + + let(:input) do + { + 'targetId' => target.to_global_id.to_s + } + end + + let(:mutation) { graphql_mutation(:todoCreate, input) } + + let(:mutation_response) { graphql_mutation_response(:todoCreate) } + + context 'the user is not allowed to create todo' do + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when user has permissions to create todo' do + before do + target.project.add_guest(current_user) + end + + it 'creates todo' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['todo']['body']).to eq(target.title) + expect(mutation_response['todo']['state']).to eq('pending') + end + end +end diff --git a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb new file mode 100644 index 00000000000..3e96d5c5058 --- /dev/null +++ b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Restoring many Todos' do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:author) { create(:user) } + let_it_be(:other_user) { create(:user) } + + let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done) } + let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) } + + let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) } + + let(:input_ids) { [todo1, todo2].map { |obj| global_id_of(obj) } } + let(:input) { { ids: input_ids } } + + let(:mutation) do + graphql_mutation(:todo_restore_many, input, + <<-QL.strip_heredoc + clientMutationId + errors + updatedIds + todos { + id + state + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:todo_restore_many) + end + + it 'restores many todos' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(todo1.reload.state).to eq('pending') + expect(todo2.reload.state).to eq('pending') + expect(other_user_todo.reload.state).to eq('done') + + expect(mutation_response).to include( + 'errors' => be_empty, + 'updatedIds' => match_array(input_ids), + 'todos' => contain_exactly( + { 'id' => global_id_of(todo1), 'state' => 'pending' }, + { 'id' => global_id_of(todo2), 'state' => 'pending' } + ) + ) + end + + context 'when using an invalid gid' do + let(:input_ids) { [global_id_of(author)] } + let(:invalid_gid_error) { /does not represent an instance of #{todo1.class}/ } + + it 'contains the expected error' do + post_graphql_mutation(mutation, current_user: current_user) + + errors = json_response['errors'] + expect(errors).not_to be_blank + expect(errors.first['message']).to match(invalid_gid_error) + + expect(todo1.reload.state).to eq('done') + expect(todo2.reload.state).to eq('done') + end + end +end diff --git a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb index 44e68c59248..37cc502103d 100644 --- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb +++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb @@ -6,7 +6,7 @@ RSpec.describe 'rendering namespace statistics' do include GraphqlHelpers let(:namespace) { user.namespace } - let!(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace, packages_size: 5.gigabytes) } + let!(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace, packages_size: 5.gigabytes, uploads_size: 3.gigabytes) } let(:user) { create(:user) } let(:query) do @@ -28,6 +28,12 @@ RSpec.describe 'rendering namespace statistics' do expect(graphql_data['namespace']['rootStorageStatistics']).not_to be_blank expect(graphql_data['namespace']['rootStorageStatistics']['packagesSize']).to eq(5.gigabytes) end + + it 'includes uploads size if the user can read the statistics' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:namespace, :root_storage_statistics, :uploads_size)).to eq(3.gigabytes) + end end it_behaves_like 'a working namespace with storage statistics query' diff --git a/spec/requests/api/graphql/project/alert_management/integrations_spec.rb b/spec/requests/api/graphql/project/alert_management/integrations_spec.rb new file mode 100644 index 00000000000..b13805a61ce --- /dev/null +++ b/spec/requests/api/graphql/project/alert_management/integrations_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'getting Alert Management Integrations' do + include ::Gitlab::Routing + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:current_user) { create(:user) } + let_it_be(:prometheus_service) { create(:prometheus_service, project: project) } + let_it_be(:project_alerting_setting) { create(:project_alerting_setting, project: project) } + let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) } + let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) } + let_it_be(:other_project_http_integration) { create(:alert_management_http_integration) } + + let(:fields) do + <<~QUERY + nodes { + #{all_graphql_fields_for('AlertManagementIntegration')} + } + QUERY + end + + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('alertManagementIntegrations', {}, fields) + ) + end + + context 'with integrations' do + let(:integrations) { graphql_data.dig('project', 'alertManagementIntegrations', 'nodes') } + + context 'without project permissions' do + let(:user) { create(:user) } + + before do + post_graphql(query, current_user: current_user) + end + + it_behaves_like 'a working graphql query' + + it { expect(integrations).to be_nil } + end + + context 'with project permissions' do + before do + project.add_maintainer(current_user) + post_graphql(query, current_user: current_user) + end + + let(:http_integration) { integrations.first } + let(:prometheus_integration) { integrations.second } + + it_behaves_like 'a working graphql query' + + it { expect(integrations.size).to eq(2) } + + it 'returns the correct properties of the integrations' do + expect(http_integration).to include( + 'id' => GitlabSchema.id_from_object(active_http_integration).to_s, + 'type' => 'HTTP', + 'name' => active_http_integration.name, + 'active' => active_http_integration.active, + 'token' => active_http_integration.token, + 'url' => active_http_integration.url, + 'apiUrl' => nil + ) + + expect(prometheus_integration).to include( + 'id' => GitlabSchema.id_from_object(prometheus_service).to_s, + 'type' => 'PROMETHEUS', + 'name' => 'Prometheus', + 'active' => prometheus_service.manual_configuration?, + 'token' => project_alerting_setting.token, + 'url' => "http://localhost/#{project.full_path}/prometheus/alerts/notify.json", + 'apiUrl' => prometheus_service.api_url + ) + end + end + end +end diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb new file mode 100644 index 00000000000..7e32f54bf1d --- /dev/null +++ b/spec/requests/api/graphql/project/container_repositories_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'getting container repositories in a project' do + using RSpec::Parameterized::TableSyntax + include GraphqlHelpers + + let_it_be_with_reload(:project) { create(:project, :private) } + let_it_be(:container_repository) { create(:container_repository, project: project) } + let_it_be(:container_repositories_delete_scheduled) { create_list(:container_repository, 2, :status_delete_scheduled, project: project) } + let_it_be(:container_repositories_delete_failed) { create_list(:container_repository, 2, :status_delete_failed, project: project) } + let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten } + let_it_be(:container_expiration_policy) { project.container_expiration_policy } + + let(:fields) do + <<~GQL + edges { + node { + #{all_graphql_fields_for('container_repositories'.classify)} + } + } + GQL + end + + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('container_repositories', {}, fields) + ) + end + + let(:user) { project.owner } + let(:variables) { {} } + let(:container_repositories_response) { graphql_data.dig('project', 'containerRepositories', 'edges') } + + before do + stub_container_registry_config(enabled: true) + container_repositories.each do |repository| + stub_container_registry_tags(repository: repository.path, tags: %w(tag1 tag2 tag3), with_manifest: false) + end + end + + subject { post_graphql(query, current_user: user, variables: variables) } + + it_behaves_like 'a working graphql query' do + before do + subject + end + + it 'matches the JSON schema' do + expect(container_repositories_response).to match_schema('graphql/container_repositories') + end + end + + context 'with different permissions' do + let_it_be(:user) { create(:user) } + + where(:project_visibility, :role, :access_granted, :can_delete) do + :private | :maintainer | true | true + :private | :developer | true | true + :private | :reporter | true | false + :private | :guest | false | false + :private | :anonymous | false | false + :public | :maintainer | true | true + :public | :developer | true | true + :public | :reporter | true | false + :public | :guest | true | false + :public | :anonymous | true | false + end + + with_them do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility.to_s.upcase, false)) + project.add_user(user, role) unless role == :anonymous + end + + it 'return the proper response' do + subject + + if access_granted + expect(container_repositories_response.size).to eq(container_repositories.size) + container_repositories_response.each do |repository_response| + expect(repository_response.dig('node', 'canDelete')).to eq(can_delete) + end + else + expect(container_repositories_response).to eq(nil) + end + end + end + end + + context 'limiting the number of repositories' do + let(:limit) { 1 } + let(:variables) do + { path: project.full_path, n: limit } + end + + let(:query) do + <<~GQL + query($path: ID!, $n: Int) { + project(fullPath: $path) { + containerRepositories(first: $n) { #{fields} } + } + } + GQL + end + + it 'only returns N repositories' do + subject + + expect(container_repositories_response.size).to eq(limit) + end + end + + context 'filter by name' do + let_it_be(:container_repository) { create(:container_repository, name: 'fooBar', project: project) } + + let(:name) { 'ooba' } + let(:query) do + <<~GQL + query($path: ID!, $name: String) { + project(fullPath: $path) { + containerRepositories(name: $name) { #{fields} } + } + } + GQL + end + + let(:variables) do + { path: project.full_path, name: name } + end + + before do + stub_container_registry_tags(repository: container_repository.path, tags: %w(tag4 tag5 tag6), with_manifest: false) + end + + it 'returns the searched container repository' do + subject + + expect(container_repositories_response.size).to eq(1) + expect(container_repositories_response.first.dig('node', 'id')).to eq(container_repository.to_global_id.to_s) + end + end +end diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb index cd84ce9cb96..c7d327a62af 100644 --- a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb +++ b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb @@ -29,10 +29,12 @@ RSpec.describe 'sentry errors requests' do let(:error_data) { graphql_data.dig('project', 'sentryErrors', 'detailedError') } - it_behaves_like 'a working graphql query' do - before do - post_graphql(query, current_user: current_user) - end + it 'returns a successful response', :aggregate_failures, :quarantine do + post_graphql(query, current_user: current_user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to be_nil + expect(json_response.keys).to include('data') end context 'when data is loading via reactive cache' do @@ -191,7 +193,7 @@ RSpec.describe 'sentry errors requests' do describe 'getting a stack trace' do let_it_be(:sentry_stack_trace) { build(:error_tracking_error_event) } - let(:sentry_gid) { Gitlab::ErrorTracking::DetailedError.new(id: 1).to_global_id.to_s } + let(:sentry_gid) { global_id_of(Gitlab::ErrorTracking::DetailedError.new(id: 1)) } let(:stack_trace_fields) do all_graphql_fields_for('SentryErrorStackTrace'.classify) diff --git a/spec/requests/api/graphql/project/grafana_integration_spec.rb b/spec/requests/api/graphql/project/grafana_integration_spec.rb index 688959e622d..9b24698f40c 100644 --- a/spec/requests/api/graphql/project/grafana_integration_spec.rb +++ b/spec/requests/api/graphql/project/grafana_integration_spec.rb @@ -45,7 +45,6 @@ RSpec.describe 'Getting Grafana Integration' do it_behaves_like 'a working graphql query' - specify { expect(integration_data['token']).to eql grafana_integration.masked_token } specify { expect(integration_data['grafanaUrl']).to eql grafana_integration.grafana_url } specify do diff --git a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb index 1b654e660e3..4bce3c7fe0f 100644 --- a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb +++ b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb @@ -14,6 +14,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha) create(:design_version, issue: issue, created_designs: create_list(:design, 3, issue: issue)) end + let_it_be(:version) do create(:design_version, issue: issue, modified_designs: old_version.designs, diff --git a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb index 640ac95cd86..ee0085718b3 100644 --- a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb +++ b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb @@ -11,12 +11,15 @@ RSpec.describe 'Getting versions related to an issue' do let_it_be(:version_a) do create(:design_version, issue: issue) end + let_it_be(:version_b) do create(:design_version, issue: issue) end + let_it_be(:version_c) do create(:design_version, issue: issue) end + let_it_be(:version_d) do create(:design_version, issue: issue) end diff --git a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb index e25453510d5..a671ddc7ab1 100644 --- a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb +++ b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb @@ -30,7 +30,7 @@ RSpec.describe 'Getting designs related to an issue' do post_graphql(query(note_fields), current_user: nil) - designs_data = graphql_data['project']['issue']['designs']['designs'] + designs_data = graphql_data['project']['issue']['designCollection']['designs'] design_data = designs_data['nodes'].first note_data = design_data['notes']['nodes'].first @@ -56,7 +56,7 @@ RSpec.describe 'Getting designs related to an issue' do 'issue', { iid: design.issue.iid.to_s }, query_graphql_field( - 'designs', {}, design_node + 'designCollection', {}, design_node ) ) ) diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index 40fec6ba068..4f27f08bf98 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -9,10 +9,9 @@ RSpec.describe 'getting an issue list for a project' do let_it_be(:project) { create(:project, :repository, :public) } let_it_be(:current_user) { create(:user) } - let_it_be(:issues, reload: true) do - [create(:issue, project: project, discussion_locked: true), - create(:issue, :with_alert, project: project)] - end + let_it_be(:issue_a, reload: true) { create(:issue, project: project, discussion_locked: true) } + let_it_be(:issue_b, reload: true) { create(:issue, :with_alert, project: project) } + let_it_be(:issues, reload: true) { [issue_a, issue_b] } let(:fields) do <<~QUERY @@ -414,4 +413,42 @@ RSpec.describe 'getting an issue list for a project' do expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(new_issues)) end end + + describe 'N+1 query checks' do + let(:extra_iid_for_second_query) { issue_b.iid.to_s } + let(:search_params) { { iids: [issue_a.iid.to_s] } } + + def execute_query + query = graphql_query_for( + :project, + { full_path: project.full_path }, + query_graphql_field(:issues, search_params, [ + query_graphql_field(:nodes, nil, requested_fields) + ]) + ) + post_graphql(query, current_user: current_user) + end + + context 'when requesting `user_notes_count`' do + let(:requested_fields) { [:user_notes_count] } + + before do + create_list(:note_on_issue, 2, noteable: issue_a, project: project) + create(:note_on_issue, noteable: issue_b, project: project) + end + + include_examples 'N+1 query check' + end + + context 'when requesting `user_discussions_count`' do + let(:requested_fields) { [:user_discussions_count] } + + before do + create_list(:note_on_issue, 2, noteable: issue_a, project: project) + create(:note_on_issue, noteable: issue_b, project: project) + end + + include_examples 'N+1 query check' + end + end end diff --git a/spec/requests/api/graphql/project/jira_import_spec.rb b/spec/requests/api/graphql/project/jira_import_spec.rb index 1cc30b95162..98a3f08baa6 100644 --- a/spec/requests/api/graphql/project/jira_import_spec.rb +++ b/spec/requests/api/graphql/project/jira_import_spec.rb @@ -19,6 +19,7 @@ RSpec.describe 'query Jira import data' do total_issue_count: 4 ) end + let_it_be(:jira_import2) do create( :jira_import_state, :finished, @@ -31,6 +32,7 @@ RSpec.describe 'query Jira import data' do total_issue_count: 3 ) end + let(:query) do %( query { diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index c737e0b8caf..2b8d537f9fc 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -243,6 +243,17 @@ RSpec.describe 'getting merge request listings nested in a project' do include_examples 'N+1 query check' end + + context 'when requesting `user_discussions_count`' do + let(:requested_fields) { [:user_discussions_count] } + + before do + create_list(:note_on_merge_request, 2, noteable: merge_request_a, project: project) + create(:note_on_merge_request, noteable: merge_request_c, project: project) + end + + include_examples 'N+1 query check' + end end describe 'sorting and pagination' do diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb index c226b10ab51..b57c594c64f 100644 --- a/spec/requests/api/graphql/project/project_statistics_spec.rb +++ b/spec/requests/api/graphql/project/project_statistics_spec.rb @@ -6,7 +6,7 @@ RSpec.describe 'rendering project statistics' do include GraphqlHelpers let(:project) { create(:project) } - let!(:project_statistics) { create(:project_statistics, project: project, packages_size: 5.gigabytes) } + let!(:project_statistics) { create(:project_statistics, project: project, packages_size: 5.gigabytes, uploads_size: 3.gigabytes) } let(:user) { create(:user) } let(:query) do @@ -31,6 +31,12 @@ RSpec.describe 'rendering project statistics' do expect(graphql_data['project']['statistics']['packagesSize']).to eq(5.gigabytes) end + it 'includes uploads size if the user can read the statistics' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :statistics, :uploadsSize)).to eq(3.gigabytes) + end + context 'when the project is public' do let(:project) { create(:project, :public) } diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb index 8fce29d0dc6..57dbe258ce4 100644 --- a/spec/requests/api/graphql/project/release_spec.rb +++ b/spec/requests/api/graphql/project/release_spec.rb @@ -13,7 +13,11 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do let_it_be(:link_filepath) { '/direct/asset/link/path' } let_it_be(:released_at) { Time.now - 1.day } - let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } } + let(:base_url_params) { { scope: 'all', release_tag: release.tag } } + let(:opened_url_params) { { state: 'opened', **base_url_params } } + let(:merged_url_params) { { state: 'merged', **base_url_params } } + let(:closed_url_params) { { state: 'closed', **base_url_params } } + let(:post_query) { post_graphql(query, current_user: current_user) } let(:path_prefix) { %w[project release] } let(:data) { graphql_data.dig(*path) } @@ -143,7 +147,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do 'name' => link.name, 'url' => link.url, 'external' => link.external?, - 'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << link.filepath : link.url + 'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << "/downloads#{link.filepath}" : link.url } end @@ -180,8 +184,11 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do let(:release_fields) do query_graphql_field(:links, nil, %{ selfUrl - mergeRequestsUrl - issuesUrl + openedMergeRequestsUrl + mergedMergeRequestsUrl + closedMergeRequestsUrl + openedIssuesUrl + closedIssuesUrl }) end @@ -190,8 +197,11 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do expect(data).to eq( 'selfUrl' => project_release_url(project, release), - 'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs), - 'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs) + 'openedMergeRequestsUrl' => project_merge_requests_url(project, opened_url_params), + 'mergedMergeRequestsUrl' => project_merge_requests_url(project, merged_url_params), + 'closedMergeRequestsUrl' => project_merge_requests_url(project, closed_url_params), + 'openedIssuesUrl' => project_issues_url(project, opened_url_params), + 'closedIssuesUrl' => project_issues_url(project, closed_url_params) ) end end diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb index 7c57c0e9177..6e364c7d7b5 100644 --- a/spec/requests/api/graphql/project/releases_spec.rb +++ b/spec/requests/api/graphql/project/releases_spec.rb @@ -10,6 +10,11 @@ RSpec.describe 'Query.project(fullPath).releases()' do let_it_be(:reporter) { create(:user) } let_it_be(:developer) { create(:user) } + let(:base_url_params) { { scope: 'all', release_tag: release.tag } } + let(:opened_url_params) { { state: 'opened', **base_url_params } } + let(:merged_url_params) { { state: 'merged', **base_url_params } } + let(:closed_url_params) { { state: 'closed', **base_url_params } } + let(:query) do graphql_query_for(:project, { fullPath: project.full_path }, %{ @@ -37,8 +42,11 @@ RSpec.describe 'Query.project(fullPath).releases()' do } links { selfUrl - mergeRequestsUrl - issuesUrl + openedMergeRequestsUrl + mergedMergeRequestsUrl + closedMergeRequestsUrl + openedIssuesUrl + closedIssuesUrl } } } @@ -101,8 +109,11 @@ RSpec.describe 'Query.project(fullPath).releases()' do }, 'links' => { 'selfUrl' => project_release_url(project, release), - 'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs), - 'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs) + 'openedMergeRequestsUrl' => project_merge_requests_url(project, opened_url_params), + 'mergedMergeRequestsUrl' => project_merge_requests_url(project, merged_url_params), + 'closedMergeRequestsUrl' => project_merge_requests_url(project, closed_url_params), + 'openedIssuesUrl' => project_issues_url(project, opened_url_params), + 'closedIssuesUrl' => project_issues_url(project, closed_url_params) } ) end @@ -300,4 +311,77 @@ RSpec.describe 'Query.project(fullPath).releases()' do it_behaves_like 'no access to any release data' end end + + describe 'sorting behavior' do + let_it_be(:today) { Time.now } + let_it_be(:yesterday) { today - 1.day } + let_it_be(:tomorrow) { today + 1.day } + + let_it_be(:project) { create(:project, :repository, :public) } + + let_it_be(:release_v1) { create(:release, project: project, tag: 'v1', released_at: yesterday, created_at: tomorrow) } + let_it_be(:release_v2) { create(:release, project: project, tag: 'v2', released_at: today, created_at: yesterday) } + let_it_be(:release_v3) { create(:release, project: project, tag: 'v3', released_at: tomorrow, created_at: today) } + + let(:current_user) { developer } + + let(:params) { nil } + + let(:sorted_tags) do + graphql_data.dig('project', 'releases', 'nodes').map { |release| release['tagName'] } + end + + let(:query) do + graphql_query_for(:project, { fullPath: project.full_path }, + %{ + releases#{params ? "(#{params})" : ""} { + nodes { + tagName + } + } + }) + end + + before do + post_query + end + + context 'when no sort: parameter is provided' do + it 'returns the results with the default sort applied (sort: RELEASED_AT_DESC)' do + expect(sorted_tags).to eq(%w(v3 v2 v1)) + end + end + + context 'with sort: RELEASED_AT_DESC' do + let(:params) { 'sort: RELEASED_AT_DESC' } + + it 'returns the releases ordered by released_at in descending order' do + expect(sorted_tags).to eq(%w(v3 v2 v1)) + end + end + + context 'with sort: RELEASED_AT_ASC' do + let(:params) { 'sort: RELEASED_AT_ASC' } + + it 'returns the releases ordered by released_at in ascending order' do + expect(sorted_tags).to eq(%w(v1 v2 v3)) + end + end + + context 'with sort: CREATED_DESC' do + let(:params) { 'sort: CREATED_DESC' } + + it 'returns the releases ordered by created_at in descending order' do + expect(sorted_tags).to eq(%w(v1 v3 v2)) + end + end + + context 'with sort: CREATED_ASC' do + let(:params) { 'sort: CREATED_ASC' } + + it 'returns the releases ordered by created_at in ascending order' do + expect(sorted_tags).to eq(%w(v2 v3 v1)) + end + end + end end diff --git a/spec/requests/api/graphql/project/terraform/states_spec.rb b/spec/requests/api/graphql/project/terraform/states_spec.rb new file mode 100644 index 00000000000..8b67b549efa --- /dev/null +++ b/spec/requests/api/graphql/project/terraform/states_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'query terraform states' do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:terraform_state) { create(:terraform_state, :with_version, :locked, project: project) } + let_it_be(:latest_version) { terraform_state.latest_version } + + let(:query) do + graphql_query_for(:project, { fullPath: project.full_path }, + %{ + terraformStates { + count + nodes { + id + name + lockedAt + createdAt + updatedAt + + latestVersion { + id + createdAt + updatedAt + + createdByUser { + id + } + + job { + name + } + } + + lockedByUser { + id + } + } + } + }) + end + + let(:current_user) { project.creator } + let(:data) { graphql_data.dig('project', 'terraformStates') } + + before do + post_graphql(query, current_user: current_user) + end + + it 'returns terraform state data', :aggregate_failures do + state = data.dig('nodes', 0) + version = state['latestVersion'] + + expect(state['id']).to eq(terraform_state.to_global_id.to_s) + expect(state['name']).to eq(terraform_state.name) + expect(state['lockedAt']).to eq(terraform_state.locked_at.iso8601) + expect(state['createdAt']).to eq(terraform_state.created_at.iso8601) + expect(state['updatedAt']).to eq(terraform_state.updated_at.iso8601) + expect(state.dig('lockedByUser', 'id')).to eq(terraform_state.locked_by_user.to_global_id.to_s) + + expect(version['id']).to eq(latest_version.to_global_id.to_s) + expect(version['createdAt']).to eq(latest_version.created_at.iso8601) + expect(version['updatedAt']).to eq(latest_version.updated_at.iso8601) + expect(version.dig('createdByUser', 'id')).to eq(latest_version.created_by_user.to_global_id.to_s) + expect(version.dig('job', 'name')).to eq(latest_version.build.name) + end + + it 'returns count of terraform states' do + count = data.dig('count') + expect(count).to be(project.terraform_states.size) + end + + context 'unauthorized users' do + let(:current_user) { nil } + + it { expect(data).to be_nil } + end +end diff --git a/spec/requests/api/graphql/read_only_spec.rb b/spec/requests/api/graphql/read_only_spec.rb index ce8a3f6ef5c..d2a45603886 100644 --- a/spec/requests/api/graphql/read_only_spec.rb +++ b/spec/requests/api/graphql/read_only_spec.rb @@ -3,55 +3,11 @@ require 'spec_helper' RSpec.describe 'Requests on a read-only node' do - include GraphqlHelpers - - before do - allow(Gitlab::Database).to receive(:read_only?) { true } - end - - context 'mutations' do - let(:current_user) { note.author } - let!(:note) { create(:note) } - - let(:mutation) do - variables = { - id: GitlabSchema.id_from_object(note).to_s - } - - graphql_mutation(:destroy_note, variables) - end - - def mutation_response - graphql_mutation_response(:destroy_note) - end - - it 'disallows the query' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(json_response['errors'].first['message']).to eq(Mutations::BaseMutation::ERROR_MESSAGE) - end - - it 'does not destroy the Note' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - end.not_to change { Note.count } - end - end - - context 'read-only queries' do - let(:current_user) { create(:user) } - let(:project) { create(:project, :repository) } - + context 'when db is read-only' do before do - project.add_developer(current_user) + allow(Gitlab::Database).to receive(:read_only?) { true } end - it 'allows the query' do - query = graphql_query_for('project', 'fullPath' => project.full_path) - - post_graphql(query, current_user: current_user) - - expect(graphql_data['project']).not_to be_nil - end + it_behaves_like 'graphql on a read-only GitLab instance' end end diff --git a/spec/requests/api/graphql/terraform/state/delete_spec.rb b/spec/requests/api/graphql/terraform/state/delete_spec.rb new file mode 100644 index 00000000000..35927d03b49 --- /dev/null +++ b/spec/requests/api/graphql/terraform/state/delete_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'delete a terraform state' do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user, maintainer_projects: [project]) } + + let(:state) { create(:terraform_state, project: project) } + let(:mutation) { graphql_mutation(:terraform_state_delete, id: state.to_global_id.to_s) } + + before do + post_graphql_mutation(mutation, current_user: user) + end + + include_examples 'a working graphql query' + + it 'deletes the state' do + expect { state.reload }.to raise_error(ActiveRecord::RecordNotFound) + end +end diff --git a/spec/requests/api/graphql/terraform/state/lock_spec.rb b/spec/requests/api/graphql/terraform/state/lock_spec.rb new file mode 100644 index 00000000000..e4d3b6336ab --- /dev/null +++ b/spec/requests/api/graphql/terraform/state/lock_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'lock a terraform state' do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user, maintainer_projects: [project]) } + + let(:state) { create(:terraform_state, project: project) } + let(:mutation) { graphql_mutation(:terraform_state_lock, id: state.to_global_id.to_s) } + + before do + expect(state).not_to be_locked + post_graphql_mutation(mutation, current_user: user) + end + + include_examples 'a working graphql query' + + it 'locks the state' do + expect(state.reload).to be_locked + expect(state.locked_by_user).to eq(user) + end +end diff --git a/spec/requests/api/graphql/terraform/state/unlock_spec.rb b/spec/requests/api/graphql/terraform/state/unlock_spec.rb new file mode 100644 index 00000000000..e90730f2d8f --- /dev/null +++ b/spec/requests/api/graphql/terraform/state/unlock_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'unlock a terraform state' do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user, maintainer_projects: [project]) } + + let(:state) { create(:terraform_state, :locked, project: project) } + let(:mutation) { graphql_mutation(:terraform_state_unlock, id: state.to_global_id.to_s) } + + before do + expect(state).to be_locked + post_graphql_mutation(mutation, current_user: user) + end + + include_examples 'a working graphql query' + + it 'unlocks the state' do + expect(state.reload).not_to be_locked + end +end diff --git a/spec/requests/api/graphql/user/group_member_query_spec.rb b/spec/requests/api/graphql/user/group_member_query_spec.rb index 3a16d962214..e47cef8cc37 100644 --- a/spec/requests/api/graphql/user/group_member_query_spec.rb +++ b/spec/requests/api/graphql/user/group_member_query_spec.rb @@ -19,6 +19,7 @@ RSpec.describe 'GroupMember' do } HEREDOC end + let_it_be(:query) do graphql_query_for('user', { id: member.user.to_global_id.to_s }, query_graphql_field("groupMemberships", {}, fields)) end diff --git a/spec/requests/api/graphql/user/project_member_query_spec.rb b/spec/requests/api/graphql/user/project_member_query_spec.rb index 0790e148caf..01827e94d5d 100644 --- a/spec/requests/api/graphql/user/project_member_query_spec.rb +++ b/spec/requests/api/graphql/user/project_member_query_spec.rb @@ -19,6 +19,7 @@ RSpec.describe 'ProjectMember' do } HEREDOC end + let_it_be(:query) do graphql_query_for('user', { id: member.user.to_global_id.to_s }, query_graphql_field("projectMemberships", {}, fields)) end diff --git a/spec/requests/api/graphql/user_query_spec.rb b/spec/requests/api/graphql/user_query_spec.rb index 79debd0b7ef..738e120549e 100644 --- a/spec/requests/api/graphql/user_query_spec.rb +++ b/spec/requests/api/graphql/user_query_spec.rb @@ -32,22 +32,27 @@ RSpec.describe 'getting user information' do create(:merge_request, :unique_branches, :unique_author, source_project: project_a, assignees: [user]) end + let_it_be(:assigned_mr_b) do create(:merge_request, :unique_branches, :unique_author, source_project: project_b, assignees: [user]) end + let_it_be(:assigned_mr_c) do create(:merge_request, :unique_branches, :unique_author, source_project: project_b, assignees: [user]) end + let_it_be(:authored_mr) do create(:merge_request, :unique_branches, source_project: project_a, author: user) end + let_it_be(:authored_mr_b) do create(:merge_request, :unique_branches, source_project: project_b, author: user) end + let_it_be(:authored_mr_c) do create(:merge_request, :unique_branches, source_project: project_b, author: user) @@ -59,6 +64,7 @@ RSpec.describe 'getting user information' do let(:user_params) { { username: user.username } } before do + create(:user_status, user: user) post_graphql(query, current_user: current_user) end @@ -76,9 +82,15 @@ RSpec.describe 'getting user information' do 'username' => presenter.username, 'webUrl' => presenter.web_url, 'avatarUrl' => presenter.avatar_url, - 'status' => presenter.status, 'email' => presenter.email )) + + expect(graphql_data['user']['status']).to match( + a_hash_including( + 'emoji' => presenter.status.emoji, + 'message' => presenter.status.message, + 'availability' => presenter.status.availability.upcase + )) end describe 'assignedMergeRequests' do |