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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests/api/graphql')
-rw-r--r--spec/requests/api/graphql/ci/config_spec.rb6
-rw-r--r--spec/requests/api/graphql/ci/config_variables_spec.rb93
-rw-r--r--spec/requests/api/graphql/ci/group_variables_spec.rb12
-rw-r--r--spec/requests/api/graphql/ci/instance_variables_spec.rb2
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb31
-rw-r--r--spec/requests/api/graphql/ci/project_variables_spec.rb4
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb163
-rw-r--r--spec/requests/api/graphql/ci/runners_spec.rb18
-rw-r--r--spec/requests/api/graphql/custom_emoji_query_spec.rb12
-rw-r--r--spec/requests/api/graphql/environments/deployments_query_spec.rb487
-rw-r--r--spec/requests/api/graphql/group/group_members_spec.rb9
-rw-r--r--spec/requests/api/graphql/group/packages_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/work_item_types_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/branches/create_spec.rb89
-rw-r--r--spec/requests/api/graphql/mutations/ci/job/destroy_spec.rb54
-rw-r--r--spec/requests/api/graphql/mutations/ci/job_artifact/destroy_spec.rb47
-rw-r--r--spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb10
-rw-r--r--spec/requests/api/graphql/mutations/releases/update_spec.rb11
-rw-r--r--spec/requests/api/graphql/packages/composer_spec.rb2
-rw-r--r--spec/requests/api/graphql/packages/conan_spec.rb2
-rw-r--r--spec/requests/api/graphql/packages/helm_spec.rb2
-rw-r--r--spec/requests/api/graphql/packages/maven_spec.rb6
-rw-r--r--spec/requests/api/graphql/packages/nuget_spec.rb2
-rw-r--r--spec/requests/api/graphql/packages/package_spec.rb15
-rw-r--r--spec/requests/api/graphql/packages/pypi_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb109
-rw-r--r--spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb109
-rw-r--r--spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb60
-rw-r--r--spec/requests/api/graphql/project/branch_rules_spec.rb122
-rw-r--r--spec/requests/api/graphql/project/deployment_spec.rb51
-rw-r--r--spec/requests/api/graphql/project/environments_spec.rb133
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/version_spec.rb10
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb26
-rw-r--r--spec/requests/api/graphql/project/job_spec.rb54
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb20
-rw-r--r--spec/requests/api/graphql/project/pipeline_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/terraform/state_spec.rb16
-rw-r--r--spec/requests/api/graphql/project/terraform/states_spec.rb18
-rw-r--r--spec/requests/api/graphql/project/work_items_spec.rb63
-rw-r--r--spec/requests/api/graphql/query_spec.rb4
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb19
45 files changed, 1817 insertions, 127 deletions
diff --git a/spec/requests/api/graphql/ci/config_spec.rb b/spec/requests/api/graphql/ci/config_spec.rb
index 5f8a895b16e..960fda80dd9 100644
--- a/spec/requests/api/graphql/ci/config_spec.rb
+++ b/spec/requests/api/graphql/ci/config_spec.rb
@@ -173,7 +173,7 @@ RSpec.describe 'Query.ciConfig' do
{
"name" => "docker",
"size" => 1,
- "jobs" =>
+ "jobs" =>
{
"nodes" => [
{
@@ -206,7 +206,7 @@ RSpec.describe 'Query.ciConfig' do
{
"name" => "deploy_job",
"size" => 1,
- "jobs" =>
+ "jobs" =>
{
"nodes" => [
{
@@ -332,7 +332,7 @@ RSpec.describe 'Query.ciConfig' do
"only" => { "refs" => %w[branches tags] },
"when" => "on_success",
"tags" => [],
- "needs" => { "nodes" => [] } }
+ "needs" => { "nodes" => [] } }
]
}
}
diff --git a/spec/requests/api/graphql/ci/config_variables_spec.rb b/spec/requests/api/graphql/ci/config_variables_spec.rb
new file mode 100644
index 00000000000..2b5a5d0dc93
--- /dev/null
+++ b/spec/requests/api/graphql/ci/config_variables_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.project(fullPath).ciConfigVariables(sha)' do
+ include GraphqlHelpers
+ include ReactiveCachingHelpers
+
+ let_it_be(:content) do
+ File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+ end
+
+ let_it_be(:project) { create(:project, :custom_repo, :public, files: { '.gitlab-ci.yml' => content }) }
+ let_it_be(:user) { create(:user) }
+
+ let(:service) { Ci::ListConfigVariablesService.new(project, user) }
+ let(:sha) { project.repository.commit.sha }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ ciConfigVariables(sha: "#{sha}") {
+ key
+ value
+ description
+ }
+ }
+ }
+ )
+ end
+
+ context 'when the user has the correct permissions' do
+ before do
+ project.add_maintainer(user)
+ allow(Ci::ListConfigVariablesService)
+ .to receive(:new)
+ .and_return(service)
+ end
+
+ context 'when the cache is not empty' do
+ before do
+ synchronous_reactive_cache(service)
+ end
+
+ it 'returns the CI variables for the config' do
+ expect(service)
+ .to receive(:execute)
+ .with(sha)
+ .and_call_original
+
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('project', 'ciConfigVariables')).to contain_exactly(
+ {
+ 'key' => 'DB_NAME',
+ 'value' => 'postgres',
+ 'description' => nil
+ },
+ {
+ 'key' => 'ENVIRONMENT_VAR',
+ 'value' => 'env var value',
+ 'description' => 'env var description'
+ }
+ )
+ end
+ end
+
+ context 'when the cache is empty' do
+ it 'returns nothing' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('project', 'ciConfigVariables')).to be_nil
+ end
+ end
+ end
+
+ context 'when the user is not authorized' do
+ before do
+ project.add_guest(user)
+ allow(Ci::ListConfigVariablesService)
+ .to receive(:new)
+ .and_return(service)
+ synchronous_reactive_cache(service)
+ end
+
+ it 'returns nothing' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('project', 'ciConfigVariables')).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/ci/group_variables_spec.rb b/spec/requests/api/graphql/ci/group_variables_spec.rb
index 5ea6646ec2c..7baf26c7648 100644
--- a/spec/requests/api/graphql/ci/group_variables_spec.rb
+++ b/spec/requests/api/graphql/ci/group_variables_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe 'Query.group(fullPath).ciVariables' do
query {
group(fullPath: "#{group.full_path}") {
ciVariables {
+ limit
nodes {
id
key
@@ -35,11 +36,18 @@ RSpec.describe 'Query.group(fullPath).ciVariables' do
end
it "returns the group's CI variables" do
- variable = create(:ci_group_variable, group: group, key: 'TEST_VAR', value: 'test',
- masked: false, protected: true, raw: true, environment_scope: 'staging')
+ variable = create(:ci_group_variable,
+ group: group,
+ key: 'TEST_VAR',
+ value: 'test',
+ masked: false,
+ protected: true,
+ raw: true,
+ environment_scope: 'staging')
post_graphql(query, current_user: user)
+ expect(graphql_data.dig('group', 'ciVariables', 'limit')).to be(200)
expect(graphql_data.dig('group', 'ciVariables', 'nodes')).to contain_exactly({
'id' => variable.to_global_id.to_s,
'key' => 'TEST_VAR',
diff --git a/spec/requests/api/graphql/ci/instance_variables_spec.rb b/spec/requests/api/graphql/ci/instance_variables_spec.rb
index c5c88697bf4..cd6b2de98a1 100644
--- a/spec/requests/api/graphql/ci/instance_variables_spec.rb
+++ b/spec/requests/api/graphql/ci/instance_variables_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe 'Query.ciVariables' do
it "returns the instance's CI variables" do
variable = create(:ci_instance_variable, key: 'TEST_VAR', value: 'test',
- masked: false, protected: true, raw: true)
+ masked: false, protected: true, raw: true)
post_graphql(query, current_user: user)
diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb
index 8c4ab13fc35..fa8fb1d54aa 100644
--- a/spec/requests/api/graphql/ci/jobs_spec.rb
+++ b/spec/requests/api/graphql/ci/jobs_spec.rb
@@ -335,4 +335,35 @@ RSpec.describe 'Query.project.pipeline' do
end
end
end
+
+ context 'when querying jobs for multiple projects' do
+ let(:query) do
+ %(
+ query {
+ projects {
+ nodes {
+ jobs {
+ nodes {
+ name
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ before do
+ create_list(:project, 2).each do |project|
+ project.add_developer(user)
+ create(:ci_build, project: project)
+ end
+ end
+
+ it 'returns an error' do
+ post_graphql(query, current_user: user)
+
+ expect_graphql_errors_to_include [/"jobs" field can be requested only for 1 Project\(s\) at a time./]
+ end
+ end
end
diff --git a/spec/requests/api/graphql/ci/project_variables_spec.rb b/spec/requests/api/graphql/ci/project_variables_spec.rb
index e61f146b24c..d49a4a7e768 100644
--- a/spec/requests/api/graphql/ci/project_variables_spec.rb
+++ b/spec/requests/api/graphql/ci/project_variables_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe 'Query.project(fullPath).ciVariables' do
query {
project(fullPath: "#{project.full_path}") {
ciVariables {
+ limit
nodes {
id
key
@@ -36,10 +37,11 @@ RSpec.describe 'Query.project(fullPath).ciVariables' do
it "returns the project's CI variables" do
variable = create(:ci_variable, project: project, key: 'TEST_VAR', value: 'test',
- masked: false, protected: true, raw: true, environment_scope: 'production')
+ masked: false, protected: true, raw: true, environment_scope: 'production')
post_graphql(query, current_user: user)
+ expect(graphql_data.dig('project', 'ciVariables', 'limit')).to be(200)
expect(graphql_data.dig('project', 'ciVariables', 'nodes')).to contain_exactly({
'id' => variable.to_global_id.to_s,
'key' => 'TEST_VAR',
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index e17a83d8e47..bd90753f9ad 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -9,24 +9,53 @@ RSpec.describe 'Query.runner(id)' do
let_it_be(:group) { create(:group) }
let_it_be(:active_instance_runner) do
- create(:ci_runner, :instance, description: 'Runner 1', contacted_at: 2.hours.ago,
- active: true, version: 'adfe156', revision: 'a', locked: true, ip_address: '127.0.0.1', maximum_timeout: 600,
- access_level: 0, tag_list: %w[tag1 tag2], run_untagged: true, executor_type: :custom,
- maintenance_note: '**Test maintenance note**')
+ create(:ci_runner, :instance,
+ description: 'Runner 1',
+ contacted_at: 2.hours.ago,
+ active: true,
+ version: 'adfe156',
+ revision: 'a',
+ locked: true,
+ ip_address: '127.0.0.1',
+ maximum_timeout: 600,
+ access_level: 0,
+ tag_list: %w[tag1 tag2],
+ run_untagged: true,
+ executor_type: :custom,
+ maintenance_note: '**Test maintenance note**')
end
let_it_be(:inactive_instance_runner) do
- create(:ci_runner, :instance, description: 'Runner 2', contacted_at: 1.day.ago, active: false,
- version: 'adfe157', revision: 'b', ip_address: '10.10.10.10', access_level: 1, run_untagged: true)
+ create(:ci_runner, :instance,
+ description: 'Runner 2',
+ contacted_at: 1.day.ago,
+ active: false,
+ version: 'adfe157',
+ revision: 'b',
+ ip_address: '10.10.10.10',
+ access_level: 1,
+ run_untagged: true)
end
let_it_be(:active_group_runner) do
- create(:ci_runner, :group, groups: [group], description: 'Group runner 1', contacted_at: 2.hours.ago,
- active: true, version: 'adfe156', revision: 'a', locked: true, ip_address: '127.0.0.1', maximum_timeout: 600,
- access_level: 0, tag_list: %w[tag1 tag2], run_untagged: true, executor_type: :shell)
+ create(:ci_runner, :group,
+ groups: [group],
+ description: 'Group runner 1',
+ contacted_at: 2.hours.ago,
+ active: true,
+ version: 'adfe156',
+ revision: 'a',
+ locked: true,
+ ip_address: '127.0.0.1',
+ maximum_timeout: 600,
+ access_level: 0,
+ tag_list: %w[tag1 tag2],
+ run_untagged: true,
+ executor_type: :shell)
end
- let_it_be(:active_project_runner) { create(:ci_runner, :project) }
+ let_it_be(:project1) { create(:project) }
+ let_it_be(:active_project_runner) { create(:ci_runner, :project, projects: [project1]) }
shared_examples 'runner details fetch' do
let(:query) do
@@ -159,8 +188,16 @@ RSpec.describe 'Query.runner(id)' do
with_them do
let(:project_runner) do
- create(:ci_runner, :project, description: 'Runner 3', contacted_at: 1.day.ago, active: false, locked: is_locked,
- version: 'adfe157', revision: 'b', ip_address: '10.10.10.10', access_level: 1, run_untagged: true)
+ create(:ci_runner, :project,
+ description: 'Runner 3',
+ contacted_at: 1.day.ago,
+ active: false,
+ locked: is_locked,
+ version: 'adfe157',
+ revision: 'b',
+ ip_address: '10.10.10.10',
+ access_level: 1,
+ run_untagged: true)
end
let(:query) do
@@ -187,7 +224,6 @@ RSpec.describe 'Query.runner(id)' do
end
describe 'ownerProject' do
- let_it_be(:project1) { create(:project) }
let_it_be(:project2) { create(:project) }
let_it_be(:runner1) { create(:ci_runner, :project, projects: [project2, project1]) }
let_it_be(:runner2) { create(:ci_runner, :project, projects: [project1, project2]) }
@@ -301,7 +337,6 @@ RSpec.describe 'Query.runner(id)' do
end
describe 'for multiple runners' do
- let_it_be(:project1) { create(:project, :test_repo) }
let_it_be(:project2) { create(:project, :test_repo) }
let_it_be(:project_runner1) { create(:ci_runner, :project, projects: [project1, project2], description: 'Runner 1') }
let_it_be(:project_runner2) { create(:ci_runner, :project, projects: [], description: 'Runner 2') }
@@ -394,6 +429,8 @@ RSpec.describe 'Query.runner(id)' do
'jobs' => nil, # returning jobs not allowed for more than 1 runner (see RunnerJobsResolver)
'projectCount' => nil,
'projects' => nil)
+
+ expect_graphql_errors_to_include [/"jobs" field can be requested only for 1 CiRunner\(s\) at a time./]
end
end
end
@@ -472,8 +509,8 @@ RSpec.describe 'Query.runner(id)' do
<<~QUERY
{
instance_runner1: #{runner_query(active_instance_runner)}
- project_runner1: #{runner_query(active_project_runner)}
group_runner1: #{runner_query(active_group_runner)}
+ project_runner1: #{runner_query(active_project_runner)}
}
QUERY
end
@@ -493,12 +530,13 @@ RSpec.describe 'Query.runner(id)' do
it 'does not execute more queries per runner', :aggregate_failures do
# warm-up license cache and so on:
- post_graphql(double_query, current_user: user)
+ personal_access_token = create(:personal_access_token, user: user)
+ args = { current_user: user, token: { personal_access_token: personal_access_token } }
+ post_graphql(double_query, **args)
- control = ActiveRecord::QueryRecorder.new { post_graphql(single_query, current_user: user) }
+ control = ActiveRecord::QueryRecorder.new { post_graphql(single_query, **args) }
- expect { post_graphql(double_query, current_user: user) }
- .not_to exceed_query_limit(control)
+ expect { post_graphql(double_query, **args) }.not_to exceed_query_limit(control)
expect(graphql_data.count).to eq 6
expect(graphql_data).to match(
@@ -528,4 +566,91 @@ RSpec.describe 'Query.runner(id)' do
))
end
end
+
+ describe 'sorting and pagination' do
+ let(:query) do
+ <<~GQL
+ query($id: CiRunnerID!, $projectSearchTerm: String, $n: Int, $cursor: String) {
+ runner(id: $id) {
+ #{fields}
+ }
+ }
+ GQL
+ end
+
+ before do
+ post_graphql(query, current_user: user, variables: variables)
+ end
+
+ context 'with project search term' do
+ let_it_be(:project1) { create(:project, description: 'abc') }
+ let_it_be(:project2) { create(:project, description: 'def') }
+ let_it_be(:project_runner) do
+ create(:ci_runner, :project, projects: [project1, project2])
+ end
+
+ let(:variables) { { id: project_runner.to_global_id.to_s, n: n, project_search_term: search_term } }
+
+ let(:fields) do
+ <<~QUERY
+ projects(search: $projectSearchTerm, first: $n, after: $cursor) {
+ count
+ nodes {
+ id
+ }
+ pageInfo {
+ hasPreviousPage
+ startCursor
+ endCursor
+ hasNextPage
+ }
+ }
+ QUERY
+ end
+
+ let(:projects_data) { graphql_data_at('runner', 'projects') }
+
+ context 'set to empty string' do
+ let(:search_term) { '' }
+
+ context 'with n = 1' do
+ let(:n) { 1 }
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns paged result' do
+ expect(projects_data).not_to be_nil
+ expect(projects_data['count']).to eq 2
+ expect(projects_data['pageInfo']['hasNextPage']).to eq true
+ end
+ end
+
+ context 'with n = 2' do
+ let(:n) { 2 }
+
+ it 'returns non-paged result' do
+ expect(projects_data).not_to be_nil
+ expect(projects_data['count']).to eq 2
+ expect(projects_data['pageInfo']['hasNextPage']).to eq false
+ end
+ end
+ end
+
+ context 'set to partial match' do
+ let(:search_term) { 'def' }
+
+ context 'with n = 1' do
+ let(:n) { 1 }
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns paged result with no additional pages' do
+ expect(projects_data).not_to be_nil
+ expect(projects_data['count']).to eq 1
+ expect(projects_data['pageInfo']['hasNextPage']).to eq false
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb
index 749f6839cb5..3054b866812 100644
--- a/spec/requests/api/graphql/ci/runners_spec.rb
+++ b/spec/requests/api/graphql/ci/runners_spec.rb
@@ -69,15 +69,6 @@ RSpec.describe 'Query.runners' do
it_behaves_like 'a working graphql query returning expected runner'
end
-
- context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do
- let(:runner_type) { 'PROJECT_TYPE' }
- let(:status) { 'NEVER_CONTACTED' }
-
- let!(:expected_runner) { project_runner }
-
- it_behaves_like 'a working graphql query returning expected runner'
- end
end
describe 'pagination' do
@@ -141,8 +132,13 @@ RSpec.describe 'Group.runners' do
describe 'edges' do
let_it_be(:runner) do
- create(:ci_runner, :group, active: false, version: 'def', revision: '456',
- description: 'Project runner', groups: [group], ip_address: '127.0.0.1')
+ create(:ci_runner, :group,
+ active: false,
+ version: 'def',
+ revision: '456',
+ description: 'Project runner',
+ groups: [group],
+ ip_address: '127.0.0.1')
end
let(:query) do
diff --git a/spec/requests/api/graphql/custom_emoji_query_spec.rb b/spec/requests/api/graphql/custom_emoji_query_spec.rb
index 13b7a22e791..5dd5ad117b0 100644
--- a/spec/requests/api/graphql/custom_emoji_query_spec.rb
+++ b/spec/requests/api/graphql/custom_emoji_query_spec.rb
@@ -35,7 +35,17 @@ RSpec.describe 'getting custom emoji within namespace' do
expect(graphql_data['group']['customEmoji']['nodes'].first['name']).to eq(custom_emoji.name)
end
- it 'returns nil when unauthorised' do
+ it 'returns nil custom emoji when the custom_emoji feature flag is disabled' do
+ stub_feature_flags(custom_emoji: false)
+
+ post_graphql(custom_emoji_query(group), current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(graphql_data['group']).to be_present
+ expect(graphql_data['group']['customEmoji']).to be_nil
+ end
+
+ it 'returns nil group when unauthorised' do
user = create(:user)
post_graphql(custom_emoji_query(group), current_user: user)
diff --git a/spec/requests/api/graphql/environments/deployments_query_spec.rb b/spec/requests/api/graphql/environments/deployments_query_spec.rb
new file mode 100644
index 00000000000..6da00057449
--- /dev/null
+++ b/spec/requests/api/graphql/environments/deployments_query_spec.rb
@@ -0,0 +1,487 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Environments Deployments query' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :private, :repository) }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+ let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
+
+ let(:user) { developer }
+
+ subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
+
+ context 'when there are deployments in the environment' do
+ let_it_be(:finished_deployment_old) do
+ create(:deployment, :success, environment: environment, project: project, finished_at: 2.days.ago)
+ end
+
+ let_it_be(:finished_deployment_new) do
+ create(:deployment, :success, environment: environment, project: project, finished_at: 1.day.ago)
+ end
+
+ let_it_be(:upcoming_deployment_old) do
+ create(:deployment, :created, environment: environment, project: project, created_at: 2.hours.ago)
+ end
+
+ let_it_be(:upcoming_deployment_new) do
+ create(:deployment, :created, environment: environment, project: project, created_at: 1.hour.ago)
+ end
+
+ let_it_be(:other_environment) { create(:environment, project: project) }
+ let_it_be(:other_deployment) { create(:deployment, :success, environment: other_environment, project: project) }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments {
+ nodes {
+ id
+ iid
+ ref
+ tag
+ sha
+ createdAt
+ updatedAt
+ finishedAt
+ status
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns all deployments of the environment' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ expect(deployments.count).to eq(4)
+ end
+
+ context 'when query last deployment' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments(statuses: [SUCCESS], orderBy: { finishedAt: DESC }, first: 1) {
+ nodes {
+ iid
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns deployment' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ expect(deployments.count).to eq(1)
+ expect(deployments[0]['iid']).to eq(finished_deployment_new.iid.to_s)
+ end
+ end
+
+ context 'when query latest upcoming deployment' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments(statuses: [CREATED RUNNING BLOCKED], orderBy: { createdAt: DESC }, first: 1) {
+ nodes {
+ iid
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns deployment' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ expect(deployments.count).to eq(1)
+ expect(deployments[0]['iid']).to eq(upcoming_deployment_new.iid.to_s)
+ end
+ end
+
+ context 'when query finished deployments in descending order' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments(statuses: [SUCCESS FAILED CANCELED], orderBy: { finishedAt: DESC }) {
+ nodes {
+ iid
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns deployments' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ expect(deployments.count).to eq(2)
+ expect(deployments[0]['iid']).to eq(finished_deployment_new.iid.to_s)
+ expect(deployments[1]['iid']).to eq(finished_deployment_old.iid.to_s)
+ end
+ end
+
+ context 'when query finished deployments in ascending order' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments(statuses: [SUCCESS FAILED CANCELED], orderBy: { finishedAt: ASC }) {
+ nodes {
+ iid
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns deployments' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ expect(deployments.count).to eq(2)
+ expect(deployments[0]['iid']).to eq(finished_deployment_old.iid.to_s)
+ expect(deployments[1]['iid']).to eq(finished_deployment_new.iid.to_s)
+ end
+ end
+
+ context 'when query upcoming deployments in descending order' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments(statuses: [CREATED RUNNING BLOCKED], orderBy: { createdAt: DESC }) {
+ nodes {
+ iid
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns deployments' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ expect(deployments.count).to eq(2)
+ expect(deployments[0]['iid']).to eq(upcoming_deployment_new.iid.to_s)
+ expect(deployments[1]['iid']).to eq(upcoming_deployment_old.iid.to_s)
+ end
+ end
+
+ context 'when query upcoming deployments in ascending order' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments(statuses: [CREATED RUNNING BLOCKED], orderBy: { createdAt: ASC }) {
+ nodes {
+ iid
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns deployments' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ expect(deployments.count).to eq(2)
+ expect(deployments[0]['iid']).to eq(upcoming_deployment_old.iid.to_s)
+ expect(deployments[1]['iid']).to eq(upcoming_deployment_new.iid.to_s)
+ end
+ end
+
+ context 'when query last deployments of multiple environments' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environments {
+ nodes {
+ name
+ deployments(statuses: [SUCCESS], orderBy: { finishedAt: DESC }, first: 1) {
+ nodes {
+ iid
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns an error for preventing N+1 queries' do
+ expect(subject['errors'][0]['message'])
+ .to include('"deployments" field can be requested only for 1 Environment(s) at a time.')
+ end
+ end
+
+ context 'when query finished and upcoming deployments together' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments(statuses: [CREATED SUCCESS]) {
+ nodes {
+ iid
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(DeploymentsFinder::InefficientQueryError)
+ end
+ end
+
+ context 'when multiple orderBy input are specified' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments(orderBy: { finishedAt: DESC, createdAt: ASC }) {
+ nodes {
+ iid
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'raises an error' do
+ expect(subject['errors'][0]['message']).to include('orderBy parameter must contain one key-value pair.')
+ end
+ end
+
+ context 'when user is guest' do
+ let(:user) { guest }
+
+ it 'returns nothing' do
+ expect(subject['data']['project']['environment']).to be_nil
+ end
+ end
+
+ shared_examples_for 'avoids N+1 database queries' do
+ it 'does not increase the query count' do
+ create_deployments
+
+ baseline = ActiveRecord::QueryRecorder.new do
+ run_with_clean_state(query, context: { current_user: user })
+ end
+
+ create_deployments
+
+ multi = ActiveRecord::QueryRecorder.new do
+ run_with_clean_state(query, context: { current_user: user })
+ end
+
+ expect(multi).not_to exceed_query_limit(baseline)
+ end
+
+ def create_deployments
+ create_list(:deployment, 3, environment: environment, project: project).each do |deployment|
+ deployment.user = create(:user).tap { |u| project.add_developer(u) }
+ deployment.deployable =
+ create(:ci_build, project: project, environment: environment.name, deployment: deployment,
+ user: deployment.user)
+
+ deployment.save!
+ end
+ end
+ end
+
+ context 'when requesting commits of deployments' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments {
+ nodes {
+ iid
+ commit {
+ author {
+ avatarUrl
+ name
+ webPath
+ }
+ fullTitle
+ webPath
+ sha
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it_behaves_like 'avoids N+1 database queries'
+
+ it 'returns commits of deployments' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ deployments.each do |deployment|
+ deployment_in_record = project.deployments.find_by_iid(deployment['iid'])
+
+ expect(deployment_in_record.sha).to eq(deployment['commit']['sha'])
+ end
+ end
+ end
+
+ context 'when requesting triggerers of deployments' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments {
+ nodes {
+ iid
+ triggerer {
+ id
+ avatarUrl
+ name
+ webPath
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it_behaves_like 'avoids N+1 database queries'
+
+ it 'returns triggerers of deployments' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ deployments.each do |deployment|
+ deployment_in_record = project.deployments.find_by_iid(deployment['iid'])
+
+ expect(deployment_in_record.deployed_by.name).to eq(deployment['triggerer']['name'])
+ end
+ end
+ end
+
+ context 'when requesting jobs of deployments' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments {
+ nodes {
+ iid
+ job {
+ id
+ status
+ name
+ webPath
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it_behaves_like 'avoids N+1 database queries'
+
+ it 'returns jobs of deployments' do
+ deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes')
+
+ deployments.each do |deployment|
+ deployment_in_record = project.deployments.find_by_iid(deployment['iid'])
+
+ expect(deployment_in_record.build.to_global_id.to_s).to eq(deployment['job']['id'])
+ end
+ end
+ end
+
+ describe 'sorting and pagination' do
+ let(:data_path) { [:project, :environment, :deployments] }
+ let(:current_user) { user }
+
+ def pagination_query(params)
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{environment.name}") {
+ deployments(statuses: [SUCCESS], #{params}) {
+ nodes {
+ iid
+ }
+ pageInfo {
+ startCursor
+ endCursor
+ hasNextPage
+ hasPreviousPage
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ def pagination_results_data(nodes)
+ nodes.map { |deployment| deployment['iid'].to_i }
+ end
+
+ context 'when sorting by finished_at in ascending order' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_argument) { graphql_args(orderBy: { finishedAt: :ASC }) }
+ let(:first_param) { 2 }
+ let(:all_records) { [finished_deployment_old.iid, finished_deployment_new.iid] }
+ end
+ end
+
+ context 'when sorting by finished_at in descending order' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_argument) { graphql_args(orderBy: { finishedAt: :DESC }) }
+ let(:first_param) { 2 }
+ let(:all_records) { [finished_deployment_new.iid, finished_deployment_old.iid] }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/group/group_members_spec.rb b/spec/requests/api/graphql/group/group_members_spec.rb
index bab8d5b770c..5f8becc0726 100644
--- a/spec/requests/api/graphql/group/group_members_spec.rb
+++ b/spec/requests/api/graphql/group/group_members_spec.rb
@@ -156,13 +156,20 @@ RSpec.describe 'getting group members information' do
expect_array_response(child_user)
end
- it 'returns invited members plus inherited members' do
+ it 'returns invited members and inherited members of a shared group' do
fetch_members(group: child_group, args: { relations: [:DIRECT, :INHERITED, :SHARED_FROM_GROUPS] })
expect(graphql_errors).to be_nil
expect_array_response(invited_user, user_1, user_2, child_user)
end
+ it 'returns invited members and inherited members of an ancestor of a shared group' do
+ fetch_members(group: grandchild_group, args: { relations: [:DIRECT, :INHERITED, :SHARED_FROM_GROUPS] })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(grandchild_user, invited_user, user_1, user_2, child_user)
+ end
+
it 'returns direct and inherited members' do
fetch_members(group: child_group, args: { relations: [:DIRECT, :INHERITED] })
diff --git a/spec/requests/api/graphql/group/packages_spec.rb b/spec/requests/api/graphql/group/packages_spec.rb
index adee556db3a..cf8736db5af 100644
--- a/spec/requests/api/graphql/group/packages_spec.rb
+++ b/spec/requests/api/graphql/group/packages_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe 'getting a package list for a group' do
it 'returns an error for the second group and data for the first' do
expect(a_packages_names).to contain_exactly(group_one_package.name)
- expect_graphql_errors_to_include [/Packages can be requested only for one group at a time/]
+ expect_graphql_errors_to_include [/"packages" field can be requested only for 1 Group\(s\) at a time./]
expect(graphql_data_at(:b, :packages)).to be(nil)
end
end
diff --git a/spec/requests/api/graphql/group/work_item_types_spec.rb b/spec/requests/api/graphql/group/work_item_types_spec.rb
index a33e3ae5427..d6b0673e4f8 100644
--- a/spec/requests/api/graphql/group/work_item_types_spec.rb
+++ b/spec/requests/api/graphql/group/work_item_types_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe 'getting a list of work item types for a group' do
end
end
- context "when user doesn't have acces to the group" do
+ context "when user doesn't have access to the group" do
let(:current_user) { create(:user) }
before do
diff --git a/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb b/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb
index 46ec22e7ef8..06093e9f7c2 100644
--- a/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb
+++ b/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb
@@ -100,6 +100,20 @@ RSpec.describe 'Reposition and move issue within board lists' do
expect(response_issue['labels']['edges'][0]['node']['title']).to eq(testing.title)
end
end
+
+ context 'when moving an issue using position_in_list' do
+ let(:issue_move_params) { { from_list_id: list1.id, to_list_id: list2.id, position_in_list: 0 } }
+
+ it 'repositions an issue' do
+ post_graphql_mutation(mutation(params), current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ response_issue = json_response['data'][mutation_result_identifier]['issue']
+ expect(response_issue['iid']).to eq(issue1.iid.to_s)
+ expect(response_issue['labels']['edges'][0]['node']['title']).to eq(testing.title)
+ expect(response_issue['relativePosition']).to be < existing_issue1.relative_position
+ end
+ end
end
context 'when user has no access to resources' do
diff --git a/spec/requests/api/graphql/mutations/branches/create_spec.rb b/spec/requests/api/graphql/mutations/branches/create_spec.rb
index 6a098002963..9ee2f41e8fc 100644
--- a/spec/requests/api/graphql/mutations/branches/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/branches/create_spec.rb
@@ -5,26 +5,18 @@ require 'spec_helper'
RSpec.describe 'Creation of a new branch' do
include GraphqlHelpers
+ let_it_be(:group) { create(:group, :public) }
let_it_be(:current_user) { create(:user) }
- let_it_be(:project) { create(:project, :public, :empty_repo) }
let(:input) { { project_path: project.full_path, name: new_branch, ref: ref } }
- let(:new_branch) { 'new_branch' }
+ let(:new_branch) { "new_branch_#{SecureRandom.hex(4)}" }
let(:ref) { 'master' }
let(:mutation) { graphql_mutation(:create_branch, input) }
let(:mutation_response) { graphql_mutation_response(:create_branch) }
- context 'the user is not allowed to create a branch' do
- it_behaves_like 'a mutation that returns a top-level access error'
- end
-
- context 'when user has permissions to create a branch' do
- before do
- project.add_developer(current_user)
- end
-
- it 'creates a new branch' do
+ shared_examples 'creates a new branch' do
+ specify do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
@@ -33,14 +25,75 @@ RSpec.describe 'Creation of a new branch' do
'commit' => a_hash_including('id')
)
end
+ end
+
+ context 'when project is public' do
+ let_it_be(:project) { create(:project, :public, :empty_repo) }
+
+ context 'when user is not allowed to create a branch' do
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user is a direct project member' do
+ context 'and user is a developer' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'creates a new branch'
+
+ context 'when ref is not correct' do
+ err_msg = 'Failed to create branch \'another_branch\': invalid reference name \'unknown\''
+ let(:new_branch) { 'another_branch' }
+ let(:ref) { 'unknown' }
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: [err_msg]
+ end
+ end
+ end
+
+ context 'when user is an inherited member from the group' do
+ context 'when project has a private repository' do
+ let_it_be(:project) { create(:project, :public, :empty_repo, :repository_private, group: group) }
+
+ context 'and user is a guest' do
+ before do
+ group.add_guest(current_user)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'and user is a developer' do
+ before do
+ group.add_developer(current_user)
+ end
+
+ it_behaves_like 'creates a new branch'
+ end
+ end
+ end
+ end
+
+ context 'when project is private' do
+ let_it_be(:project) { create(:project, :private, :empty_repo, group: group) }
+
+ context 'when user is an inherited member from the group' do
+ context 'and user is a guest' do
+ before do
+ group.add_guest(current_user)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
- context 'when ref is not correct' do
- err_msg = 'Failed to create branch \'another_branch\': invalid reference name \'unknown\''
- let(:new_branch) { 'another_branch' }
- let(:ref) { 'unknown' }
+ context 'and user is a developer' do
+ before do
+ group.add_developer(current_user)
+ end
- it_behaves_like 'a mutation that returns errors in the response',
- errors: [err_msg]
+ it_behaves_like 'creates a new branch'
+ end
end
end
end
diff --git a/spec/requests/api/graphql/mutations/ci/job/destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/job/destroy_spec.rb
new file mode 100644
index 00000000000..5855eb6bb51
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ci/job/destroy_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'JobArtifactsDestroy' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:job) { create(:ci_build) }
+
+ let(:mutation) do
+ variables = {
+ id: job.to_global_id.to_s
+ }
+ graphql_mutation(:job_artifacts_destroy, variables, <<~FIELDS)
+ job {
+ name
+ }
+ destroyedArtifactsCount
+ errors
+ FIELDS
+ end
+
+ before do
+ create(:ci_job_artifact, :archive, job: job)
+ create(:ci_job_artifact, :junit, job: job)
+ end
+
+ it 'returns an error if the user is not allowed to destroy the job artifacts' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_errors).not_to be_empty
+ expect(job.reload.job_artifacts.count).to be(2)
+ end
+
+ it 'destroys the job artifacts and returns the expected data' do
+ job.project.add_maintainer(user)
+ expected_data = {
+ 'jobArtifactsDestroy' => {
+ 'errors' => [],
+ 'destroyedArtifactsCount' => 2,
+ 'job' => {
+ 'name' => job.name
+ }
+ }
+ }
+
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(graphql_data).to eq(expected_data)
+ expect(job.reload.job_artifacts.count).to be(0)
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/ci/job_artifact/destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/job_artifact/destroy_spec.rb
new file mode 100644
index 00000000000..a5ec9ea343d
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ci/job_artifact/destroy_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ArtifactDestroy' do
+ include GraphqlHelpers
+
+ let(:user) { create(:user) }
+ let(:artifact) { create(:ci_job_artifact) }
+
+ let(:mutation) do
+ variables = {
+ id: artifact.to_global_id.to_s
+ }
+ graphql_mutation(:artifact_destroy, variables, 'errors')
+ end
+
+ it 'returns an error if the user is not allowed to destroy the artifact' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_errors).not_to be_empty
+ end
+
+ context 'when the user is allowed to destroy the artifact' do
+ before do
+ artifact.job.project.add_maintainer(user)
+ end
+
+ it 'destroys the artifact' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect { artifact.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'returns error if destory fails' do
+ allow_next_found_instance_of(Ci::JobArtifact) do |instance|
+ allow(instance).to receive(:destroy).and_return(false)
+ allow(instance).to receive_message_chain(:errors, :full_messages).and_return(['cannot be removed'])
+ end
+
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_data_at(:artifact_destroy, :errors)).to contain_exactly('cannot be removed')
+ 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
index c91437fa355..66facdebe78 100644
--- a/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb
@@ -39,5 +39,19 @@ RSpec.describe 'Creation of a new Custom Emoji' do
expect(gql_response['customEmoji']['name']).to eq(attributes[:name])
expect(gql_response['customEmoji']['url']).to eq(attributes[:url])
end
+
+ context 'when the custom_emoji feature flag is disabled' do
+ before do
+ stub_feature_flags(custom_emoji: false)
+ end
+
+ it 'does nothing and returns and error' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to not_change(CustomEmoji, :count)
+
+ expect_graphql_errors_to_include('Custom emoji feature is disabled')
+ end
+ end
end
end
diff --git a/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb b/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb
index 07fd57a2cee..7d25206e617 100644
--- a/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb
@@ -68,6 +68,20 @@ RSpec.describe 'Deletion of custom emoji' do
end
it_behaves_like 'deletes custom emoji'
+
+ context 'when the custom_emoji feature flag is disabled' do
+ before do
+ stub_feature_flags(custom_emoji: false)
+ end
+
+ it_behaves_like 'does not delete custom emoji'
+
+ it 'returns an error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect_graphql_errors_to_include('Custom emoji feature is disabled')
+ end
+ end
end
end
end
diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb
index 9272e218172..85eaec90f47 100644
--- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Promote an incident timeline event from a comment' do
include GraphqlHelpers
+ include NotesHelper
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
@@ -53,7 +54,7 @@ RSpec.describe 'Promote an incident timeline event from a comment' do
'promotedFromNote' => {
'id' => comment.to_global_id.to_s
},
- 'note' => comment.note,
+ 'note' => "@#{comment.author.username} [commented](#{noteable_note_url(comment)}): '#{comment.note}'",
'action' => 'comment',
'editable' => true,
'occurredAt' => comment.created_at.iso8601
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
index 608b36e4f15..8cec5867aca 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -93,6 +93,16 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do
expect(response).to have_gitlab_http_status(:success)
expect(mutation_assignee_nodes).to match_array(expected_result)
end
+
+ it 'triggers webhooks', :sidekiq_inline do
+ hook = create(:project_hook, merge_requests_events: true, project: merge_request.project)
+
+ expect(WebHookWorker).to receive(:perform_async).with(hook.id, anything, 'merge_request_hooks', anything)
+
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
end
context 'when passing an empty list of assignees' do
diff --git a/spec/requests/api/graphql/mutations/releases/update_spec.rb b/spec/requests/api/graphql/mutations/releases/update_spec.rb
index 33d4e57904c..240db764f40 100644
--- a/spec/requests/api/graphql/mutations/releases/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/releases/update_spec.rb
@@ -22,9 +22,14 @@ RSpec.describe 'Updating an existing release' do
let_it_be(:milestones) { [milestone_12_3, milestone_12_4] }
let_it_be(:release) do
- create(:release, project: project, tag: tag_name, name: name,
- description: description, released_at: Time.parse(released_at).utc,
- created_at: Time.parse(created_at).utc, milestones: milestones)
+ create(:release,
+ project: project,
+ tag: tag_name,
+ name: name,
+ description: description,
+ released_at: Time.parse(released_at).utc,
+ created_at: Time.parse(created_at).utc,
+ milestones: milestones)
end
let(:mutation_name) { :release_update }
diff --git a/spec/requests/api/graphql/packages/composer_spec.rb b/spec/requests/api/graphql/packages/composer_spec.rb
index 9830623ede8..89c01d44771 100644
--- a/spec/requests/api/graphql/packages/composer_spec.rb
+++ b/spec/requests/api/graphql/packages/composer_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'package details' do
include GraphqlHelpers
include_context 'package details setup'
- let_it_be(:package) { create(:composer_package, project: project) }
+ let_it_be(:package) { create(:composer_package, :last_downloaded_at, project: project) }
let_it_be(:composer_json) { { name: 'name', type: 'type', license: 'license', version: 1 } }
let_it_be(:composer_metadatum) do
# we are forced to manually create the metadatum, without using the factory to force the sha to be a string
diff --git a/spec/requests/api/graphql/packages/conan_spec.rb b/spec/requests/api/graphql/packages/conan_spec.rb
index 5bd5a71bbeb..7ad85edecef 100644
--- a/spec/requests/api/graphql/packages/conan_spec.rb
+++ b/spec/requests/api/graphql/packages/conan_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'conan package details' do
include GraphqlHelpers
include_context 'package details setup'
- let_it_be(:package) { create(:conan_package, project: project) }
+ let_it_be(:package) { create(:conan_package, :last_downloaded_at, project: project) }
let(:metadata) { query_graphql_fragment('ConanMetadata') }
let(:package_files_metadata) { query_graphql_fragment('ConanFileMetadata') }
diff --git a/spec/requests/api/graphql/packages/helm_spec.rb b/spec/requests/api/graphql/packages/helm_spec.rb
index 1675b8faa23..79a589e2dc2 100644
--- a/spec/requests/api/graphql/packages/helm_spec.rb
+++ b/spec/requests/api/graphql/packages/helm_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'helm package details' do
include GraphqlHelpers
include_context 'package details setup'
- let_it_be(:package) { create(:helm_package, project: project) }
+ let_it_be(:package) { create(:helm_package, :last_downloaded_at, project: project) }
let(:package_files_metadata) { query_graphql_fragment('HelmFileMetadata') }
diff --git a/spec/requests/api/graphql/packages/maven_spec.rb b/spec/requests/api/graphql/packages/maven_spec.rb
index 9d59a922660..b7f39efcf73 100644
--- a/spec/requests/api/graphql/packages/maven_spec.rb
+++ b/spec/requests/api/graphql/packages/maven_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'maven package details' do
include GraphqlHelpers
include_context 'package details setup'
- let_it_be(:package) { create(:maven_package, project: project) }
+ let_it_be(:package) { create(:maven_package, :last_downloaded_at, project: project) }
let(:metadata) { query_graphql_fragment('MavenMetadata') }
@@ -31,7 +31,9 @@ RSpec.describe 'maven package details' do
context 'a versionless maven package' do
let_it_be(:maven_metadatum) { create(:maven_metadatum, app_version: nil) }
- let_it_be(:package) { create(:maven_package, project: project, version: nil, maven_metadatum: maven_metadatum) }
+ let_it_be(:package) do
+ create(:maven_package, :last_downloaded_at, project: project, version: nil, maven_metadatum: maven_metadatum)
+ end
subject { post_graphql(query, current_user: user) }
diff --git a/spec/requests/api/graphql/packages/nuget_spec.rb b/spec/requests/api/graphql/packages/nuget_spec.rb
index 87cffc67ce5..7de132d1574 100644
--- a/spec/requests/api/graphql/packages/nuget_spec.rb
+++ b/spec/requests/api/graphql/packages/nuget_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'nuget package details' do
include GraphqlHelpers
include_context 'package details setup'
- let_it_be(:package) { create(:nuget_package, :with_metadatum, project: project) }
+ let_it_be(:package) { create(:nuget_package, :last_downloaded_at, :with_metadatum, project: project) }
let_it_be(:dependency_link) { create(:packages_dependency_link, :with_nuget_metadatum, package: package) }
let(:metadata) { query_graphql_fragment('NugetMetadata') }
diff --git a/spec/requests/api/graphql/packages/package_spec.rb b/spec/requests/api/graphql/packages/package_spec.rb
index c28b37db5af..e9f82d66775 100644
--- a/spec/requests/api/graphql/packages/package_spec.rb
+++ b/spec/requests/api/graphql/packages/package_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe 'package details' do
let_it_be_with_reload(:group) { create(:group) }
let_it_be_with_reload(:project) { create(:project, group: group) }
+ let_it_be_with_reload(:composer_package) { create(:composer_package, :last_downloaded_at, project: project) }
let_it_be(:user) { create(:user) }
- let_it_be(:composer_package) { create(:composer_package, project: project) }
let_it_be(:composer_json) { { name: 'name', type: 'type', license: 'license', version: 1 } }
let_it_be(:composer_metadatum) do
# we are forced to manually create the metadatum, without using the factory to force the sha to be a string
@@ -65,6 +65,17 @@ RSpec.describe 'package details' do
end
end
+ context 'with package without last_downloaded_at' do
+ before do
+ composer_package.update!(last_downloaded_at: nil)
+ subject
+ end
+
+ it 'matches the JSON schema' do
+ expect(package_details).to match_schema('graphql/packages/package_details')
+ end
+ end
+
context 'with package files pending destruction' do
let_it_be(:package_file) { create(:package_file, package: composer_package) }
let_it_be(:package_file_pending_destruction) { create(:package_file, :pending_destruction, package: composer_package) }
@@ -97,7 +108,7 @@ RSpec.describe 'package details' do
expect(graphql_data_at(:a, :name)).to eq(composer_package.name)
- expect_graphql_errors_to_include [/Package details can be requested only for one package at a time/]
+ expect_graphql_errors_to_include [/"package" field can be requested only for 1 Query\(s\) at a time./]
expect(graphql_data_at(:b)).to be(nil)
end
end
diff --git a/spec/requests/api/graphql/packages/pypi_spec.rb b/spec/requests/api/graphql/packages/pypi_spec.rb
index 0cc5bd2e3b2..c0e589f3597 100644
--- a/spec/requests/api/graphql/packages/pypi_spec.rb
+++ b/spec/requests/api/graphql/packages/pypi_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'pypi package details' do
include GraphqlHelpers
include_context 'package details setup'
- let_it_be(:package) { create(:pypi_package, project: project) }
+ let_it_be(:package) { create(:pypi_package, :last_downloaded_at, project: project) }
let(:metadata) { query_graphql_fragment('PypiMetadata') }
diff --git a/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb b/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb
new file mode 100644
index 00000000000..cb5006ec8e4
--- /dev/null
+++ b/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting merge access levels for a branch protection' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+
+ let(:merge_access_level_data) { merge_access_levels_data[0] }
+
+ let(:merge_access_levels_data) do
+ graphql_data_at('project',
+ 'branchRules',
+ 'nodes',
+ 0,
+ 'branchProtection',
+ 'mergeAccessLevels',
+ 'nodes')
+ end
+
+ let(:project) { protected_branch.project }
+
+ let(:merge_access_levels_count) { protected_branch.merge_access_levels.size }
+
+ let(:variables) { { path: project.full_path } }
+
+ let(:fields) { all_graphql_fields_for('MergeAccessLevel') }
+
+ let(:query) do
+ <<~GQL
+ query($path: ID!) {
+ project(fullPath: $path) {
+ branchRules(first: 1) {
+ nodes {
+ branchProtection {
+ mergeAccessLevels {
+ nodes {
+ #{fields}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ context 'when the user does not have read_protected_branch abilities' do
+ let_it_be(:protected_branch) { create(:protected_branch) }
+
+ before do
+ project.add_guest(current_user)
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(merge_access_levels_data).not_to be_present }
+ end
+
+ shared_examples 'merge access request' do
+ let(:merge_access) { protected_branch.merge_access_levels.first }
+
+ before do
+ project.add_maintainer(current_user)
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns all merge access levels' do
+ expect(merge_access_levels_data.size).to eq(merge_access_levels_count)
+ end
+
+ it 'includes access_level' do
+ expect(merge_access_level_data['accessLevel'])
+ .to eq(merge_access.access_level)
+ end
+
+ it 'includes access_level_description' do
+ expect(merge_access_level_data['accessLevelDescription'])
+ .to eq(merge_access.humanize)
+ end
+ end
+
+ context 'when the user does have read_protected_branch abilities' do
+ let(:merge_access) { protected_branch.merge_access_levels.first }
+
+ context 'when no one has access' do
+ let_it_be(:protected_branch) { create(:protected_branch, :no_one_can_merge) }
+
+ it_behaves_like 'merge access request'
+ end
+
+ context 'when developers have access' do
+ let_it_be(:protected_branch) { create(:protected_branch, :developers_can_merge) }
+
+ it_behaves_like 'merge access request'
+ end
+
+ context 'when maintainers have access' do
+ let_it_be(:protected_branch) { create(:protected_branch, :maintainers_can_merge) }
+
+ it_behaves_like 'merge access request'
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb b/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb
new file mode 100644
index 00000000000..59f9c7d61cb
--- /dev/null
+++ b/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting push access levels for a branch protection' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+
+ let(:push_access_level_data) { push_access_levels_data[0] }
+
+ let(:push_access_levels_data) do
+ graphql_data_at('project',
+ 'branchRules',
+ 'nodes',
+ 0,
+ 'branchProtection',
+ 'pushAccessLevels',
+ 'nodes')
+ end
+
+ let(:project) { protected_branch.project }
+
+ let(:push_access_levels_count) { protected_branch.push_access_levels.size }
+
+ let(:variables) { { path: project.full_path } }
+
+ let(:fields) { all_graphql_fields_for('PushAccessLevel'.classify) }
+
+ let(:query) do
+ <<~GQL
+ query($path: ID!) {
+ project(fullPath: $path) {
+ branchRules(first: 1) {
+ nodes {
+ branchProtection {
+ pushAccessLevels {
+ nodes {
+ #{fields}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ context 'when the user does not have read_protected_branch abilities' do
+ let_it_be(:protected_branch) { create(:protected_branch) }
+
+ before do
+ project.add_guest(current_user)
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(push_access_levels_data).not_to be_present }
+ end
+
+ shared_examples 'push access request' do
+ let(:push_access) { protected_branch.push_access_levels.first }
+
+ before do
+ project.add_maintainer(current_user)
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns all push access levels' do
+ expect(push_access_levels_data.size).to eq(push_access_levels_count)
+ end
+
+ it 'includes access_level' do
+ expect(push_access_level_data['accessLevel'])
+ .to eq(push_access.access_level)
+ end
+
+ it 'includes access_level_description' do
+ expect(push_access_level_data['accessLevelDescription'])
+ .to eq(push_access.humanize)
+ end
+ end
+
+ context 'when the user does have read_protected_branch abilities' do
+ let(:push_access) { protected_branch.push_access_levels.first }
+
+ context 'when no one has access' do
+ let_it_be(:protected_branch) { create(:protected_branch, :no_one_can_push) }
+
+ it_behaves_like 'push access request'
+ end
+
+ context 'when developers have access' do
+ let_it_be(:protected_branch) { create(:protected_branch, :developers_can_push) }
+
+ it_behaves_like 'push access request'
+ end
+
+ context 'when maintainers have access' do
+ let_it_be(:protected_branch) { create(:protected_branch, :maintainers_can_push) }
+
+ it_behaves_like 'push access request'
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb b/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb
new file mode 100644
index 00000000000..8a3f546ef95
--- /dev/null
+++ b/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting branch protection for a branch rule' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:branch_rule) { create(:protected_branch) }
+ let_it_be(:project) { branch_rule.project }
+
+ let(:branch_protection_data) do
+ graphql_data_at('project', 'branchRules', 'nodes', 0, 'branchProtection')
+ end
+
+ let(:variables) { { path: project.full_path } }
+
+ let(:fields) { all_graphql_fields_for('BranchProtection') }
+
+ let(:query) do
+ <<~GQL
+ query($path: ID!) {
+ project(fullPath: $path) {
+ branchRules(first: 1) {
+ nodes {
+ branchProtection {
+ #{fields}
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ context 'when the user does not have read_protected_branch abilities' do
+ before do
+ project.add_guest(current_user)
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(branch_protection_data).not_to be_present }
+ end
+
+ context 'when the user does have read_protected_branch abilities' do
+ before do
+ project.add_maintainer(current_user)
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'includes allow_force_push' do
+ expect(branch_protection_data['allowForcePush']).to be_in([true, false])
+ expect(branch_protection_data['allowForcePush']).to eq(branch_rule.allow_force_push)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/branch_rules_spec.rb b/spec/requests/api/graphql/project/branch_rules_spec.rb
new file mode 100644
index 00000000000..70fb37941e2
--- /dev/null
+++ b/spec/requests/api/graphql/project/branch_rules_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting list of branch rules for a project' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:branch_name_a) { 'branch_name_a' }
+ let_it_be(:branch_name_b) { 'wildcard-*' }
+ let_it_be(:branch_rules) { [branch_rule_a, branch_rule_b] }
+
+ let_it_be(:branch_rule_a) do
+ create(:protected_branch, project: project, name: branch_name_a)
+ end
+
+ let_it_be(:branch_rule_b) do
+ create(:protected_branch, project: project, name: branch_name_b)
+ end
+
+ let(:branch_rules_data) { graphql_data_at('project', 'branchRules', 'edges') }
+ let(:variables) { { path: project.full_path } }
+
+ let(:fields) do
+ <<~QUERY
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ }
+ edges {
+ cursor
+ node {
+ #{all_graphql_fields_for('branch_rules'.classify)}
+ }
+ }
+ QUERY
+ end
+
+ let(:query) do
+ <<~GQL
+ query($path: ID!, $n: Int, $cursor: String) {
+ project(fullPath: $path) {
+ branchRules(first: $n, after: $cursor) { #{fields} }
+ }
+ }
+ GQL
+ end
+
+ context 'when the user does not have read_protected_branch abilities' do
+ before do
+ project.add_guest(current_user)
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(branch_rules_data).to be_empty }
+ end
+
+ context 'when the user does have read_protected_branch abilities' do
+ before do
+ project.add_maintainer(current_user)
+ post_graphql(query, current_user: current_user, variables: variables)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'includes a name' do
+ expect(branch_rules_data.dig(0, 'node', 'name')).to be_present
+ end
+
+ it 'includes created_at and updated_at' do
+ expect(branch_rules_data.dig(0, 'node', 'createdAt')).to be_present
+ expect(branch_rules_data.dig(1, 'node', 'updatedAt')).to be_present
+ end
+
+ context 'when limiting the number of results' do
+ let(:branch_rule_limit) { 1 }
+ let(:variables) { { path: project.full_path, n: branch_rule_limit } }
+ let(:next_variables) do
+ { path: project.full_path, n: branch_rule_limit, cursor: last_cursor }
+ end
+
+ it_behaves_like 'a working graphql query' do
+ it 'only returns N branch_rules' do
+ expect(branch_rules_data.size).to eq(branch_rule_limit)
+ expect(has_next_page).to be_truthy
+ expect(has_prev_page).to be_falsey
+ post_graphql(query, current_user: current_user, variables: next_variables)
+ expect(branch_rules_data.size).to eq(branch_rule_limit)
+ expect(has_next_page).to be_falsey
+ expect(has_prev_page).to be_truthy
+ end
+ end
+
+ context 'when no limit is provided' do
+ let(:branch_rule_limit) { nil }
+
+ it 'returns all branch_rules' do
+ expect(branch_rules_data.size).to eq(branch_rules.size)
+ end
+ end
+ end
+ end
+
+ def pagination_info
+ graphql_data_at('project', 'branchRules', 'pageInfo')
+ end
+
+ def has_next_page
+ pagination_info['hasNextPage']
+ end
+
+ def has_prev_page
+ pagination_info['hasPreviousPage']
+ end
+
+ def last_cursor
+ branch_rules_data.last['cursor']
+ end
+end
diff --git a/spec/requests/api/graphql/project/deployment_spec.rb b/spec/requests/api/graphql/project/deployment_spec.rb
new file mode 100644
index 00000000000..e5ef7bcafbf
--- /dev/null
+++ b/spec/requests/api/graphql/project/deployment_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Project Deployment query' do
+ let_it_be(:project) { create(:project, :private, :repository) }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+ let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:deployment) { create(:deployment, environment: environment, project: project) }
+
+ subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
+
+ let(:user) { developer }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ deployment(iid: #{deployment.iid}) {
+ id
+ iid
+ ref
+ tag
+ sha
+ createdAt
+ updatedAt
+ finishedAt
+ status
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns the deployment of the project' do
+ deployment_data = subject.dig('data', 'project', 'deployment')
+
+ expect(deployment_data['iid']).to eq(deployment.iid.to_s)
+ end
+
+ context 'when user is guest' do
+ let(:user) { guest }
+
+ it 'returns nothing' do
+ deployment_data = subject.dig('data', 'project', 'deployment')
+
+ expect(deployment_data).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/environments_spec.rb b/spec/requests/api/graphql/project/environments_spec.rb
new file mode 100644
index 00000000000..e5b6aebbf2c
--- /dev/null
+++ b/spec/requests/api/graphql/project/environments_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Project Environments query' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :private, :repository) }
+ let_it_be_with_refind(:production) { create(:environment, :production, project: project) }
+ let_it_be_with_refind(:staging) { create(:environment, :staging, project: project) }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+
+ subject { post_graphql(query, current_user: user) }
+
+ let(:user) { developer }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{production.name}") {
+ slug
+ createdAt
+ updatedAt
+ autoStopAt
+ autoDeleteAt
+ tier
+ environmentType
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns the specified fields of the environment', :aggregate_failures do
+ production.update!(auto_stop_at: 1.day.ago, auto_delete_at: 2.days.ago, environment_type: 'review')
+
+ subject
+
+ environment_data = graphql_data.dig('project', 'environment')
+ expect(environment_data['slug']).to eq(production.slug)
+ expect(environment_data['createdAt']).to eq(production.created_at.iso8601)
+ expect(environment_data['updatedAt']).to eq(production.updated_at.iso8601)
+ expect(environment_data['autoStopAt']).to eq(production.auto_stop_at.iso8601)
+ expect(environment_data['autoDeleteAt']).to eq(production.auto_delete_at.iso8601)
+ expect(environment_data['tier']).to eq(production.tier.upcase)
+ expect(environment_data['environmentType']).to eq(production.environment_type)
+ end
+
+ describe 'last deployments of environments' do
+ ::Deployment.statuses.each do |status, _|
+ let_it_be(:"production_#{status}_deployment") do
+ create(:deployment, status.to_sym, environment: production, project: project)
+ end
+
+ let_it_be(:"staging_#{status}_deployment") do
+ create(:deployment, status.to_sym, environment: staging, project: project)
+ end
+ end
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environments {
+ nodes {
+ name
+ lastSuccessDeployment: lastDeployment(status: SUCCESS) {
+ iid
+ }
+ lastRunningDeployment: lastDeployment(status: RUNNING) {
+ iid
+ }
+ lastBlockedDeployment: lastDeployment(status: BLOCKED) {
+ iid
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'returns all last deployments of the environment' do
+ subject
+
+ environments_data = graphql_data_at(:project, :environments, :nodes)
+
+ environments_data.each do |environment_data|
+ name = environment_data['name']
+ success_deployment = public_send(:"#{name}_success_deployment")
+ running_deployment = public_send(:"#{name}_running_deployment")
+ blocked_deployment = public_send(:"#{name}_blocked_deployment")
+
+ expect(environment_data['lastSuccessDeployment']['iid']).to eq(success_deployment.iid.to_s)
+ expect(environment_data['lastRunningDeployment']['iid']).to eq(running_deployment.iid.to_s)
+ expect(environment_data['lastBlockedDeployment']['iid']).to eq(blocked_deployment.iid.to_s)
+ end
+ end
+
+ it 'executes the same number of queries in single environment and multiple environments' do
+ single_environment_query =
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ environment(name: "#{production.name}") {
+ name
+ lastSuccessDeployment: lastDeployment(status: SUCCESS) {
+ iid
+ }
+ lastRunningDeployment: lastDeployment(status: RUNNING) {
+ iid
+ }
+ lastBlockedDeployment: lastDeployment(status: BLOCKED) {
+ iid
+ }
+ }
+ }
+ }
+ )
+
+ baseline = ActiveRecord::QueryRecorder.new do
+ run_with_clean_state(single_environment_query, context: { current_user: user })
+ end
+
+ multi = ActiveRecord::QueryRecorder.new do
+ run_with_clean_state(query, context: { current_user: user })
+ end
+
+ expect(multi).not_to exceed_query_limit(baseline)
+ end
+ end
+end
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 8cda61f0628..0444ce43c22 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
@@ -11,14 +11,14 @@ RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)
let_it_be(:developer) { create(:user) }
let_it_be(:stranger) { create(:user) }
let_it_be(:old_version) do
- create(:design_version, issue: issue,
- created_designs: create_list(:design, 3, issue: issue))
+ 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,
- created_designs: create_list(:design, 2, issue: issue))
+ create(:design_version,
+ issue: issue,
+ modified_designs: old_version.designs,
+ created_designs: create_list(:design, 2, issue: issue))
end
let(:current_user) { developer }
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 596e023a027..28282860416 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -27,14 +27,6 @@ RSpec.describe 'getting an issue list for a project' do
QUERY
end
- let(:query) do
- graphql_query_for(
- 'project',
- { 'fullPath' => project.full_path },
- query_graphql_field('issues', issue_filter_params, fields)
- )
- end
-
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: current_user)
@@ -89,6 +81,14 @@ RSpec.describe 'getting an issue list for a project' do
end
end
+ context 'when filtering by search' do
+ it_behaves_like 'query with a search term' do
+ let(:issuable_data) { issues_data }
+ let(:user) { current_user }
+ let_it_be(:issuable) { create(:issue, project: project, description: 'bar') }
+ end
+ end
+
context 'when limiting the number of results' do
let(:query) do
<<~GQL
@@ -301,7 +301,7 @@ RSpec.describe 'getting an issue list for a project' do
let_it_be(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) }
context 'when ascending' do
- it_behaves_like 'sorted paginated query' do
+ it_behaves_like 'sorted paginated query', is_reversible: true do
let(:sort_param) { :RELATIVE_POSITION_ASC }
let(:first_param) { 2 }
let(:all_records) do
@@ -679,4 +679,12 @@ RSpec.describe 'getting an issue list for a project' do
def issues_ids
graphql_dig_at(issues_data, :node, :id)
end
+
+ def query(params = issue_filter_params)
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('issues', params, fields)
+ )
+ end
end
diff --git a/spec/requests/api/graphql/project/job_spec.rb b/spec/requests/api/graphql/project/job_spec.rb
new file mode 100644
index 00000000000..6edd4cf753f
--- /dev/null
+++ b/spec/requests/api/graphql/project/job_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.project.job' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:job) { create(:ci_build, project: project, name: 'GQL test job') }
+
+ let(:query) do
+ <<~QUERY
+ {
+ project(fullPath: "#{project.full_path}") {
+ job(id: "#{job.to_global_id}") {
+ name
+ }
+ }
+ }
+ QUERY
+ end
+
+ context 'when the user can read jobs on the project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns the job that matches the given ID' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('project', 'job', 'name')).to eq('GQL test job')
+ end
+
+ context 'when no job matches the given ID' do
+ let(:job) { create(:ci_build, project: create(:project), name: 'Job from another project') }
+
+ it 'returns null' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('project', 'job')).to be_nil
+ end
+ end
+ end
+
+ context 'when the user cannot read jobs on the project' do
+ it 'returns null' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('project', 'job')).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index d2f34080be3..6a59df81405 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -365,7 +365,7 @@ RSpec.describe 'getting merge request information nested in a project' do
expect(interaction_data).to contain_exactly a_hash_including(
'canMerge' => false,
'canUpdate' => can_update,
- 'reviewState' => attention_requested,
+ 'reviewState' => unreviewed,
'reviewed' => false,
'approved' => false
)
@@ -398,8 +398,8 @@ RSpec.describe 'getting merge request information nested in a project' do
describe 'scalability' do
let_it_be(:other_users) { create_list(:user, 3) }
- let(:attention_requested) do
- { 'reviewState' => 'ATTENTION_REQUESTED' }
+ let(:unreviewed) do
+ { 'reviewState' => 'UNREVIEWED' }
end
let(:reviewed) do
@@ -425,15 +425,15 @@ RSpec.describe 'getting merge request information nested in a project' do
other_users.each do |user|
assign_user(user)
- merge_request.merge_request_reviewers.find_or_create_by!(reviewer: user, state: :attention_requested)
+ merge_request.merge_request_reviewers.find_or_create_by!(reviewer: user)
end
expect { post_graphql(query) }.not_to exceed_query_limit(baseline)
expect(interaction_data).to contain_exactly(
- include(attention_requested),
- include(attention_requested),
- include(attention_requested),
+ include(unreviewed),
+ include(unreviewed),
+ include(unreviewed),
include(reviewed)
)
end
@@ -462,17 +462,17 @@ RSpec.describe 'getting merge request information nested in a project' do
it_behaves_like 'when requesting information about MR interactions' do
let(:field) { :reviewers }
- let(:attention_requested) { 'ATTENTION_REQUESTED' }
+ let(:unreviewed) { 'UNREVIEWED' }
let(:can_update) { false }
def assign_user(user)
- merge_request.merge_request_reviewers.create!(reviewer: user, state: :attention_requested)
+ merge_request.merge_request_reviewers.create!(reviewer: user)
end
end
it_behaves_like 'when requesting information about MR interactions' do
let(:field) { :assignees }
- let(:attention_requested) { nil }
+ let(:unreviewed) { nil }
let(:can_update) { true } # assignees can update MRs
def assign_user(user)
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index 08c6a2d9927..41915d3cdee 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -111,7 +111,7 @@ RSpec.describe 'getting pipeline information nested in a project' do
name: build_job.name,
pipeline: pipeline,
stage_idx: 0,
- stage: build_job.stage)
+ stage: build_job.stage_name)
end
let(:fields) do
diff --git a/spec/requests/api/graphql/project/terraform/state_spec.rb b/spec/requests/api/graphql/project/terraform/state_spec.rb
index 8f2d2cffef2..5e207ec0963 100644
--- a/spec/requests/api/graphql/project/terraform/state_spec.rb
+++ b/spec/requests/api/graphql/project/terraform/state_spec.rb
@@ -60,17 +60,17 @@ RSpec.describe 'query a single terraform state' do
expect(data).to match a_graphql_entity_for(
terraform_state,
:name,
- 'lockedAt' => terraform_state.locked_at.iso8601,
- 'createdAt' => terraform_state.created_at.iso8601,
- 'updatedAt' => terraform_state.updated_at.iso8601,
- 'lockedByUser' => a_graphql_entity_for(terraform_state.locked_by_user),
+ 'lockedAt' => terraform_state.locked_at.iso8601,
+ 'createdAt' => terraform_state.created_at.iso8601,
+ 'updatedAt' => terraform_state.updated_at.iso8601,
+ 'lockedByUser' => a_graphql_entity_for(terraform_state.locked_by_user),
'latestVersion' => a_graphql_entity_for(
latest_version,
- 'serial' => eq(latest_version.version),
- 'createdAt' => eq(latest_version.created_at.iso8601),
- 'updatedAt' => eq(latest_version.updated_at.iso8601),
+ 'serial' => eq(latest_version.version),
+ 'createdAt' => eq(latest_version.created_at.iso8601),
+ 'updatedAt' => eq(latest_version.updated_at.iso8601),
'createdByUser' => a_graphql_entity_for(latest_version.created_by_user),
- 'job' => { 'name' => eq(latest_version.build.name) }
+ 'job' => { 'name' => eq(latest_version.build.name) }
)
)
end
diff --git a/spec/requests/api/graphql/project/terraform/states_spec.rb b/spec/requests/api/graphql/project/terraform/states_spec.rb
index a7ec6f69776..cc3660bcc6b 100644
--- a/spec/requests/api/graphql/project/terraform/states_spec.rb
+++ b/spec/requests/api/graphql/project/terraform/states_spec.rb
@@ -64,18 +64,18 @@ RSpec.describe 'query terraform states' do
expect(data['nodes']).to contain_exactly a_graphql_entity_for(
terraform_state, :name,
- 'lockedAt' => terraform_state.locked_at.iso8601,
- 'createdAt' => terraform_state.created_at.iso8601,
- 'updatedAt' => terraform_state.updated_at.iso8601,
- 'lockedByUser' => a_graphql_entity_for(terraform_state.locked_by_user),
+ 'lockedAt' => terraform_state.locked_at.iso8601,
+ 'createdAt' => terraform_state.created_at.iso8601,
+ 'updatedAt' => terraform_state.updated_at.iso8601,
+ 'lockedByUser' => a_graphql_entity_for(terraform_state.locked_by_user),
'latestVersion' => a_graphql_entity_for(
latest_version,
- 'serial' => eq(latest_version.version),
- 'downloadPath' => eq(download_path),
- 'createdAt' => eq(latest_version.created_at.iso8601),
- 'updatedAt' => eq(latest_version.updated_at.iso8601),
+ 'serial' => eq(latest_version.version),
+ 'downloadPath' => eq(download_path),
+ 'createdAt' => eq(latest_version.created_at.iso8601),
+ 'updatedAt' => eq(latest_version.updated_at.iso8601),
'createdByUser' => a_graphql_entity_for(latest_version.created_by_user),
- 'job' => { 'name' => eq(latest_version.build.name) }
+ 'job' => { 'name' => eq(latest_version.build.name) }
)
)
end
diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb
index 6ef28392b8b..69f8d1cac74 100644
--- a/spec/requests/api/graphql/project/work_items_spec.rb
+++ b/spec/requests/api/graphql/project/work_items_spec.rb
@@ -10,7 +10,10 @@ RSpec.describe 'getting an work item list for a project' do
let_it_be(:current_user) { create(:user) }
let_it_be(:item1) { create(:work_item, project: project, discussion_locked: true, title: 'item1') }
- let_it_be(:item2) { create(:work_item, project: project, title: 'item2') }
+ let_it_be(:item2) do
+ create(:work_item, project: project, title: 'item2', last_edited_by: current_user, last_edited_at: 1.day.ago)
+ end
+
let_it_be(:confidential_item) { create(:work_item, confidential: true, project: project, title: 'item3') }
let_it_be(:other_item) { create(:work_item) }
@@ -27,14 +30,6 @@ RSpec.describe 'getting an work item list for a project' do
QUERY
end
- let(:query) do
- graphql_query_for(
- 'project',
- { 'fullPath' => project.full_path },
- query_graphql_field('workItems', item_filter_params, fields)
- )
- end
-
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: current_user)
@@ -83,6 +78,48 @@ RSpec.describe 'getting an work item list for a project' do
end
end
+ context 'when fetching description edit information' do
+ let(:fields) do
+ <<~GRAPHQL
+ nodes {
+ widgets {
+ type
+ ... on WorkItemWidgetDescription {
+ edited
+ lastEditedAt
+ lastEditedBy {
+ webPath
+ username
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ it 'avoids N+1 queries' do
+ post_graphql(query, current_user: current_user) # warm-up
+
+ control = ActiveRecord::QueryRecorder.new do
+ post_graphql(query, current_user: current_user)
+ end
+ expect_graphql_errors_to_be_empty
+
+ create_list(:work_item, 3, :last_edited_by_user, last_edited_at: 1.week.ago, project: project)
+
+ expect_graphql_errors_to_be_empty
+ expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control)
+ end
+ end
+
+ context 'when filtering by search' do
+ it_behaves_like 'query with a search term' do
+ let(:issuable_data) { items_data }
+ let(:user) { current_user }
+ let_it_be(:issuable) { create(:work_item, project: project, description: 'bar') }
+ end
+ end
+
describe 'sorting and pagination' do
let(:data_path) { [:project, :work_items] }
@@ -118,4 +155,12 @@ RSpec.describe 'getting an work item list for a project' do
def item_ids
graphql_dig_at(items_data, :node, :id)
end
+
+ def query(params = item_filter_params)
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('workItems', params, fields)
+ )
+ end
end
diff --git a/spec/requests/api/graphql/query_spec.rb b/spec/requests/api/graphql/query_spec.rb
index 4aa9c4b8254..359c599cd3a 100644
--- a/spec/requests/api/graphql/query_spec.rb
+++ b/spec/requests/api/graphql/query_spec.rb
@@ -108,8 +108,8 @@ RSpec.describe 'Query' do
design_at_version,
'filename' => design_at_version.design.filename,
'version' => a_graphql_entity_for(version, :sha),
- 'design' => a_graphql_entity_for(design),
- 'issue' => { 'title' => issue.title, 'iid' => issue.iid.to_s },
+ 'design' => a_graphql_entity_for(design),
+ 'issue' => { 'title' => issue.title, 'iid' => issue.iid.to_s },
'project' => a_graphql_entity_for(project, :full_path)
)
end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index 34644e5893a..e4bb4109c76 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -14,7 +14,10 @@ RSpec.describe 'Query.work_item(id)' do
project: project,
description: '- List item',
start_date: Date.today,
- due_date: 1.week.from_now
+ due_date: 1.week.from_now,
+ created_at: 1.week.ago,
+ last_edited_at: 1.day.ago,
+ last_edited_by: guest
)
end
@@ -67,6 +70,12 @@ RSpec.describe 'Query.work_item(id)' do
... on WorkItemWidgetDescription {
description
descriptionHtml
+ edited
+ lastEditedBy {
+ webPath
+ username
+ }
+ lastEditedAt
}
}
GRAPHQL
@@ -79,7 +88,13 @@ RSpec.describe 'Query.work_item(id)' do
hash_including(
'type' => 'DESCRIPTION',
'description' => work_item.description,
- 'descriptionHtml' => ::MarkupHelper.markdown_field(work_item, :description, {})
+ 'descriptionHtml' => ::MarkupHelper.markdown_field(work_item, :description, {}),
+ 'edited' => true,
+ 'lastEditedAt' => work_item.last_edited_at.iso8601,
+ 'lastEditedBy' => {
+ 'webPath' => "/#{guest.full_path}",
+ 'username' => guest.username
+ }
)
)
)